From 05f218fb6617932e56bf5388c3b389c3028a7b73 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 3 Jul 2024 09:17:46 +0200 Subject: [PATCH 01/81] Implement P256 verification via RIP-7212 precompile with Solidity fallback (#4881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: cairo Co-authored-by: sudo rm -rf --no-preserve-root / --- .changeset/odd-lobsters-wash.md | 5 + contracts/mocks/Stateless.sol | 1 + contracts/utils/Errors.sol | 5 + contracts/utils/README.adoc | 2 + contracts/utils/cryptography/P256.sol | 316 ++ contracts/utils/math/Math.sol | 19 +- docs/modules/ROOT/pages/utilities.adoc | 45 +- scripts/checks/coverage.sh | 2 +- test/utils/cryptography/P256.t.sol | 135 + test/utils/cryptography/P256.test.js | 156 + .../ecdsa_secp256r1_sha256_p1363_test.json | 3719 +++++++++++++++++ 11 files changed, 4400 insertions(+), 5 deletions(-) create mode 100644 .changeset/odd-lobsters-wash.md create mode 100644 contracts/utils/cryptography/P256.sol create mode 100644 test/utils/cryptography/P256.t.sol create mode 100644 test/utils/cryptography/P256.test.js create mode 100644 test/utils/cryptography/ecdsa_secp256r1_sha256_p1363_test.json diff --git a/.changeset/odd-lobsters-wash.md b/.changeset/odd-lobsters-wash.md new file mode 100644 index 00000000000..578f7a42ea7 --- /dev/null +++ b/.changeset/odd-lobsters-wash.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`P256`: Library for verification and public key recovery of P256 (aka secp256r1) signatures. diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 6bf78babc7d..7f18d573fda 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -25,6 +25,7 @@ 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 {P256} from "../utils/cryptography/P256.sol"; import {Panic} from "../utils/Panic.sol"; import {Packing} from "../utils/Packing.sol"; import {RSA} from "../utils/cryptography/RSA.sol"; diff --git a/contracts/utils/Errors.sol b/contracts/utils/Errors.sol index 024109444f0..633d8c20286 100644 --- a/contracts/utils/Errors.sol +++ b/contracts/utils/Errors.sol @@ -23,4 +23,9 @@ library Errors { * @dev The deployment failed. */ error FailedDeployment(); + + /** + * @dev A necessary precompile is missing. + */ + error MissingPrecompile(address); } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 056ad3331ac..71c39dfa9c5 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -8,6 +8,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Math}, {SignedMath}: Implementation of various arithmetic functions. * {SafeCast}: Checked downcasting functions to avoid silent truncation. * {ECDSA}, {MessageHashUtils}: Libraries for interacting with ECDSA signatures. + * {P256}: Library for verifying and recovering public keys from secp256r1 signatures. + * {RSA}: Library with RSA PKCS#1 v1.5 signature verification utilities. * {SignatureChecker}: A library helper to support regular ECDSA from EOAs as well as ERC-1271 signatures for smart contracts. * {Hashes}: Commonly used hash functions. * {MerkleProof}: Functions for verifying https://en.wikipedia.org/wiki/Merkle_tree[Merkle Tree] proofs. diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol new file mode 100644 index 00000000000..83c9c975447 --- /dev/null +++ b/contracts/utils/cryptography/P256.sol @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; +import {Errors} from "../Errors.sol"; + +/** + * @dev Implementation of secp256r1 verification and recovery functions. + * + * The secp256r1 curve (also known as P256) is a NIST standard curve with wide support in modern devices + * and cryptographic standards. Some notable examples include Apple's Secure Enclave and Android's Keystore + * as well as authentication protocols like FIDO2. + * + * Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/main/src/Secp256r1.sol[implementation of itsobvioustech]. + * Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/master/contracts/Secp256r1.sol[maxrobot] and + * https://github.com/tdrerup/elliptic-curve-solidity/blob/master/contracts/curves/EllipticCurve.sol[tdrerup] implementations. + */ +library P256 { + struct JPoint { + uint256 x; + uint256 y; + uint256 z; + } + + /// @dev Generator (x component) + uint256 internal constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; + /// @dev Generator (y component) + uint256 internal constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; + /// @dev P (size of the field) + uint256 internal constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; + /// @dev N (order of G) + uint256 internal constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; + /// @dev A parameter of the weierstrass equation + uint256 internal constant A = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; + /// @dev B parameter of the weierstrass equation + uint256 internal constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; + + /// @dev (P + 1) / 4. Useful to compute sqrt + uint256 private constant P1DIV4 = 0x3fffffffc0000000400000000000000000000000400000000000000000000000; + + /// @dev N/2 for excluding higher order `s` values + uint256 private constant HALF_N = 0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8; + + /** + * @dev Verifies a secp256r1 signature using the RIP-7212 precompile and falls back to the Solidity implementation + * if the precompile is not available. This version should work on all chains, but requires the deployment of more + * bytecode. + * + * @param h - hashed message + * @param r - signature half R + * @param s - signature half S + * @param qx - public key coordinate X + * @param qy - public key coordinate Y + * + * IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability. + * To flip the `s` value, compute `s = N - s`. + */ + function verify(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) { + (bool valid, bool supported) = _tryVerifyNative(h, r, s, qx, qy); + return supported ? valid : verifySolidity(h, r, s, qx, qy); + } + + /** + * @dev Same as {verify}, but it will revert if the required precompile is not available. + * + * Make sure any logic (code or precompile) deployed at that address is the expected one, + * otherwise the returned value may be misinterpreted as a positive boolean. + */ + function verifyNative(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) { + (bool valid, bool supported) = _tryVerifyNative(h, r, s, qx, qy); + if (supported) { + return valid; + } else { + revert Errors.MissingPrecompile(address(0x100)); + } + } + + /** + * @dev Same as {verify}, but it will return false if the required precompile is not available. + */ + function _tryVerifyNative( + bytes32 h, + bytes32 r, + bytes32 s, + bytes32 qx, + bytes32 qy + ) private view returns (bool valid, bool supported) { + if (!_isProperSignature(r, s) || !isValidPublicKey(qx, qy)) { + return (false, true); // signature is invalid, and its not because the precompile is missing + } + + (bool success, bytes memory returndata) = address(0x100).staticcall(abi.encode(h, r, s, qx, qy)); + return (success && returndata.length == 0x20) ? (abi.decode(returndata, (bool)), true) : (false, false); + } + + /** + * @dev Same as {verify}, but only the Solidity implementation is used. + */ + function verifySolidity(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) { + if (!_isProperSignature(r, s) || !isValidPublicKey(qx, qy)) { + return false; + } + + JPoint[16] memory points = _preComputeJacobianPoints(uint256(qx), uint256(qy)); + uint256 w = Math.invModPrime(uint256(s), N); + uint256 u1 = mulmod(uint256(h), w, N); + uint256 u2 = mulmod(uint256(r), w, N); + (uint256 x, ) = _jMultShamir(points, u1, u2); + return ((x % N) == uint256(r)); + } + + /** + * @dev Public key recovery + * + * @param h - hashed message + * @param v - signature recovery param + * @param r - signature half R + * @param s - signature half S + * + * IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability. + * To flip the `s` value, compute `s = N - s` and `v = 1 - v` if (`v = 0 | 1`). + */ + function recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal view returns (bytes32, bytes32) { + if (!_isProperSignature(r, s) || v > 1) { + return (0, 0); + } + + uint256 rx = uint256(r); + uint256 ry2 = addmod(mulmod(addmod(mulmod(rx, rx, P), A, P), rx, P), B, P); // weierstrass equation y² = x³ + a.x + b + uint256 ry = Math.modExp(ry2, P1DIV4, P); // This formula for sqrt work because P ≡ 3 (mod 4) + if (mulmod(ry, ry, P) != ry2) return (0, 0); // Sanity check + if (ry % 2 != v % 2) ry = P - ry; + + JPoint[16] memory points = _preComputeJacobianPoints(rx, ry); + uint256 w = Math.invModPrime(uint256(r), N); + uint256 u1 = mulmod(N - (uint256(h) % N), w, N); + uint256 u2 = mulmod(uint256(s), w, N); + (uint256 x, uint256 y) = _jMultShamir(points, u1, u2); + return (bytes32(x), bytes32(y)); + } + + /** + * @dev Checks if (x, y) are valid coordinates of a point on the curve. + * In particular this function checks that x <= P and y <= P. + */ + function isValidPublicKey(bytes32 x, bytes32 y) internal pure returns (bool result) { + assembly ("memory-safe") { + let p := P + let lhs := mulmod(y, y, p) // y^2 + let rhs := addmod(mulmod(addmod(mulmod(x, x, p), A, p), x, p), B, p) // ((x^2 + a) * x) + b = x^3 + ax + b + result := and(and(lt(x, p), lt(y, p)), eq(lhs, rhs)) // Should conform with the Weierstrass equation + } + } + + /** + * @dev Checks if (r, s) is a proper signature. + * In particular, this checks that `s` is in the "lower-range", making the signature non-malleable. + */ + function _isProperSignature(bytes32 r, bytes32 s) private pure returns (bool) { + return uint256(r) > 0 && uint256(r) < N && uint256(s) > 0 && uint256(s) <= HALF_N; + } + + /** + * @dev Reduce from jacobian to affine coordinates + * @param jx - jacobian coordinate x + * @param jy - jacobian coordinate y + * @param jz - jacobian coordinate z + * @return ax - affine coordinate x + * @return ay - affine coordinate y + */ + function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (uint256 ax, uint256 ay) { + if (jz == 0) return (0, 0); + uint256 zinv = Math.invModPrime(jz, P); + uint256 zzinv = mulmod(zinv, zinv, P); + uint256 zzzinv = mulmod(zzinv, zinv, P); + ax = mulmod(jx, zzinv, P); + ay = mulmod(jy, zzzinv, P); + } + + /** + * @dev Point addition on the jacobian coordinates + * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 + */ + function _jAdd( + JPoint memory p1, + uint256 x2, + uint256 y2, + uint256 z2 + ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { + assembly ("memory-safe") { + let p := P + let z1 := mload(add(p1, 0x40)) + let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³ + let s2 := mulmod(y2, mulmod(mulmod(z1, z1, p), z1, p), p) // s2 = y2*z1³ + let r := addmod(s2, sub(p, s1), p) // r = s2-s1 + let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2² + let u2 := mulmod(x2, mulmod(z1, z1, p), p) // u2 = x2*z1² + let h := addmod(u2, sub(p, u1), p) // h = u2-u1 + let hh := mulmod(h, h, p) // h² + + // x' = r²-h³-2*u1*h² + rx := addmod( + addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p), + sub(p, mulmod(2, mulmod(u1, hh, p), p)), + p + ) + // y' = r*(u1*h²-x')-s1*h³ + ry := addmod( + mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), + sub(p, mulmod(s1, mulmod(h, hh, p), p)), + p + ) + // z' = h*z1*z2 + rz := mulmod(h, mulmod(z1, z2, p), p) + } + } + + /** + * @dev Point doubling on the jacobian coordinates + * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2 + */ + function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) { + assembly ("memory-safe") { + let p := P + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ + let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s + + // x' = t + rx := t + // y' = m*(s-t)-8*y⁴ + ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + // z' = 2*y*z + rz := mulmod(2, mulmod(y, z, p), p) + } + } + + /** + * @dev Compute P·u1 + Q·u2 using the precomputed points for P and Q (see {_preComputeJacobianPoints}). + * + * Uses Strauss Shamir trick for EC multiplication + * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method + * we optimise on this a bit to do with 2 bits at a time rather than a single bit + * the individual points for a single pass are precomputed + * overall this reduces the number of additions while keeping the same number of doublings + */ + function _jMultShamir(JPoint[16] memory points, uint256 u1, uint256 u2) private view returns (uint256, uint256) { + uint256 x = 0; + uint256 y = 0; + uint256 z = 0; + unchecked { + for (uint256 i = 0; i < 128; ++i) { + if (z > 0) { + (x, y, z) = _jDouble(x, y, z); + (x, y, z) = _jDouble(x, y, z); + } + // Read 2 bits of u1, and 2 bits of u2. Combining the two give a lookup index in the table. + uint256 pos = ((u1 >> 252) & 0xc) | ((u2 >> 254) & 0x3); + if (pos > 0) { + if (z == 0) { + (x, y, z) = (points[pos].x, points[pos].y, points[pos].z); + } else { + (x, y, z) = _jAdd(points[pos], x, y, z); + } + } + u1 <<= 2; + u2 <<= 2; + } + } + return _affineFromJacobian(x, y, z); + } + + /** + * @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix + * that contains combination of P and G (generator) up to 3 times each. See the table below: + * + * ┌────┬─────────────────────┐ + * │ i │ 0 1 2 3 │ + * ├────┼─────────────────────┤ + * │ 0 │ 0 p 2p 3p │ + * │ 4 │ g g+p g+2p g+3p │ + * │ 8 │ 2g 2g+p 2g+2p 2g+3p │ + * │ 12 │ 3g 3g+p 3g+2p 3g+3p │ + * └────┴─────────────────────┘ + */ + function _preComputeJacobianPoints(uint256 px, uint256 py) private pure returns (JPoint[16] memory points) { + points[0x00] = JPoint(0, 0, 0); // 0,0 + points[0x01] = JPoint(px, py, 1); // 1,0 (p) + points[0x04] = JPoint(GX, GY, 1); // 0,1 (g) + points[0x02] = _jDoublePoint(points[0x01]); // 2,0 (2p) + points[0x08] = _jDoublePoint(points[0x04]); // 0,2 (2g) + points[0x03] = _jAddPoint(points[0x01], points[0x02]); // 3,0 (3p) + points[0x05] = _jAddPoint(points[0x01], points[0x04]); // 1,1 (p+g) + points[0x06] = _jAddPoint(points[0x02], points[0x04]); // 2,1 (2p+g) + points[0x07] = _jAddPoint(points[0x03], points[0x04]); // 3,1 (3p+g) + points[0x09] = _jAddPoint(points[0x01], points[0x08]); // 1,2 (p+2g) + points[0x0a] = _jAddPoint(points[0x02], points[0x08]); // 2,2 (2p+2g) + points[0x0b] = _jAddPoint(points[0x03], points[0x08]); // 3,2 (3p+2g) + points[0x0c] = _jAddPoint(points[0x04], points[0x08]); // 0,3 (g+2g) + points[0x0d] = _jAddPoint(points[0x01], points[0x0c]); // 1,3 (p+3g) + points[0x0e] = _jAddPoint(points[0x02], points[0x0c]); // 2,3 (2p+3g) + points[0x0f] = _jAddPoint(points[0x03], points[0x0C]); // 3,3 (3p+3g) + } + + function _jAddPoint(JPoint memory p1, JPoint memory p2) private pure returns (JPoint memory) { + (uint256 x, uint256 y, uint256 z) = _jAdd(p1, p2.x, p2.y, p2.z); + return JPoint(x, y, z); + } + + function _jDoublePoint(JPoint memory p) private pure returns (JPoint memory) { + (uint256 x, uint256 y, uint256 z) = _jDouble(p.x, p.y, p.z); + return JPoint(x, y, z); + } +} diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 5bf5bddbb09..d5ae271339e 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -237,8 +237,8 @@ library Math { * * If the input value is not inversible, 0 is returned. * - * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Ferma's little theorem and get the - * inverse using `Math.modExp(a, n - 2, n)`. + * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the + * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}. */ function invMod(uint256 a, uint256 n) internal pure returns (uint256) { unchecked { @@ -288,6 +288,21 @@ library Math { } } + /** + * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`. + * + * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is + * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that + * `a**(p-2)` is the modular multiplicative inverse of a in Fp. + * + * NOTE: this function does NOT check that `p` is a prime greater than `2`. + */ + function invModPrime(uint256 a, uint256 p) internal view returns (uint256) { + unchecked { + return Math.modExp(a, p - 2, p); + } + } + /** * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m) * diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 506a581dfad..31d4d5e33ed 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -8,9 +8,9 @@ Here are some of the more popular ones. === Checking Signatures On-Chain -At a high level, signatures are a set of cryptographic algorithms that allow for a _signer_ to prove himself owner of a _private key_ used to authorize an piece of information (generally a transaction or `UserOperation`). Natively, the EVM supports the Elliptic Curve Digital Signature Algorithm (https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm[ECDSA]) using the secp256r1 field, however other signature algorithms such as RSA are supported. +At a high level, signatures are a set of cryptographic algorithms that allow for a _signer_ to prove himself owner of a _private key_ used to authorize an piece of information (generally a transaction or `UserOperation`). Natively, the EVM supports the Elliptic Curve Digital Signature Algorithm (https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm[ECDSA]) using the secp256k1 curve, however other signature algorithms such as P256 and RSA are supported. -==== Ethereum Signatures (secp256r1) +==== Ethereum Signatures (secp256k1) xref:api:utils.adoc#ECDSA[`ECDSA`] provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#sign[`web3.eth.sign`], and are a 65 byte array (of type `bytes` in Solidity) arranged the following way: `[[v (1)], [r (32)], [s (32)]]`. @@ -30,6 +30,47 @@ function _verify(bytes32 data, bytes memory signature, address account) internal WARNING: Getting signature verification right is not trivial: make sure you fully read and understand xref:api:utils.adoc#MessageHashUtils[`MessageHashUtils`]'s and xref:api:utils.adoc#ECDSA[`ECDSA`]'s documentation. +==== P256 Signatures (secp256r1) + +P256, also known as secp256r1, is one of the most used signature schemes. P256 signatures are standardized by the National Institute of Standards and Technology (NIST) and it's widely available in consumer hardware and software. + +These signatures are different to regular Ethereum Signatures (secp256k1) in that they use a different elliptic curve to perform operations but have similar security guarantees. + +[source,solidity] +---- +using P256 for bytes32; + +function _verify( + bytes32 data, + bytes32 r, + bytes32 s, + bytes32 qx, + bytes32 qy +) internal pure returns (bool) { + return data.verify(data, r, s, qx, qy); +} +---- + +By default, the `verify` function will try calling the (https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md)[RIP-7212] precompile at address `0x100` and will fallback to an implementation in Solidity if not available. We encourage you to use `verifyNative` if you know the precompile is available on the chain you're working on and on any other chain on which you intend to use the same bytecode in the future. In case of any doubts regarding the implementation roadmap of the native precompile `P256` of potential future target chains, please consider using `verifySolidity`. + +[source,solidity] +---- +using P256 for bytes32; + +function _verify( + bytes32 data, + bytes32 r, + bytes32 s, + bytes32 qx, + bytes32 qy +) internal pure returns (bool) { + // Will only call the precompile at address(0x100) + return data.verifyNative(data, r, s, qx, qy); +} +---- + +IMPORTANT: The P256 library only allows for `s` values in the lower order of the curve (i.e. `s <= N/2`) to prevent malleability. In case your tooling produces signatures in both sides of the curve, consider flipping the `s` value to keep compatibility. + ==== RSA RSA a public-key cryptosystem that was popularized by corporate and governmental public key infrastructures (https://en.wikipedia.org/wiki/Public_key_infrastructure[PKIs]) and https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions[DNSSEC]. diff --git a/scripts/checks/coverage.sh b/scripts/checks/coverage.sh index 12e2323547a..a591069c493 100755 --- a/scripts/checks/coverage.sh +++ b/scripts/checks/coverage.sh @@ -10,7 +10,7 @@ hardhat coverage if [ "${CI:-"false"}" == "true" ]; then # Foundry coverage - forge coverage --report lcov + forge coverage --report lcov --ir-minimum # Remove zero hits sed -i '/,0/d' lcov.info fi diff --git a/test/utils/cryptography/P256.t.sol b/test/utils/cryptography/P256.t.sol new file mode 100644 index 00000000000..1391afd76ef --- /dev/null +++ b/test/utils/cryptography/P256.t.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {P256} from "@openzeppelin/contracts/utils/cryptography/P256.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +contract P256Test is Test { + /// forge-config: default.fuzz.runs = 512 + function testVerify(uint256 seed, bytes32 digest) public { + uint256 privateKey = bound(uint256(keccak256(abi.encode(seed))), 1, P256.N - 1); + + (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); + s = _ensureLowerS(s); + assertTrue(P256.verify(digest, r, s, x, y)); + assertTrue(P256.verifySolidity(digest, r, s, x, y)); + } + + /// forge-config: default.fuzz.runs = 512 + function testRecover(uint256 seed, bytes32 digest) public { + uint256 privateKey = bound(uint256(keccak256(abi.encode(seed))), 1, P256.N - 1); + + (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); + s = _ensureLowerS(s); + (bytes32 qx0, bytes32 qy0) = P256.recovery(digest, 0, r, s); + (bytes32 qx1, bytes32 qy1) = P256.recovery(digest, 1, r, s); + assertTrue((qx0 == x && qy0 == y) || (qx1 == x && qy1 == y)); + } + + function _ensureLowerS(bytes32 s) private pure returns (bytes32) { + uint256 _s = uint256(s); + unchecked { + return _s > P256.N / 2 ? bytes32(P256.N - _s) : s; + } + } +} + +/** + * @dev Library to derive P256 public key from private key + * Should be removed if Foundry adds this functionality + * See https://github.com/foundry-rs/foundry/issues/7908 + */ +library P256PublicKey { + function getPublicKey(uint256 privateKey) internal view returns (bytes32, bytes32) { + (uint256 x, uint256 y, uint256 z) = _jMult(P256.GX, P256.GY, 1, privateKey); + return _affineFromJacobian(x, y, z); + } + + function _jMult( + uint256 x, + uint256 y, + uint256 z, + uint256 k + ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { + unchecked { + for (uint256 i = 0; i < 256; ++i) { + if (rz > 0) { + (rx, ry, rz) = _jDouble(rx, ry, rz); + } + if (k >> 255 > 0) { + if (rz == 0) { + (rx, ry, rz) = (x, y, z); + } else { + (rx, ry, rz) = _jAdd(rx, ry, rz, x, y, z); + } + } + k <<= 1; + } + } + } + + /// From P256.sol + + function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (bytes32 ax, bytes32 ay) { + if (jz == 0) return (0, 0); + uint256 zinv = Math.invModPrime(jz, P256.P); + uint256 zzinv = mulmod(zinv, zinv, P256.P); + uint256 zzzinv = mulmod(zzinv, zinv, P256.P); + ax = bytes32(mulmod(jx, zzinv, P256.P)); + ay = bytes32(mulmod(jy, zzzinv, P256.P)); + } + + function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) { + uint256 p = P256.P; + uint256 a = P256.A; + assembly ("memory-safe") { + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(a, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ + let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s + + // x' = t + rx := t + // y' = m*(s-t)-8*y⁴ + ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + // z' = 2*y*z + rz := mulmod(2, mulmod(y, z, p), p) + } + } + + function _jAdd( + uint256 x1, + uint256 y1, + uint256 z1, + uint256 x2, + uint256 y2, + uint256 z2 + ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { + uint256 p = P256.P; + assembly ("memory-safe") { + let zz1 := mulmod(z1, z1, p) // zz1 = z1² + let zz2 := mulmod(z2, z2, p) // zz2 = z2² + let u1 := mulmod(x1, zz2, p) // u1 = x1*z2² + let u2 := mulmod(x2, zz1, p) // u2 = x2*z1² + let s1 := mulmod(y1, mulmod(zz2, z2, p), p) // s1 = y1*z2³ + let s2 := mulmod(y2, mulmod(zz1, z1, p), p) // s2 = y2*z1³ + let h := addmod(u2, sub(p, u1), p) // h = u2-u1 + let hh := mulmod(h, h, p) // h² + let hhh := mulmod(h, hh, p) // h³ + let r := addmod(s2, sub(p, s1), p) // r = s2-s1 + + // x' = r²-h³-2*u1*h² + rx := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) + // y' = r*(u1*h²-x')-s1*h³ + ry := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), sub(p, mulmod(s1, hhh, p)), p) + // z' = h*z1*z2 + rz := mulmod(h, mulmod(z1, z2, p), p) + } + } +} diff --git a/test/utils/cryptography/P256.test.js b/test/utils/cryptography/P256.test.js new file mode 100644 index 00000000000..b9655cad3f9 --- /dev/null +++ b/test/utils/cryptography/P256.test.js @@ -0,0 +1,156 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { secp256r1 } = require('@noble/curves/p256'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n; + +// As in ECDSA, signatures are malleable and the tooling produce both high and low S values. +// We need to ensure that the s value is in the lower half of the order of the curve. +const ensureLowerOrderS = ({ s, recovery, ...rest }) => { + if (s > N / 2n) { + s = N - s; + recovery = 1 - recovery; + } + return { s, recovery, ...rest }; +}; + +const prepareSignature = ( + privateKey = secp256r1.utils.randomPrivateKey(), + messageHash = ethers.hexlify(ethers.randomBytes(0x20)), +) => { + const publicKey = [ + secp256r1.getPublicKey(privateKey, false).slice(0x01, 0x21), + secp256r1.getPublicKey(privateKey, false).slice(0x21, 0x41), + ].map(ethers.hexlify); + const { r, s, recovery } = ensureLowerOrderS(secp256r1.sign(messageHash.replace(/0x/, ''), privateKey)); + const signature = [r, s].map(v => ethers.toBeHex(v, 0x20)); + + return { privateKey, publicKey, signature, recovery, messageHash }; +}; + +describe('P256', function () { + async function fixture() { + return { mock: await ethers.deployContract('$P256') }; + } + + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('with signature', function () { + beforeEach(async function () { + Object.assign(this, prepareSignature()); + }); + + it('verify valid signature', async function () { + expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.true; + expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.true; + await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)) + .to.be.revertedWithCustomError(this.mock, 'MissingPrecompile') + .withArgs('0x0000000000000000000000000000000000000100'); + }); + + it('recover public key', async function () { + expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.deep.equal( + this.publicKey, + ); + }); + + it('reject signature with flipped public key coordinates ([x,y] >> [y,x])', async function () { + // flip public key + this.publicKey.reverse(); + + expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; + expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; + expect(await this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; // Flipped public key is not in the curve + }); + + it('reject signature with flipped signature values ([r,s] >> [s,r])', async function () { + // Preselected signature where `r < N/2` and `s < N/2` + this.signature = [ + '0x45350225bad31e89db662fcc4fb2f79f349adbb952b3f652eed1f2aa72fb0356', + '0x513eb68424c42630012309eee4a3b43e0bdc019d179ef0e0c461800845e237ee', + ]; + + // Corresponding hash and public key + this.messageHash = '0x2ad1f900fe63745deeaedfdf396cb6f0f991c4338a9edf114d52f7d1812040a0'; + this.publicKey = [ + '0x9e30de165e521257996425d9bf12a7d366925614bf204eabbb78172b48e52e59', + '0x94bf0fe72f99654d7beae4780a520848e306d46a1275b965c4f4c2b8e9a2c08d', + ]; + + // Make sure it works + expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.true; + + // Flip signature + this.signature.reverse(); + + expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; + expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; + await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)) + .to.be.revertedWithCustomError(this.mock, 'MissingPrecompile') + .withArgs('0x0000000000000000000000000000000000000100'); + expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.not.deep.equal( + this.publicKey, + ); + }); + + it('reject signature with invalid message hash', async function () { + // random message hash + this.messageHash = ethers.hexlify(ethers.randomBytes(32)); + + expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; + expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; + await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)) + .to.be.revertedWithCustomError(this.mock, 'MissingPrecompile') + .withArgs('0x0000000000000000000000000000000000000100'); + expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.not.deep.equal( + this.publicKey, + ); + }); + + it('fail to recover signature with invalid recovery bit', async function () { + // flip recovery bit + this.recovery = 1 - this.recovery; + + expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.not.deep.equal( + this.publicKey, + ); + }); + }); + + // test cases for https://github.com/C2SP/wycheproof/blob/4672ff74d68766e7785c2cac4c597effccef2c5c/testvectors/ecdsa_secp256r1_sha256_p1363_test.json + describe('wycheproof tests', function () { + for (const { key, tests } of require('./ecdsa_secp256r1_sha256_p1363_test.json').testGroups) { + // parse public key + let [x, y] = [key.wx, key.wy].map(v => ethers.stripZerosLeft('0x' + v, 32)); + if (x.length > 66 || y.length > 66) continue; + x = ethers.zeroPadValue(x, 32); + y = ethers.zeroPadValue(y, 32); + + // run all tests for this key + for (const { tcId, comment, msg, sig, result } of tests) { + // only keep properly formatted signatures + if (sig.length != 128) continue; + + it(`${tcId}: ${comment}`, async function () { + // split signature, and reduce modulo N + let [r, s] = Array(2) + .fill() + .map((_, i) => ethers.toBigInt('0x' + sig.substring(64 * i, 64 * (i + 1)))); + // move s to lower part of the curve if needed + if (s <= N && s > N / 2n) s = N - s; + // prepare signature + r = ethers.toBeHex(r, 32); + s = ethers.toBeHex(s, 32); + // hash + const messageHash = ethers.sha256('0x' + msg); + + // check verify + expect(await this.mock.$verify(messageHash, r, s, x, y)).to.equal(result == 'valid'); + }); + } + } + }); +}); diff --git a/test/utils/cryptography/ecdsa_secp256r1_sha256_p1363_test.json b/test/utils/cryptography/ecdsa_secp256r1_sha256_p1363_test.json new file mode 100644 index 00000000000..9cd94cf8b41 --- /dev/null +++ b/test/utils/cryptography/ecdsa_secp256r1_sha256_p1363_test.json @@ -0,0 +1,3719 @@ +{ + "algorithm" : "ECDSA", + "generatorVersion" : "0.8r12", + "numberOfTests" : 219, + "header" : [ + "Test vectors of type EcdsaVerify are meant for the verification", + "of IEEE P1363 encoded ECDSA signatures." + ], + "notes" : { + "EdgeCase" : "Edge case values such as r=1 and s=0 can lead to forgeries if the ECDSA implementation does not check boundaries and computes s^(-1)==0.", + "PointDuplication" : "Some implementations of ECDSA do not handle duplication and points at infinity correctly. This is a test vector that has been specially crafted to check for such an omission.", + "SigSize" : "The size of the signature should always be twice the number of bytes of the size of the order. But some libraries accept signatures with less bytes." + }, + "schema" : "ecdsa_p1363_verify_schema.json", + "testGroups" : [ + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "KSexBRK64-3c_kZ4KBKLrSkDJpkZ9whgacjE32xzKDg", + "y" : "x3h5ZOqsAOWSH7FJimD0YGdms9loUAFVjRqXTnNBUT4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e", + "wx" : "2927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838", + "wy" : "00c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKSexBRK64+3c/kZ4KBKLrSkDJpkZ\n9whgacjE32xzKDjHeHlk6qwA5ZIfsUmKYPRgZ2az2WhQAVWNGpdOc0FRPg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 1, + "comment" : "signature malleability", + "msg" : "313233343030", + "sig" : "2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e184cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd76", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 2, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "012ba3a8bd6b94d5ed80a6d9d1190a436ebccc0833490686deac8635bcb9bf536900b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 3, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "d45c5740946b2a147f59262ee6f5bc90bd01ed280528b62b3aed5fc93f06f739b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 4, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "012ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1800b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 5, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "d45c5741946b2a137f59262ee6f5bc91001af27a5e1117a64733950642a3d1e8b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 6, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "002ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1801b329f478a2bbd0a6c384ee1493b1f518276e0e4a5375928d6fcd160c11cb6d2c", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 7, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "002ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e1801b329f479a2bbd0a5c384ee1493b1f5186a87139cac5df4087c134b49156847db", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 8, + "comment" : "Modified r or s, e.g. by adding or subtracting the order of the group", + "msg" : "313233343030", + "sig" : "2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e184cd60b865d442f5a3c7b11eb6c4e0ae79578ec6353a20bf783ecb4b6ea97b825", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 9, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 10, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 11, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 12, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 13, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 14, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 15, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 16, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 17, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 18, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000001ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 19, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000001ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 20, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000001ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 21, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000001ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 22, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000001ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 23, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325510000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 24, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325510000000000000000000000000000000000000000000000000000000000000001", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 25, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 26, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 27, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 28, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 29, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 30, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325500000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 31, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325500000000000000000000000000000000000000000000000000000000000000001", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 32, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 33, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 34, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 35, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 36, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 37, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325520000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 38, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325520000000000000000000000000000000000000000000000000000000000000001", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 39, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 40, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 41, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 42, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 43, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 44, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 45, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000001", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 46, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000ffffffffffffffffffffffffffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 47, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000ffffffffffffffffffffffffffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 48, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000ffffffffffffffffffffffffffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 49, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000ffffffffffffffffffffffffffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 50, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000ffffffffffffffffffffffffffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 51, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff000000010000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 52, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff000000010000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 53, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000001000000000000000000000000ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 54, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000001000000000000000000000000ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 55, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000001000000000000000000000000ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632552", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 56, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000001000000000000000000000000ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 57, + "comment" : "Signature with special case values for r and s", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000001000000000000000000000000ffffffff00000001000000000000000000000001000000000000000000000000", + "result" : "invalid", + "flags" : [ + "EdgeCase" + ] + }, + { + "tcId" : 58, + "comment" : "Edge case for Shamir multiplication", + "msg" : "3639383139", + "sig" : "64a1aab5000d0e804f3e2fc02bdee9be8ff312334e2ba16d11547c97711c898e6af015971cc30be6d1a206d4e013e0997772a2f91d73286ffd683b9bb2cf4f1b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 59, + "comment" : "special case hash", + "msg" : "343236343739373234", + "sig" : "16aea964a2f6506d6f78c81c91fc7e8bded7d397738448de1e19a0ec580bf266252cd762130c6667cfe8b7bc47d27d78391e8e80c578d1cd38c3ff033be928e9", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 60, + "comment" : "special case hash", + "msg" : "37313338363834383931", + "sig" : "9cc98be2347d469bf476dfc26b9b733df2d26d6ef524af917c665baccb23c882093496459effe2d8d70727b82462f61d0ec1b7847929d10ea631dacb16b56c32", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 61, + "comment" : "special case hash", + "msg" : "3130333539333331363638", + "sig" : "73b3c90ecd390028058164524dde892703dce3dea0d53fa8093999f07ab8aa432f67b0b8e20636695bb7d8bf0a651c802ed25a395387b5f4188c0c4075c88634", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 62, + "comment" : "special case hash", + "msg" : "33393439343031323135", + "sig" : "bfab3098252847b328fadf2f89b95c851a7f0eb390763378f37e90119d5ba3ddbdd64e234e832b1067c2d058ccb44d978195ccebb65c2aaf1e2da9b8b4987e3b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 63, + "comment" : "special case hash", + "msg" : "31333434323933303739", + "sig" : "204a9784074b246d8bf8bf04a4ceb1c1f1c9aaab168b1596d17093c5cd21d2cd51cce41670636783dc06a759c8847868a406c2506fe17975582fe648d1d88b52", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 64, + "comment" : "special case hash", + "msg" : "33373036323131373132", + "sig" : "ed66dc34f551ac82f63d4aa4f81fe2cb0031a91d1314f835027bca0f1ceeaa0399ca123aa09b13cd194a422e18d5fda167623c3f6e5d4d6abb8953d67c0c48c7", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 65, + "comment" : "special case hash", + "msg" : "333433363838373132", + "sig" : "060b700bef665c68899d44f2356a578d126b062023ccc3c056bf0f60a237012b8d186c027832965f4fcc78a3366ca95dedbb410cbef3f26d6be5d581c11d3610", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 66, + "comment" : "special case hash", + "msg" : "31333531353330333730", + "sig" : "9f6adfe8d5eb5b2c24d7aa7934b6cf29c93ea76cd313c9132bb0c8e38c96831db26a9c9e40e55ee0890c944cf271756c906a33e66b5bd15e051593883b5e9902", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 67, + "comment" : "special case hash", + "msg" : "36353533323033313236", + "sig" : "a1af03ca91677b673ad2f33615e56174a1abf6da168cebfa8868f4ba273f16b720aa73ffe48afa6435cd258b173d0c2377d69022e7d098d75caf24c8c5e06b1c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 68, + "comment" : "special case hash", + "msg" : "31353634333436363033", + "sig" : "fdc70602766f8eed11a6c99a71c973d5659355507b843da6e327a28c11893db93df5349688a085b137b1eacf456a9e9e0f6d15ec0078ca60a7f83f2b10d21350", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 69, + "comment" : "special case hash", + "msg" : "34343239353339313137", + "sig" : "b516a314f2fce530d6537f6a6c49966c23456f63c643cf8e0dc738f7b876e675d39ffd033c92b6d717dd536fbc5efdf1967c4bd80954479ba66b0120cd16fff2", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 70, + "comment" : "special case hash", + "msg" : "3130393533323631333531", + "sig" : "3b2cbf046eac45842ecb7984d475831582717bebb6492fd0a485c101e29ff0a84c9b7b47a98b0f82de512bc9313aaf51701099cac5f76e68c8595fc1c1d99258", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 71, + "comment" : "special case hash", + "msg" : "35393837333530303431", + "sig" : "30c87d35e636f540841f14af54e2f9edd79d0312cfa1ab656c3fb15bfde48dcf47c15a5a82d24b75c85a692bd6ecafeb71409ede23efd08e0db9abf6340677ed", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 72, + "comment" : "special case hash", + "msg" : "33343633303036383738", + "sig" : "38686ff0fda2cef6bc43b58cfe6647b9e2e8176d168dec3c68ff262113760f52067ec3b651f422669601662167fa8717e976e2db5e6a4cf7c2ddabb3fde9d67d", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 73, + "comment" : "special case hash", + "msg" : "39383137333230323837", + "sig" : "44a3e23bf314f2b344fc25c7f2de8b6af3e17d27f5ee844b225985ab6e2775cf2d48e223205e98041ddc87be532abed584f0411f5729500493c9cc3f4dd15e86", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 74, + "comment" : "special case hash", + "msg" : "33323232303431303436", + "sig" : "2ded5b7ec8e90e7bf11f967a3d95110c41b99db3b5aa8d330eb9d638781688e97d5792c53628155e1bfc46fb1a67e3088de049c328ae1f44ec69238a009808f9", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 75, + "comment" : "special case hash", + "msg" : "36363636333037313034", + "sig" : "bdae7bcb580bf335efd3bc3d31870f923eaccafcd40ec2f605976f15137d8b8ff6dfa12f19e525270b0106eecfe257499f373a4fb318994f24838122ce7ec3c7", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 76, + "comment" : "special case hash", + "msg" : "31303335393531383938", + "sig" : "50f9c4f0cd6940e162720957ffff513799209b78596956d21ece251c2401f1c6d7033a0a787d338e889defaaabb106b95a4355e411a59c32aa5167dfab244726", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 77, + "comment" : "special case hash", + "msg" : "31383436353937313935", + "sig" : "f612820687604fa01906066a378d67540982e29575d019aabe90924ead5c860d3f9367702dd7dd4f75ea98afd20e328a1a99f4857b316525328230ce294b0fef", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 78, + "comment" : "special case hash", + "msg" : "33313336303436313839", + "sig" : "9505e407657d6e8bc93db5da7aa6f5081f61980c1949f56b0f2f507da5782a7ac60d31904e3669738ffbeccab6c3656c08e0ed5cb92b3cfa5e7f71784f9c5021", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 79, + "comment" : "special case hash", + "msg" : "32363633373834323534", + "sig" : "bbd16fbbb656b6d0d83e6a7787cd691b08735aed371732723e1c68a40404517d9d8e35dba96028b7787d91315be675877d2d097be5e8ee34560e3e7fd25c0f00", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 80, + "comment" : "special case hash", + "msg" : "31363532313030353234", + "sig" : "2ec9760122db98fd06ea76848d35a6da442d2ceef7559a30cf57c61e92df327e7ab271da90859479701fccf86e462ee3393fb6814c27b760c4963625c0a19878", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 81, + "comment" : "special case hash", + "msg" : "35373438303831363936", + "sig" : "54e76b7683b6650baa6a7fc49b1c51eed9ba9dd463221f7a4f1005a89fe00c592ea076886c773eb937ec1cc8374b7915cfd11b1c1ae1166152f2f7806a31c8fd", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 82, + "comment" : "special case hash", + "msg" : "36333433393133343638", + "sig" : "5291deaf24659ffbbce6e3c26f6021097a74abdbb69be4fb10419c0c496c946665d6fcf336d27cc7cdb982bb4e4ecef5827f84742f29f10abf83469270a03dc3", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 83, + "comment" : "special case hash", + "msg" : "31353431313033353938", + "sig" : "207a3241812d75d947419dc58efb05e8003b33fc17eb50f9d15166a88479f107cdee749f2e492b213ce80b32d0574f62f1c5d70793cf55e382d5caadf7592767", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 84, + "comment" : "special case hash", + "msg" : "3130343738353830313238", + "sig" : "6554e49f82a855204328ac94913bf01bbe84437a355a0a37c0dee3cf81aa7728aea00de2507ddaf5c94e1e126980d3df16250a2eaebc8be486effe7f22b4f929", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 85, + "comment" : "special case hash", + "msg" : "3130353336323835353638", + "sig" : "a54c5062648339d2bff06f71c88216c26c6e19b4d80a8c602990ac82707efdfce99bbe7fcfafae3e69fd016777517aa01056317f467ad09aff09be73c9731b0d", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 86, + "comment" : "special case hash", + "msg" : "393533393034313035", + "sig" : "975bd7157a8d363b309f1f444012b1a1d23096593133e71b4ca8b059cff37eaf7faa7a28b1c822baa241793f2abc930bd4c69840fe090f2aacc46786bf919622", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 87, + "comment" : "special case hash", + "msg" : "393738383438303339", + "sig" : "5694a6f84b8f875c276afd2ebcfe4d61de9ec90305afb1357b95b3e0da43885e0dffad9ffd0b757d8051dec02ebdf70d8ee2dc5c7870c0823b6ccc7c679cbaa4", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 88, + "comment" : "special case hash", + "msg" : "33363130363732343432", + "sig" : "a0c30e8026fdb2b4b4968a27d16a6d08f7098f1a98d21620d7454ba9790f1ba65e470453a8a399f15baf463f9deceb53acc5ca64459149688bd2760c65424339", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 89, + "comment" : "special case hash", + "msg" : "31303534323430373035", + "sig" : "614ea84acf736527dd73602cd4bb4eea1dfebebd5ad8aca52aa0228cf7b99a88737cc85f5f2d2f60d1b8183f3ed490e4de14368e96a9482c2a4dd193195c902f", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 90, + "comment" : "special case hash", + "msg" : "35313734343438313937", + "sig" : "bead6734ebe44b810d3fb2ea00b1732945377338febfd439a8d74dfbd0f942fa6bb18eae36616a7d3cad35919fd21a8af4bbe7a10f73b3e036a46b103ef56e2a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 91, + "comment" : "special case hash", + "msg" : "31393637353631323531", + "sig" : "499625479e161dacd4db9d9ce64854c98d922cbf212703e9654fae182df9bad242c177cf37b8193a0131108d97819edd9439936028864ac195b64fca76d9d693", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 92, + "comment" : "special case hash", + "msg" : "33343437323533333433", + "sig" : "08f16b8093a8fb4d66a2c8065b541b3d31e3bfe694f6b89c50fb1aaa6ff6c9b29d6455e2d5d1779748573b611cb95d4a21f967410399b39b535ba3e5af81ca2e", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 93, + "comment" : "special case hash", + "msg" : "333638323634333138", + "sig" : "be26231b6191658a19dd72ddb99ed8f8c579b6938d19bce8eed8dc2b338cb5f8e1d9a32ee56cffed37f0f22b2dcb57d5c943c14f79694a03b9c5e96952575c89", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 94, + "comment" : "special case hash", + "msg" : "33323631313938363038", + "sig" : "15e76880898316b16204ac920a02d58045f36a229d4aa4f812638c455abe0443e74d357d3fcb5c8c5337bd6aba4178b455ca10e226e13f9638196506a1939123", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 95, + "comment" : "special case hash", + "msg" : "39363738373831303934", + "sig" : "352ecb53f8df2c503a45f9846fc28d1d31e6307d3ddbffc1132315cc07f16dad1348dfa9c482c558e1d05c5242ca1c39436726ecd28258b1899792887dd0a3c6", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 96, + "comment" : "special case hash", + "msg" : "34393538383233383233", + "sig" : "4a40801a7e606ba78a0da9882ab23c7677b8642349ed3d652c5bfa5f2a9558fb3a49b64848d682ef7f605f2832f7384bdc24ed2925825bf8ea77dc5981725782", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 97, + "comment" : "special case hash", + "msg" : "383234363337383337", + "sig" : "eacc5e1a8304a74d2be412b078924b3bb3511bac855c05c9e5e9e44df3d61e967451cd8e18d6ed1885dd827714847f96ec4bb0ed4c36ce9808db8f714204f6d1", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 98, + "comment" : "special case hash", + "msg" : "3131303230383333373736", + "sig" : "2f7a5e9e5771d424f30f67fdab61e8ce4f8cd1214882adb65f7de94c31577052ac4e69808345809b44acb0b2bd889175fb75dd050c5a449ab9528f8f78daa10c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 99, + "comment" : "special case hash", + "msg" : "313333383731363438", + "sig" : "ffcda40f792ce4d93e7e0f0e95e1a2147dddd7f6487621c30a03d710b330021979938b55f8a17f7ed7ba9ade8f2065a1fa77618f0b67add8d58c422c2453a49a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 100, + "comment" : "special case hash", + "msg" : "333232313434313632", + "sig" : "81f2359c4faba6b53d3e8c8c3fcc16a948350f7ab3a588b28c17603a431e39a8cd6f6a5cc3b55ead0ff695d06c6860b509e46d99fccefb9f7f9e101857f74300", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 101, + "comment" : "special case hash", + "msg" : "3130363836363535353436", + "sig" : "dfc8bf520445cbb8ee1596fb073ea283ea130251a6fdffa5c3f5f2aaf75ca808048e33efce147c9dd92823640e338e68bfd7d0dc7a4905b3a7ac711e577e90e7", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 102, + "comment" : "special case hash", + "msg" : "3632313535323436", + "sig" : "ad019f74c6941d20efda70b46c53db166503a0e393e932f688227688ba6a576293320eb7ca0710255346bdbb3102cdcf7964ef2e0988e712bc05efe16c199345", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 103, + "comment" : "special case hash", + "msg" : "37303330383138373734", + "sig" : "ac8096842e8add68c34e78ce11dd71e4b54316bd3ebf7fffdeb7bd5a3ebc1883f5ca2f4f23d674502d4caf85d187215d36e3ce9f0ce219709f21a3aac003b7a8", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 104, + "comment" : "special case hash", + "msg" : "35393234353233373434", + "sig" : "677b2d3a59b18a5ff939b70ea002250889ddcd7b7b9d776854b4943693fb92f76b4ba856ade7677bf30307b21f3ccda35d2f63aee81efd0bab6972cc0795db55", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 105, + "comment" : "special case hash", + "msg" : "31343935353836363231", + "sig" : "479e1ded14bcaed0379ba8e1b73d3115d84d31d4b7c30e1f05e1fc0d5957cfb0918f79e35b3d89487cf634a4f05b2e0c30857ca879f97c771e877027355b2443", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 106, + "comment" : "special case hash", + "msg" : "34303035333134343036", + "sig" : "43dfccd0edb9e280d9a58f01164d55c3d711e14b12ac5cf3b64840ead512a0a31dbe33fa8ba84533cd5c4934365b3442ca1174899b78ef9a3199f49584389772", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 107, + "comment" : "special case hash", + "msg" : "33303936343537353132", + "sig" : "5b09ab637bd4caf0f4c7c7e4bca592fea20e9087c259d26a38bb4085f0bbff1145b7eb467b6748af618e9d80d6fdcd6aa24964e5a13f885bca8101de08eb0d75", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 108, + "comment" : "special case hash", + "msg" : "32373834303235363230", + "sig" : "5e9b1c5a028070df5728c5c8af9b74e0667afa570a6cfa0114a5039ed15ee06fb1360907e2d9785ead362bb8d7bd661b6c29eeffd3c5037744edaeb9ad990c20", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 109, + "comment" : "special case hash", + "msg" : "32363138373837343138", + "sig" : "0671a0a85c2b72d54a2fb0990e34538b4890050f5a5712f6d1a7a5fb8578f32edb1846bab6b7361479ab9c3285ca41291808f27fd5bd4fdac720e5854713694c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 110, + "comment" : "special case hash", + "msg" : "31363432363235323632", + "sig" : "7673f8526748446477dbbb0590a45492c5d7d69859d301abbaedb35b2095103a3dc70ddf9c6b524d886bed9e6af02e0e4dec0d417a414fed3807ef4422913d7c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 111, + "comment" : "special case hash", + "msg" : "36383234313839343336", + "sig" : "7f085441070ecd2bb21285089ebb1aa6450d1a06c36d3ff39dfd657a796d12b5249712012029870a2459d18d47da9aa492a5e6cb4b2d8dafa9e4c5c54a2b9a8b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 112, + "comment" : "special case hash", + "msg" : "343834323435343235", + "sig" : "914c67fb61dd1e27c867398ea7322d5ab76df04bc5aa6683a8e0f30a5d287348fa07474031481dda4953e3ac1959ee8cea7e66ec412b38d6c96d28f6d37304ea", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "CtmVACiNRmlAAx1yqfVEWk1DeEZAhVvwpph00t5f4QM", + "y" : "xQEebvLELc1Q1dPSn5mubrosgMkkT0xUIvCXn_DDul4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040ad99500288d466940031d72a9f5445a4d43784640855bf0a69874d2de5fe103c5011e6ef2c42dcd50d5d3d29f99ae6eba2c80c9244f4c5422f0979ff0c3ba5e", + "wx" : "0ad99500288d466940031d72a9f5445a4d43784640855bf0a69874d2de5fe103", + "wy" : "00c5011e6ef2c42dcd50d5d3d29f99ae6eba2c80c9244f4c5422f0979ff0c3ba5e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040ad99500288d466940031d72a9f5445a4d43784640855bf0a69874d2de5fe103c5011e6ef2c42dcd50d5d3d29f99ae6eba2c80c9244f4c5422f0979ff0c3ba5e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECtmVACiNRmlAAx1yqfVEWk1DeEZA\nhVvwpph00t5f4QPFAR5u8sQtzVDV09Kfma5uuiyAySRPTFQi8Jef8MO6Xg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 113, + "comment" : "k*G has a large x-coordinate", + "msg" : "313233343030", + "sig" : "000000000000000000000000000000004319055358e8617b0c46353d039cdaabffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 114, + "comment" : "r too large", + "msg" : "313233343030", + "sig" : "ffffffff00000001000000000000000000000000fffffffffffffffffffffffcffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "qwX9nQ3ia5zm9IGWUtn8aRk9CqOY8PuoAT4JxYIgRVQ", + "y" : "GSNScSKMeGdZCV0St1rwaS3UED8Z9qjDL0lDWh6bjUU" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04ab05fd9d0de26b9ce6f4819652d9fc69193d0aa398f0fba8013e09c58220455419235271228c786759095d12b75af0692dd4103f19f6a8c32f49435a1e9b8d45", + "wx" : "00ab05fd9d0de26b9ce6f4819652d9fc69193d0aa398f0fba8013e09c582204554", + "wy" : "19235271228c786759095d12b75af0692dd4103f19f6a8c32f49435a1e9b8d45" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004ab05fd9d0de26b9ce6f4819652d9fc69193d0aa398f0fba8013e09c58220455419235271228c786759095d12b75af0692dd4103f19f6a8c32f49435a1e9b8d45", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqwX9nQ3ia5zm9IGWUtn8aRk9CqOY\n8PuoAT4JxYIgRVQZI1JxIox4Z1kJXRK3WvBpLdQQPxn2qMMvSUNaHpuNRQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 115, + "comment" : "r,s are large", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "gJhPOaH_OKhqaKpCAba-Xfv-z4diGXELB7rfb91MbFY", + "y" : "Ef65c5DZgm56Bt-0GHHJQNdEFe08rCCJ8URQGbtV7ZU" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0480984f39a1ff38a86a68aa4201b6be5dfbfecf876219710b07badf6fdd4c6c5611feb97390d9826e7a06dfb41871c940d74415ed3cac2089f1445019bb55ed95", + "wx" : "0080984f39a1ff38a86a68aa4201b6be5dfbfecf876219710b07badf6fdd4c6c56", + "wy" : "11feb97390d9826e7a06dfb41871c940d74415ed3cac2089f1445019bb55ed95" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000480984f39a1ff38a86a68aa4201b6be5dfbfecf876219710b07badf6fdd4c6c5611feb97390d9826e7a06dfb41871c940d74415ed3cac2089f1445019bb55ed95", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgJhPOaH/OKhqaKpCAba+Xfv+z4di\nGXELB7rfb91MbFYR/rlzkNmCbnoG37QYcclA10QV7TysIInxRFAZu1XtlQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 116, + "comment" : "r and s^-1 have a large Hamming weight", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd909135bdb6799286170f5ead2de4f6511453fe50914f3df2de54a36383df8dd4", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "QgG0JylEIBwylPW6qaMjK23Wh0lfzBmnCpW8YCtPfAU", + "y" : "lcN-up7oFxwbtaxv6vdTvDb0Y-Ou8WYpVywMCo-wgA4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044201b4272944201c3294f5baa9a3232b6dd687495fcc19a70a95bc602b4f7c0595c37eba9ee8171c1bb5ac6feaf753bc36f463e3aef16629572c0c0a8fb0800e", + "wx" : "4201b4272944201c3294f5baa9a3232b6dd687495fcc19a70a95bc602b4f7c05", + "wy" : "0095c37eba9ee8171c1bb5ac6feaf753bc36f463e3aef16629572c0c0a8fb0800e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044201b4272944201c3294f5baa9a3232b6dd687495fcc19a70a95bc602b4f7c0595c37eba9ee8171c1bb5ac6feaf753bc36f463e3aef16629572c0c0a8fb0800e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQgG0JylEIBwylPW6qaMjK23Wh0lf\nzBmnCpW8YCtPfAWVw366nugXHBu1rG/q91O8NvRj467xZilXLAwKj7CADg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 117, + "comment" : "r and s^-1 have a large Hamming weight", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd27b4577ca009376f71303fd5dd227dcef5deb773ad5f5a84360644669ca249a5", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "pxr2TeUSakpOAreSLWbOlBXOiKTJ0lUU2RCCyHJayVc", + "y" : "XUdyPI--WAuzaf7JwmZdjjCkNbmTJkVILnyfEehyKWs" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a71af64de5126a4a4e02b7922d66ce9415ce88a4c9d25514d91082c8725ac9575d47723c8fbe580bb369fec9c2665d8e30a435b9932645482e7c9f11e872296b", + "wx" : "00a71af64de5126a4a4e02b7922d66ce9415ce88a4c9d25514d91082c8725ac957", + "wy" : "5d47723c8fbe580bb369fec9c2665d8e30a435b9932645482e7c9f11e872296b" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a71af64de5126a4a4e02b7922d66ce9415ce88a4c9d25514d91082c8725ac9575d47723c8fbe580bb369fec9c2665d8e30a435b9932645482e7c9f11e872296b", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpxr2TeUSakpOAreSLWbOlBXOiKTJ\n0lUU2RCCyHJayVddR3I8j75YC7Np/snCZl2OMKQ1uZMmRUgufJ8R6HIpaw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 118, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000001", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 119, + "comment" : "incorrect size of signature", + "msg" : "313233343030", + "sig" : "0501", + "result" : "acceptable", + "flags" : [ + "SigSize" + ] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "ZifOxPBzHqI_wpMfkOvlt1cvWX0g3wj8KzHujvFrFXI", + "y" : "YXDtd9jQoU_FycPEyb5_DT7hj3CbsnXq8gc-JY_mlKU" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046627cec4f0731ea23fc2931f90ebe5b7572f597d20df08fc2b31ee8ef16b15726170ed77d8d0a14fc5c9c3c4c9be7f0d3ee18f709bb275eaf2073e258fe694a5", + "wx" : "6627cec4f0731ea23fc2931f90ebe5b7572f597d20df08fc2b31ee8ef16b1572", + "wy" : "6170ed77d8d0a14fc5c9c3c4c9be7f0d3ee18f709bb275eaf2073e258fe694a5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046627cec4f0731ea23fc2931f90ebe5b7572f597d20df08fc2b31ee8ef16b15726170ed77d8d0a14fc5c9c3c4c9be7f0d3ee18f709bb275eaf2073e258fe694a5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZifOxPBzHqI/wpMfkOvlt1cvWX0g\n3wj8KzHujvFrFXJhcO132NChT8XJw8TJvn8NPuGPcJuyderyBz4lj+aUpQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 120, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000003", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 121, + "comment" : "incorrect size of signature", + "msg" : "313233343030", + "sig" : "0503", + "result" : "acceptable", + "flags" : [ + "SigSize" + ] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "WnyIJehWkczh9edUTFTnPxSvwBDLcxNDJiyn7Fp39b8", + "y" : "727fYqRJfBvXsUf7bD0irzw5v86V8w4ToW09eygS-BM" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045a7c8825e85691cce1f5e7544c54e73f14afc010cb731343262ca7ec5a77f5bfef6edf62a4497c1bd7b147fb6c3d22af3c39bfce95f30e13a16d3d7b2812f813", + "wx" : "5a7c8825e85691cce1f5e7544c54e73f14afc010cb731343262ca7ec5a77f5bf", + "wy" : "00ef6edf62a4497c1bd7b147fb6c3d22af3c39bfce95f30e13a16d3d7b2812f813" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045a7c8825e85691cce1f5e7544c54e73f14afc010cb731343262ca7ec5a77f5bfef6edf62a4497c1bd7b147fb6c3d22af3c39bfce95f30e13a16d3d7b2812f813", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWnyIJehWkczh9edUTFTnPxSvwBDL\ncxNDJiyn7Fp39b/vbt9ipEl8G9exR/tsPSKvPDm/zpXzDhOhbT17KBL4Ew==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 122, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000005", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 123, + "comment" : "incorrect size of signature", + "msg" : "313233343030", + "sig" : "0505", + "result" : "acceptable", + "flags" : [ + "SigSize" + ] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "y-DCkTLNc4Nk_t1gMVKZDASOXi__mW2IP6bKynl4xzc", + "y" : "cK9qjORMtBIksmA2BvTATRiOgL_3zDGtUYnUqw1w6ME" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04cbe0c29132cd738364fedd603152990c048e5e2fff996d883fa6caca7978c73770af6a8ce44cb41224b2603606f4c04d188e80bff7cc31ad5189d4ab0d70e8c1", + "wx" : "00cbe0c29132cd738364fedd603152990c048e5e2fff996d883fa6caca7978c737", + "wy" : "70af6a8ce44cb41224b2603606f4c04d188e80bff7cc31ad5189d4ab0d70e8c1" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004cbe0c29132cd738364fedd603152990c048e5e2fff996d883fa6caca7978c73770af6a8ce44cb41224b2603606f4c04d188e80bff7cc31ad5189d4ab0d70e8c1", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy+DCkTLNc4Nk/t1gMVKZDASOXi//\nmW2IP6bKynl4xzdwr2qM5Ey0EiSyYDYG9MBNGI6Av/fMMa1RidSrDXDowQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 124, + "comment" : "small r and s", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 125, + "comment" : "incorrect size of signature", + "msg" : "313233343030", + "sig" : "0506", + "result" : "acceptable", + "flags" : [ + "SigSize" + ] + }, + { + "tcId" : 126, + "comment" : "r is larger than n", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325560000000000000000000000000000000000000000000000000000000000000006", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "S-QXgJcALw3qto8NmhMODtM6Z5XQKiB5bbg0RLA34Tk", + "y" : "IPEwUeDuzc_OTazqD1DR8kfKpmnxk8G0B1tRriltLVY" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044be4178097002f0deab68f0d9a130e0ed33a6795d02a20796db83444b037e13920f13051e0eecdcfce4dacea0f50d1f247caa669f193c1b4075b51ae296d2d56", + "wx" : "4be4178097002f0deab68f0d9a130e0ed33a6795d02a20796db83444b037e139", + "wy" : "20f13051e0eecdcfce4dacea0f50d1f247caa669f193c1b4075b51ae296d2d56" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044be4178097002f0deab68f0d9a130e0ed33a6795d02a20796db83444b037e13920f13051e0eecdcfce4dacea0f50d1f247caa669f193c1b4075b51ae296d2d56", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES+QXgJcALw3qto8NmhMODtM6Z5XQ\nKiB5bbg0RLA34Tkg8TBR4O7Nz85NrOoPUNHyR8qmafGTwbQHW1GuKW0tVg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 127, + "comment" : "s is larger than n", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000005ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc75fbd8", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "0Pc3kiA3Fq_UvkMp-qSNJp8VMT67ujedd4PJe_PokNk", + "y" : "lx9KMgZgW-wheCv14nXHFEF-j1ZlSea8aGkNI2PInME" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d0f73792203716afd4be4329faa48d269f15313ebbba379d7783c97bf3e890d9971f4a3206605bec21782bf5e275c714417e8f566549e6bc68690d2363c89cc1", + "wx" : "00d0f73792203716afd4be4329faa48d269f15313ebbba379d7783c97bf3e890d9", + "wy" : "00971f4a3206605bec21782bf5e275c714417e8f566549e6bc68690d2363c89cc1" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d0f73792203716afd4be4329faa48d269f15313ebbba379d7783c97bf3e890d9971f4a3206605bec21782bf5e275c714417e8f566549e6bc68690d2363c89cc1", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0Pc3kiA3Fq/UvkMp+qSNJp8VMT67\nujedd4PJe/PokNmXH0oyBmBb7CF4K/XidccUQX6PVmVJ5rxoaQ0jY8icwQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 128, + "comment" : "small r and s^-1", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000000000000000000000000001008f1e3c7862c58b16bb76eddbb76eddbb516af4f63f2d74d76e0d28c9bb75ea88", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "SDiyvjWmJ2qA754igUD52bls6Dt6JU9xzN67uAVM4F8", + "y" : "-py8EjyRmxngAjgZjQQGkEO9ZgqCiBQFH8uKrHOKbGs" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044838b2be35a6276a80ef9e228140f9d9b96ce83b7a254f71ccdebbb8054ce05ffa9cbc123c919b19e00238198d04069043bd660a828814051fcb8aac738a6c6b", + "wx" : "4838b2be35a6276a80ef9e228140f9d9b96ce83b7a254f71ccdebbb8054ce05f", + "wy" : "00fa9cbc123c919b19e00238198d04069043bd660a828814051fcb8aac738a6c6b" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044838b2be35a6276a80ef9e228140f9d9b96ce83b7a254f71ccdebbb8054ce05ffa9cbc123c919b19e00238198d04069043bd660a828814051fcb8aac738a6c6b", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESDiyvjWmJ2qA754igUD52bls6Dt6\nJU9xzN67uAVM4F/6nLwSPJGbGeACOBmNBAaQQ71mCoKIFAUfy4qsc4psaw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 129, + "comment" : "smallish r and s^-1", + "msg" : "313233343030", + "sig" : "000000000000000000000000000000000000000000000000002d9b4d347952d6ef3043e7329581dbb3974497710ab11505ee1c87ff907beebadd195a0ffe6d7a", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "c5OYPKMKUgu8R4PcmWB0aqtETvUgwKjncRGapOdLD2Q", + "y" : "6de-GrAaC_Ym5wmGPmpIbbrzJ5OvzPd04sbNJ7GFdSY" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "047393983ca30a520bbc4783dc9960746aab444ef520c0a8e771119aa4e74b0f64e9d7be1ab01a0bf626e709863e6a486dbaf32793afccf774e2c6cd27b1857526", + "wx" : "7393983ca30a520bbc4783dc9960746aab444ef520c0a8e771119aa4e74b0f64", + "wy" : "00e9d7be1ab01a0bf626e709863e6a486dbaf32793afccf774e2c6cd27b1857526" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200047393983ca30a520bbc4783dc9960746aab444ef520c0a8e771119aa4e74b0f64e9d7be1ab01a0bf626e709863e6a486dbaf32793afccf774e2c6cd27b1857526", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc5OYPKMKUgu8R4PcmWB0aqtETvUg\nwKjncRGapOdLD2Tp174asBoL9ibnCYY+akhtuvMnk6/M93Tixs0nsYV1Jg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 130, + "comment" : "100-bit r and small s^-1", + "msg" : "313233343030", + "sig" : "000000000000000000000000000000000000001033e67e37b32b445580bf4eff8b748b74000000008b748b748b748b7466e769ad4a16d3dcd87129b8e91d1b4d", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "WsMxoRA_6WZpc3nzVqk381BYigVHfjCIUbilAtXfzcU", + "y" : "_pmT30tXk5srjaCVv215QmUgTP4DvplaAuZdQIyHHAs" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045ac331a1103fe966697379f356a937f350588a05477e308851b8a502d5dfcdc5fe9993df4b57939b2b8da095bf6d794265204cfe03be995a02e65d408c871c0b", + "wx" : "5ac331a1103fe966697379f356a937f350588a05477e308851b8a502d5dfcdc5", + "wy" : "00fe9993df4b57939b2b8da095bf6d794265204cfe03be995a02e65d408c871c0b" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045ac331a1103fe966697379f356a937f350588a05477e308851b8a502d5dfcdc5fe9993df4b57939b2b8da095bf6d794265204cfe03be995a02e65d408c871c0b", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWsMxoRA/6WZpc3nzVqk381BYigVH\nfjCIUbilAtXfzcX+mZPfS1eTmyuNoJW/bXlCZSBM/gO+mVoC5l1AjIccCw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 131, + "comment" : "small r and 100 bit s^-1", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000100ef9f6ba4d97c09d03178fa20b4aaad83be3cf9cb824a879fec3270fc4b81ef5b", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "HSCb6N4t6HcJWjmdOQTHTMRY2Sbie7jljl6uV2fEFQk", + "y" : "3VngTCFPexjc41H8KlSYk6aGDoAWPzjMYKTyydBA2Mk" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "041d209be8de2de877095a399d3904c74cc458d926e27bb8e58e5eae5767c41509dd59e04c214f7b18dce351fc2a549893a6860e80163f38cc60a4f2c9d040d8c9", + "wx" : "1d209be8de2de877095a399d3904c74cc458d926e27bb8e58e5eae5767c41509", + "wy" : "00dd59e04c214f7b18dce351fc2a549893a6860e80163f38cc60a4f2c9d040d8c9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200041d209be8de2de877095a399d3904c74cc458d926e27bb8e58e5eae5767c41509dd59e04c214f7b18dce351fc2a549893a6860e80163f38cc60a4f2c9d040d8c9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHSCb6N4t6HcJWjmdOQTHTMRY2Sbi\ne7jljl6uV2fEFQndWeBMIU97GNzjUfwqVJiTpoYOgBY/OMxgpPLJ0EDYyQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 132, + "comment" : "100-bit r and s^-1", + "msg" : "313233343030", + "sig" : "00000000000000000000000000000000000000062522bbd3ecbe7c39e93e7c25ef9f6ba4d97c09d03178fa20b4aaad83be3cf9cb824a879fec3270fc4b81ef5b", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "CDU5--5EYl46yq-i_LQTSTks7wYzobj6vs7gwTOxDpk", + "y" : "kVwevnvwDfhTUZZ3ClgEeuKkAvJjJrt9QdTXYWM3kR4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04083539fbee44625e3acaafa2fcb41349392cef0633a1b8fabecee0c133b10e99915c1ebe7bf00df8535196770a58047ae2a402f26326bb7d41d4d7616337911e", + "wx" : "083539fbee44625e3acaafa2fcb41349392cef0633a1b8fabecee0c133b10e99", + "wy" : "00915c1ebe7bf00df8535196770a58047ae2a402f26326bb7d41d4d7616337911e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004083539fbee44625e3acaafa2fcb41349392cef0633a1b8fabecee0c133b10e99915c1ebe7bf00df8535196770a58047ae2a402f26326bb7d41d4d7616337911e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECDU5++5EYl46yq+i/LQTSTks7wYz\nobj6vs7gwTOxDpmRXB6+e/AN+FNRlncKWAR64qQC8mMmu31B1NdhYzeRHg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 133, + "comment" : "r and s^-1 are close to n", + "msg" : "313233343030", + "sig" : "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6324d5555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "ius2inAnpNZKveo3OQwMHWom85ni2XNN4es9Dhk3OHQ", + "y" : "Bb0Tg0cV4duum4dc8HvVXhtmkcf3U2rvOxm_ekrfV20" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "048aeb368a7027a4d64abdea37390c0c1d6a26f399e2d9734de1eb3d0e1937387405bd13834715e1dbae9b875cf07bd55e1b6691c7f7536aef3b19bf7a4adf576d", + "wx" : "008aeb368a7027a4d64abdea37390c0c1d6a26f399e2d9734de1eb3d0e19373874", + "wy" : "05bd13834715e1dbae9b875cf07bd55e1b6691c7f7536aef3b19bf7a4adf576d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200048aeb368a7027a4d64abdea37390c0c1d6a26f399e2d9734de1eb3d0e1937387405bd13834715e1dbae9b875cf07bd55e1b6691c7f7536aef3b19bf7a4adf576d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEius2inAnpNZKveo3OQwMHWom85ni\n2XNN4es9Dhk3OHQFvRODRxXh266bh1zwe9VeG2aRx/dTau87Gb96St9XbQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 134, + "comment" : "s == 1", + "msg" : "313233343030", + "sig" : "555555550000000055555555555555553ef7a8e48d07df81a693439654210c700000000000000000000000000000000000000000000000000000000000000001", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 135, + "comment" : "s == 0", + "msg" : "313233343030", + "sig" : "555555550000000055555555555555553ef7a8e48d07df81a693439654210c700000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "tTPUaV3VuMXgd1flXm5Rb34siPoCOeI_YOjsB91w8oc", + "y" : "GxNO5YzFgyeEVoY_M8OoXYgffUo5hQFD4p1OrwCa_kc" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04b533d4695dd5b8c5e07757e55e6e516f7e2c88fa0239e23f60e8ec07dd70f2871b134ee58cc583278456863f33c3a85d881f7d4a39850143e29d4eaf009afe47", + "wx" : "00b533d4695dd5b8c5e07757e55e6e516f7e2c88fa0239e23f60e8ec07dd70f287", + "wy" : "1b134ee58cc583278456863f33c3a85d881f7d4a39850143e29d4eaf009afe47" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004b533d4695dd5b8c5e07757e55e6e516f7e2c88fa0239e23f60e8ec07dd70f2871b134ee58cc583278456863f33c3a85d881f7d4a39850143e29d4eaf009afe47", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtTPUaV3VuMXgd1flXm5Rb34siPoC\nOeI/YOjsB91w8ocbE07ljMWDJ4RWhj8zw6hdiB99SjmFAUPinU6vAJr+Rw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 136, + "comment" : "point at infinity during verify", + "msg" : "313233343030", + "sig" : "7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "9Q03G5G_sdfRThMjUjvDqoy_LFf54oTeYoyLRTZ4e4Y", + "y" : "-UrYh6yU1SckfNLn0MixKRxVPJcwQFOAsUy7IJ9fot0" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f50d371b91bfb1d7d14e1323523bc3aa8cbf2c57f9e284de628c8b4536787b86f94ad887ac94d527247cd2e7d0c8b1291c553c9730405380b14cbb209f5fa2dd", + "wx" : "00f50d371b91bfb1d7d14e1323523bc3aa8cbf2c57f9e284de628c8b4536787b86", + "wy" : "00f94ad887ac94d527247cd2e7d0c8b1291c553c9730405380b14cbb209f5fa2dd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f50d371b91bfb1d7d14e1323523bc3aa8cbf2c57f9e284de628c8b4536787b86f94ad887ac94d527247cd2e7d0c8b1291c553c9730405380b14cbb209f5fa2dd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9Q03G5G/sdfRThMjUjvDqoy/LFf5\n4oTeYoyLRTZ4e4b5StiHrJTVJyR80ufQyLEpHFU8lzBAU4CxTLsgn1+i3Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 137, + "comment" : "edge case for signature malleability", + "msg" : "313233343030", + "sig" : "7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a97fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "aOxuKY6v4WU5FWzlehSwSnBHwiG6_DpYLq6w2FfE2UY", + "y" : "l77RrxeFARf9s5sjJPIgpWmO0WxCaiczW7OFrIym-zA" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0468ec6e298eafe16539156ce57a14b04a7047c221bafc3a582eaeb0d857c4d94697bed1af17850117fdb39b2324f220a5698ed16c426a27335bb385ac8ca6fb30", + "wx" : "68ec6e298eafe16539156ce57a14b04a7047c221bafc3a582eaeb0d857c4d946", + "wy" : "0097bed1af17850117fdb39b2324f220a5698ed16c426a27335bb385ac8ca6fb30" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000468ec6e298eafe16539156ce57a14b04a7047c221bafc3a582eaeb0d857c4d94697bed1af17850117fdb39b2324f220a5698ed16c426a27335bb385ac8ca6fb30", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaOxuKY6v4WU5FWzlehSwSnBHwiG6\n/DpYLq6w2FfE2UaXvtGvF4UBF/2zmyMk8iClaY7RbEJqJzNbs4WsjKb7MA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 138, + "comment" : "edge case for signature malleability", + "msg" : "313233343030", + "sig" : "7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a97fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a9", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "adoDZHNNLlMP7OlAGSZf77eBoPGwj2yIl732VXknyLg", + "y" : "ZtLTx9zVGLI9cmlg8Gmtcakz2G74q7zOiyD3HiqEcAI" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0469da0364734d2e530fece94019265fefb781a0f1b08f6c8897bdf6557927c8b866d2d3c7dcd518b23d726960f069ad71a933d86ef8abbcce8b20f71e2a847002", + "wx" : "69da0364734d2e530fece94019265fefb781a0f1b08f6c8897bdf6557927c8b8", + "wy" : "66d2d3c7dcd518b23d726960f069ad71a933d86ef8abbcce8b20f71e2a847002" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000469da0364734d2e530fece94019265fefb781a0f1b08f6c8897bdf6557927c8b866d2d3c7dcd518b23d726960f069ad71a933d86ef8abbcce8b20f71e2a847002", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEadoDZHNNLlMP7OlAGSZf77eBoPGw\nj2yIl732VXknyLhm0tPH3NUYsj1yaWDwaa1xqTPYbvirvM6LIPceKoRwAg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 139, + "comment" : "u1 == 1", + "msg" : "313233343030", + "sig" : "555555550000000055555555555555553ef7a8e48d07df81a693439654210c70bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "2K3AACOo7cAlduK2Pj4wYhpHHisjIGIBh78GehrB_zI", + "y" : "M-K1DsCYB6zLNhMf_5XtEqCahrTqlpCqMoYVdrojYuE" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d8adc00023a8edc02576e2b63e3e30621a471e2b2320620187bf067a1ac1ff3233e2b50ec09807accb36131fff95ed12a09a86b4ea9690aa32861576ba2362e1", + "wx" : "00d8adc00023a8edc02576e2b63e3e30621a471e2b2320620187bf067a1ac1ff32", + "wy" : "33e2b50ec09807accb36131fff95ed12a09a86b4ea9690aa32861576ba2362e1" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d8adc00023a8edc02576e2b63e3e30621a471e2b2320620187bf067a1ac1ff3233e2b50ec09807accb36131fff95ed12a09a86b4ea9690aa32861576ba2362e1", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2K3AACOo7cAlduK2Pj4wYhpHHisj\nIGIBh78GehrB/zIz4rUOwJgHrMs2Ex//le0SoJqGtOqWkKoyhhV2uiNi4Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 140, + "comment" : "u1 == n - 1", + "msg" : "313233343030", + "sig" : "555555550000000055555555555555553ef7a8e48d07df81a693439654210c7044a5ad0ad0636d9f12bc9e0a6bdd5e1cbcb012ea7bf091fcec15b0c43202d52e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "NiOslzztClb6bYgvA6fVx-3KAs_HskAfqzaQ2-dat4U", + "y" : "jbBpCOZLKGE9pyV-c385eT2o5xO6BkO5LpuzJSvn-P4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043623ac973ced0a56fa6d882f03a7d5c7edca02cfc7b2401fab3690dbe75ab7858db06908e64b28613da7257e737f39793da8e713ba0643b92e9bb3252be7f8fe", + "wx" : "3623ac973ced0a56fa6d882f03a7d5c7edca02cfc7b2401fab3690dbe75ab785", + "wy" : "008db06908e64b28613da7257e737f39793da8e713ba0643b92e9bb3252be7f8fe" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043623ac973ced0a56fa6d882f03a7d5c7edca02cfc7b2401fab3690dbe75ab7858db06908e64b28613da7257e737f39793da8e713ba0643b92e9bb3252be7f8fe", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENiOslzztClb6bYgvA6fVx+3KAs/H\nskAfqzaQ2+dat4WNsGkI5ksoYT2nJX5zfzl5PajnE7oGQ7kum7MlK+f4/g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 141, + "comment" : "u2 == 1", + "msg" : "313233343030", + "sig" : "555555550000000055555555555555553ef7a8e48d07df81a693439654210c70555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "zwTqd-liJSPYlLk_9S3DAnsxlZUDtvo4kOXgQmP5IvE", + "y" : "6FKPt8AGs5g8i4QA5XtO1xdAwvOXVDiCEZm-3q7Ksuk" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04cf04ea77e9622523d894b93ff52dc3027b31959503b6fa3890e5e04263f922f1e8528fb7c006b3983c8b8400e57b4ed71740c2f3975438821199bedeaecab2e9", + "wx" : "00cf04ea77e9622523d894b93ff52dc3027b31959503b6fa3890e5e04263f922f1", + "wy" : "00e8528fb7c006b3983c8b8400e57b4ed71740c2f3975438821199bedeaecab2e9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004cf04ea77e9622523d894b93ff52dc3027b31959503b6fa3890e5e04263f922f1e8528fb7c006b3983c8b8400e57b4ed71740c2f3975438821199bedeaecab2e9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzwTqd+liJSPYlLk/9S3DAnsxlZUD\ntvo4kOXgQmP5IvHoUo+3wAazmDyLhADle07XF0DC85dUOIIRmb7ersqy6Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 142, + "comment" : "u2 == n - 1", + "msg" : "313233343030", + "sig" : "555555550000000055555555555555553ef7a8e48d07df81a693439654210c70aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "23osihq1c-WSncJAd7UI1-aD1JInmWvaPp942-_3c1A", + "y" : "T0F_O8mogHXC4KrdWhMxFzDPfMdqgvEaNurwimyZogY" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04db7a2c8a1ab573e5929dc24077b508d7e683d49227996bda3e9f78dbeff773504f417f3bc9a88075c2e0aadd5a13311730cf7cc76a82f11a36eaf08a6c99a206", + "wx" : "00db7a2c8a1ab573e5929dc24077b508d7e683d49227996bda3e9f78dbeff77350", + "wy" : "4f417f3bc9a88075c2e0aadd5a13311730cf7cc76a82f11a36eaf08a6c99a206" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004db7a2c8a1ab573e5929dc24077b508d7e683d49227996bda3e9f78dbeff773504f417f3bc9a88075c2e0aadd5a13311730cf7cc76a82f11a36eaf08a6c99a206", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE23osihq1c+WSncJAd7UI1+aD1JIn\nmWvaPp942+/3c1BPQX87yaiAdcLgqt1aEzEXMM98x2qC8Ro26vCKbJmiBg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 143, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffde91e1ba60fdedb76a46bcb51dc0b8b4b7e019f0a28721885fa5d3a8196623397", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "3q0Rx6WzloYvIZdNxHUvre_5lO_pu9BatBN2XqgLbh8", + "y" : "HePwZA6Kxu3Pic_1PEDiZbuUB4o0NzbfB6oDGPx_4f8" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04dead11c7a5b396862f21974dc4752fadeff994efe9bbd05ab413765ea80b6e1f1de3f0640e8ac6edcf89cff53c40e265bb94078a343736df07aa0318fc7fe1ff", + "wx" : "00dead11c7a5b396862f21974dc4752fadeff994efe9bbd05ab413765ea80b6e1f", + "wy" : "1de3f0640e8ac6edcf89cff53c40e265bb94078a343736df07aa0318fc7fe1ff" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004dead11c7a5b396862f21974dc4752fadeff994efe9bbd05ab413765ea80b6e1f1de3f0640e8ac6edcf89cff53c40e265bb94078a343736df07aa0318fc7fe1ff", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3q0Rx6WzloYvIZdNxHUvre/5lO/p\nu9BatBN2XqgLbh8d4/BkDorG7c+Jz/U8QOJlu5QHijQ3Nt8HqgMY/H/h/w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 144, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdea5843ffeb73af94313ba4831b53fe24f799e525b1e8e8c87b59b95b430ad9", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "0LxHLg18geuu06bvlsGGE7sf6m-ZQyb76A4A395nx-k", + "y" : "mGxyPqSEPUg4m5RvZK1WyDrXD_F7qFM1Zn0bufphnv0" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d0bc472e0d7c81ebaed3a6ef96c18613bb1fea6f994326fbe80e00dfde67c7e9986c723ea4843d48389b946f64ad56c83ad70ff17ba85335667d1bb9fa619efd", + "wx" : "00d0bc472e0d7c81ebaed3a6ef96c18613bb1fea6f994326fbe80e00dfde67c7e9", + "wy" : "00986c723ea4843d48389b946f64ad56c83ad70ff17ba85335667d1bb9fa619efd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d0bc472e0d7c81ebaed3a6ef96c18613bb1fea6f994326fbe80e00dfde67c7e9986c723ea4843d48389b946f64ad56c83ad70ff17ba85335667d1bb9fa619efd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0LxHLg18geuu06bvlsGGE7sf6m+Z\nQyb76A4A395nx+mYbHI+pIQ9SDiblG9krVbIOtcP8XuoUzVmfRu5+mGe/Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 145, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd03ffcabf2f1b4d2a65190db1680d62bb994e41c5251cd73b3c3dfc5e5bafc035", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "oKRMqUfWairLc2AIucCNGrKtA3duAmQPeEldRY3VHDI", + "y" : "Yzf-XPjEYEsfHECdwthy1ClKR2JCDfQ6MKI5LkBCat0" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a0a44ca947d66a2acb736008b9c08d1ab2ad03776e02640f78495d458dd51c326337fe5cf8c4604b1f1c409dc2d872d4294a4762420df43a30a2392e40426add", + "wx" : "00a0a44ca947d66a2acb736008b9c08d1ab2ad03776e02640f78495d458dd51c32", + "wy" : "6337fe5cf8c4604b1f1c409dc2d872d4294a4762420df43a30a2392e40426add" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a0a44ca947d66a2acb736008b9c08d1ab2ad03776e02640f78495d458dd51c326337fe5cf8c4604b1f1c409dc2d872d4294a4762420df43a30a2392e40426add", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoKRMqUfWairLc2AIucCNGrKtA3du\nAmQPeEldRY3VHDJjN/5c+MRgSx8cQJ3C2HLUKUpHYkIN9DowojkuQEJq3Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 146, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd4dfbc401f971cd304b33dfdb17d0fed0fe4c1a88ae648e0d2847f74977534989", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "ycIRUpDQCLRftl-tD2AjiSmMJUILd1AZ1Ctiw86Klrc", + "y" : "OHfSWoCA3ALZh8pzDwQFwsnb76xG-eYBzD8G6XE5c_0" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04c9c2115290d008b45fb65fad0f602389298c25420b775019d42b62c3ce8a96b73877d25a8080dc02d987ca730f0405c2c9dbefac46f9e601cc3f06e9713973fd", + "wx" : "00c9c2115290d008b45fb65fad0f602389298c25420b775019d42b62c3ce8a96b7", + "wy" : "3877d25a8080dc02d987ca730f0405c2c9dbefac46f9e601cc3f06e9713973fd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004c9c2115290d008b45fb65fad0f602389298c25420b775019d42b62c3ce8a96b73877d25a8080dc02d987ca730f0405c2c9dbefac46f9e601cc3f06e9713973fd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEycIRUpDQCLRftl+tD2AjiSmMJUIL\nd1AZ1Ctiw86Klrc4d9JagIDcAtmHynMPBAXCydvvrEb55gHMPwbpcTlz/Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 147, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbc4024761cd2ffd43dfdb17d0fed112b988977055cd3a8e54971eba9cda5ca71", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "Xsoe9MKH3dxmuLzPG4jookwAGJYvPF5--oO8Gl_2Az4", + "y" : "XnnEyywkW4xFq9zoqOTadY2SpgfDLNQH7K7yLxyTSnE" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045eca1ef4c287dddc66b8bccf1b88e8a24c0018962f3c5e7efa83bc1a5ff6033e5e79c4cb2c245b8c45abdce8a8e4da758d92a607c32cd407ecaef22f1c934a71", + "wx" : "5eca1ef4c287dddc66b8bccf1b88e8a24c0018962f3c5e7efa83bc1a5ff6033e", + "wy" : "5e79c4cb2c245b8c45abdce8a8e4da758d92a607c32cd407ecaef22f1c934a71" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045eca1ef4c287dddc66b8bccf1b88e8a24c0018962f3c5e7efa83bc1a5ff6033e5e79c4cb2c245b8c45abdce8a8e4da758d92a607c32cd407ecaef22f1c934a71", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXsoe9MKH3dxmuLzPG4jookwAGJYv\nPF5++oO8Gl/2Az5eecTLLCRbjEWr3Oio5Np1jZKmB8Ms1AfsrvIvHJNKcQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 148, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd788048ed39a5ffa77bfb62fa1fda2257742bf35d128fb3459f2a0c909ee86f91", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "XKqgMOf98OSTa8erWpY1PgoB5BMMP4vyLUc-MXAppHo", + "y" : "3ratxGL3BY8qINNx6XAiVOmyAWQgBbPO2pJrQrF4vvk" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045caaa030e7fdf0e4936bc7ab5a96353e0a01e4130c3f8bf22d473e317029a47adeb6adc462f7058f2a20d371e9702254e9b201642005b3ceda926b42b178bef9", + "wx" : "5caaa030e7fdf0e4936bc7ab5a96353e0a01e4130c3f8bf22d473e317029a47a", + "wy" : "00deb6adc462f7058f2a20d371e9702254e9b201642005b3ceda926b42b178bef9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045caaa030e7fdf0e4936bc7ab5a96353e0a01e4130c3f8bf22d473e317029a47adeb6adc462f7058f2a20d371e9702254e9b201642005b3ceda926b42b178bef9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXKqgMOf98OSTa8erWpY1PgoB5BMM\nP4vyLUc+MXAppHretq3EYvcFjyog03HpcCJU6bIBZCAFs87akmtCsXi++Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 149, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd476d9131fd381bd917d0fed112bc9e0a5924b5ed5b11167edd8b23582b3cb15e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "wv0gusBuVVu4rAzmnrHqIPg6H8NQHIpmRpsaMfYZsJg", + "y" : "YjcFB3n1K2Fb17jXaiX8lcou0yUlx18n_8h6w5fmy68" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04c2fd20bac06e555bb8ac0ce69eb1ea20f83a1fc3501c8a66469b1a31f619b0986237050779f52b615bd7b8d76a25fc95ca2ed32525c75f27ffc87ac397e6cbaf", + "wx" : "00c2fd20bac06e555bb8ac0ce69eb1ea20f83a1fc3501c8a66469b1a31f619b098", + "wy" : "6237050779f52b615bd7b8d76a25fc95ca2ed32525c75f27ffc87ac397e6cbaf" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004c2fd20bac06e555bb8ac0ce69eb1ea20f83a1fc3501c8a66469b1a31f619b0986237050779f52b615bd7b8d76a25fc95ca2ed32525c75f27ffc87ac397e6cbaf", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwv0gusBuVVu4rAzmnrHqIPg6H8NQ\nHIpmRpsaMfYZsJhiNwUHefUrYVvXuNdqJfyVyi7TJSXHXyf/yHrDl+bLrw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 150, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd8374253e3e21bd154448d0a8f640fe46fafa8b19ce78d538f6cc0a19662d3601", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "P9ahyn93-zsLvnJsNyAQBoQm4R6mrnjOF77a5LuobO0", + "y" : "A85VFkBr-M-quHRerBzWkBitb1C1Rhhy3fxW4Ns8j_Q" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043fd6a1ca7f77fb3b0bbe726c372010068426e11ea6ae78ce17bedae4bba86ced03ce5516406bf8cfaab8745eac1cd69018ad6f50b5461872ddfc56e0db3c8ff4", + "wx" : "3fd6a1ca7f77fb3b0bbe726c372010068426e11ea6ae78ce17bedae4bba86ced", + "wy" : "03ce5516406bf8cfaab8745eac1cd69018ad6f50b5461872ddfc56e0db3c8ff4" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043fd6a1ca7f77fb3b0bbe726c372010068426e11ea6ae78ce17bedae4bba86ced03ce5516406bf8cfaab8745eac1cd69018ad6f50b5461872ddfc56e0db3c8ff4", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEP9ahyn93+zsLvnJsNyAQBoQm4R6m\nrnjOF77a5LuobO0DzlUWQGv4z6q4dF6sHNaQGK1vULVGGHLd/Fbg2zyP9A==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 151, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd357cfd3be4d01d413c5b9ede36cba5452c11ee7fe14879e749ae6a2d897a52d6", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "nLjlHielrjtiSmDW3DJzTkmJ2yDpvKPt4e33sIaRERQ", + "y" : "tMEEqzxnfks21lVuitX1I0EKGfLid6qJX8VzIrRCdUQ" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "049cb8e51e27a5ae3b624a60d6dc32734e4989db20e9bca3ede1edf7b086911114b4c104ab3c677e4b36d6556e8ad5f523410a19f2e277aa895fc57322b4427544", + "wx" : "009cb8e51e27a5ae3b624a60d6dc32734e4989db20e9bca3ede1edf7b086911114", + "wy" : "00b4c104ab3c677e4b36d6556e8ad5f523410a19f2e277aa895fc57322b4427544" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200049cb8e51e27a5ae3b624a60d6dc32734e4989db20e9bca3ede1edf7b086911114b4c104ab3c677e4b36d6556e8ad5f523410a19f2e277aa895fc57322b4427544", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnLjlHielrjtiSmDW3DJzTkmJ2yDp\nvKPt4e33sIaRERS0wQSrPGd+SzbWVW6K1fUjQQoZ8uJ3qolfxXMitEJ1RA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 152, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd29798c5c0ee287d4a5e8e6b799fd86b8df5225298e6ffc807cd2f2bc27a0a6d8", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "o-UsFW3K8QUCYgt5VbwrQLx47z1WnhIjwmJRLY9JYCo", + "y" : "SiA58xwQlwJK08yG5XMh3gMjVUY0hhZM8ZKUSXffFH8" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a3e52c156dcaf10502620b7955bc2b40bc78ef3d569e1223c262512d8f49602a4a2039f31c1097024ad3cc86e57321de032355463486164cf192944977df147f", + "wx" : "00a3e52c156dcaf10502620b7955bc2b40bc78ef3d569e1223c262512d8f49602a", + "wy" : "4a2039f31c1097024ad3cc86e57321de032355463486164cf192944977df147f" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a3e52c156dcaf10502620b7955bc2b40bc78ef3d569e1223c262512d8f49602a4a2039f31c1097024ad3cc86e57321de032355463486164cf192944977df147f", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEo+UsFW3K8QUCYgt5VbwrQLx47z1W\nnhIjwmJRLY9JYCpKIDnzHBCXAkrTzIblcyHeAyNVRjSGFkzxkpRJd98Ufw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 153, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0b70f22c781092452dca1a5711fa3a5a1f72add1bf52c2ff7cae4820b30078dd", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "8Zt4kocg1b7o5nD7kAEPsVw3v5G1ilFXw_PAWbJlXog", + "y" : "z3AeyWL7ShHc8nP13DV-WEaFYMfP65QtB0q9QykmBQk" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f19b78928720d5bee8e670fb90010fb15c37bf91b58a5157c3f3c059b2655e88cf701ec962fb4a11dcf273f5dc357e58468560c7cfeb942d074abd4329260509", + "wx" : "00f19b78928720d5bee8e670fb90010fb15c37bf91b58a5157c3f3c059b2655e88", + "wy" : "00cf701ec962fb4a11dcf273f5dc357e58468560c7cfeb942d074abd4329260509" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f19b78928720d5bee8e670fb90010fb15c37bf91b58a5157c3f3c059b2655e88cf701ec962fb4a11dcf273f5dc357e58468560c7cfeb942d074abd4329260509", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8Zt4kocg1b7o5nD7kAEPsVw3v5G1\nilFXw/PAWbJlXojPcB7JYvtKEdzyc/XcNX5YRoVgx8/rlC0HSr1DKSYFCQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 154, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd16e1e458f021248a5b9434ae23f474b43ee55ba37ea585fef95c90416600f1ba", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "g6dERZ7N-wGlz1KyegW7czdILSQvI117TLiTRVRckKg", + "y" : "wF1JM3uWSYEyh96f_pA1X9kF3188MpRYKBIfN8xQ3m4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0483a744459ecdfb01a5cf52b27a05bb7337482d242f235d7b4cb89345545c90a8c05d49337b9649813287de9ffe90355fd905df5f3c32945828121f37cc50de6e", + "wx" : "0083a744459ecdfb01a5cf52b27a05bb7337482d242f235d7b4cb89345545c90a8", + "wy" : "00c05d49337b9649813287de9ffe90355fd905df5f3c32945828121f37cc50de6e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000483a744459ecdfb01a5cf52b27a05bb7337482d242f235d7b4cb89345545c90a8c05d49337b9649813287de9ffe90355fd905df5f3c32945828121f37cc50de6e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEg6dERZ7N+wGlz1KyegW7czdILSQv\nI117TLiTRVRckKjAXUkze5ZJgTKH3p/+kDVf2QXfXzwylFgoEh83zFDebg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 155, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd2252d6856831b6cf895e4f0535eeaf0e5e5809753df848fe760ad86219016a97", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "3RPGs0xWmC3a4STwOd_SP0sZu-iM7o5SiuUeXW86Idc", + "y" : "v61MLm8mP-XrWcqXTQOfwOTDNFaS-1Mgva5L07QqRf8" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04dd13c6b34c56982ddae124f039dfd23f4b19bbe88cee8e528ae51e5d6f3a21d7bfad4c2e6f263fe5eb59ca974d039fc0e4c3345692fb5320bdae4bd3b42a45ff", + "wx" : "00dd13c6b34c56982ddae124f039dfd23f4b19bbe88cee8e528ae51e5d6f3a21d7", + "wy" : "00bfad4c2e6f263fe5eb59ca974d039fc0e4c3345692fb5320bdae4bd3b42a45ff" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004dd13c6b34c56982ddae124f039dfd23f4b19bbe88cee8e528ae51e5d6f3a21d7bfad4c2e6f263fe5eb59ca974d039fc0e4c3345692fb5320bdae4bd3b42a45ff", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3RPGs0xWmC3a4STwOd/SP0sZu+iM\n7o5SiuUeXW86Ide/rUwubyY/5etZypdNA5/A5MM0VpL7UyC9rkvTtCpF/w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 156, + "comment" : "edge case for u1", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd81ffe55f178da695b28c86d8b406b15dab1a9e39661a3ae017fbe390ac0972c3", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "Z-b2Wc3ehpovZfCU6U5bTfrWNrv5UZL-7tAbDz3rdGA", + "y" : "o34KUfJYt661Hf5ZL1z9VoW75YcSyNkjPGKIZDfDi6A" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0467e6f659cdde869a2f65f094e94e5b4dfad636bbf95192feeed01b0f3deb7460a37e0a51f258b7aeb51dfe592f5cfd5685bbe58712c8d9233c62886437c38ba0", + "wx" : "67e6f659cdde869a2f65f094e94e5b4dfad636bbf95192feeed01b0f3deb7460", + "wy" : "00a37e0a51f258b7aeb51dfe592f5cfd5685bbe58712c8d9233c62886437c38ba0" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000467e6f659cdde869a2f65f094e94e5b4dfad636bbf95192feeed01b0f3deb7460a37e0a51f258b7aeb51dfe592f5cfd5685bbe58712c8d9233c62886437c38ba0", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZ+b2Wc3ehpovZfCU6U5bTfrWNrv5\nUZL+7tAbDz3rdGCjfgpR8li3rrUd/lkvXP1WhbvlhxLI2SM8YohkN8OLoA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 157, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd7fffffffaaaaaaaaffffffffffffffffe9a2538f37b28a2c513dee40fecbb71a", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "LrZBJQWuwFxlRfApkyCH5JDQVRHo7B9Zlhe7Nn-eyq8", + "y" : "gF9R78xIA0A_mxrgEkiQ8GpD_tzdsxgw9maa8pKJXLA" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042eb6412505aec05c6545f029932087e490d05511e8ec1f599617bb367f9ecaaf805f51efcc4803403f9b1ae0124890f06a43fedcddb31830f6669af292895cb0", + "wx" : "2eb6412505aec05c6545f029932087e490d05511e8ec1f599617bb367f9ecaaf", + "wy" : "00805f51efcc4803403f9b1ae0124890f06a43fedcddb31830f6669af292895cb0" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042eb6412505aec05c6545f029932087e490d05511e8ec1f599617bb367f9ecaaf805f51efcc4803403f9b1ae0124890f06a43fedcddb31830f6669af292895cb0", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrZBJQWuwFxlRfApkyCH5JDQVRHo\n7B9Zlhe7Nn+eyq+AX1HvzEgDQD+bGuASSJDwakP+3N2zGDD2ZprykolcsA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 158, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdb62f26b5f2a2b26f6de86d42ad8a13da3ab3cccd0459b201de009e526adf21f2", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "hNtkWGjqs146n9gOBW4uhVQ146a2jXWlCoVGJf4NfzU", + "y" : "bSWJrGVe3JoR7z4HXt3amr-S5yFxVw73v0Oi7jkzjP4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0484db645868eab35e3a9fd80e056e2e855435e3a6b68d75a50a854625fe0d7f356d2589ac655edc9a11ef3e075eddda9abf92e72171570ef7bf43a2ee39338cfe", + "wx" : "0084db645868eab35e3a9fd80e056e2e855435e3a6b68d75a50a854625fe0d7f35", + "wy" : "6d2589ac655edc9a11ef3e075eddda9abf92e72171570ef7bf43a2ee39338cfe" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000484db645868eab35e3a9fd80e056e2e855435e3a6b68d75a50a854625fe0d7f356d2589ac655edc9a11ef3e075eddda9abf92e72171570ef7bf43a2ee39338cfe", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhNtkWGjqs146n9gOBW4uhVQ146a2\njXWlCoVGJf4NfzVtJYmsZV7cmhHvPgde3dqav5LnIXFXDve/Q6LuOTOM/g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 159, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbb1d9ac949dd748cd02bbbe749bd351cd57b38bb61403d700686aa7b4c90851e", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "kbnkfFYnhmLXXAmDsiyo6mqlBZt6L_djfrKXXjhq1mM", + "y" : "SaqP8oPQ93wY1tEdwGIWX9E8PAMQZ5wUCDAqFoVOz70" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0491b9e47c56278662d75c0983b22ca8ea6aa5059b7a2ff7637eb2975e386ad66349aa8ff283d0f77c18d6d11dc062165fd13c3c0310679c1408302a16854ecfbd", + "wx" : "0091b9e47c56278662d75c0983b22ca8ea6aa5059b7a2ff7637eb2975e386ad663", + "wy" : "49aa8ff283d0f77c18d6d11dc062165fd13c3c0310679c1408302a16854ecfbd" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000491b9e47c56278662d75c0983b22ca8ea6aa5059b7a2ff7637eb2975e386ad66349aa8ff283d0f77c18d6d11dc062165fd13c3c0310679c1408302a16854ecfbd", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkbnkfFYnhmLXXAmDsiyo6mqlBZt6\nL/djfrKXXjhq1mNJqo/yg9D3fBjW0R3AYhZf0Tw8AxBnnBQIMCoWhU7PvQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 160, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd66755a00638cdaec1c732513ca0234ece52545dac11f816e818f725b4f60aaf2", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "8-wvE8rwTQGStH-0xTEfttTcawqegC5TJ_fsXujkg00", + "y" : "-X4-Rot9Dbhn1uz-geKw-VMd-H79tHwTOKwyH-_lpDI" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f3ec2f13caf04d0192b47fb4c5311fb6d4dc6b0a9e802e5327f7ec5ee8e4834df97e3e468b7d0db867d6ecfe81e2b0f9531df87efdb47c1338ac321fefe5a432", + "wx" : "00f3ec2f13caf04d0192b47fb4c5311fb6d4dc6b0a9e802e5327f7ec5ee8e4834d", + "wy" : "00f97e3e468b7d0db867d6ecfe81e2b0f9531df87efdb47c1338ac321fefe5a432" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f3ec2f13caf04d0192b47fb4c5311fb6d4dc6b0a9e802e5327f7ec5ee8e4834df97e3e468b7d0db867d6ecfe81e2b0f9531df87efdb47c1338ac321fefe5a432", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8+wvE8rwTQGStH+0xTEfttTcawqe\ngC5TJ/fsXujkg035fj5Gi30NuGfW7P6B4rD5Ux34fv20fBM4rDIf7+WkMg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 161, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd55a00c9fcdaebb6032513ca0234ecfffe98ebe492fdf02e48ca48e982beb3669", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "2SsgCu_Ktqx9r9msry-hCzGAI1uPRrRQPkaTxnD8zIg", + "y" : "XvLzrr9bMXR1M2JWdo98Ge-3NS0n5MzK3IW2uKuSLHI" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d92b200aefcab6ac7dafd9acaf2fa10b3180235b8f46b4503e4693c670fccc885ef2f3aebf5b317475336256768f7c19efb7352d27e4cccadc85b6b8ab922c72", + "wx" : "00d92b200aefcab6ac7dafd9acaf2fa10b3180235b8f46b4503e4693c670fccc88", + "wy" : "5ef2f3aebf5b317475336256768f7c19efb7352d27e4cccadc85b6b8ab922c72" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d92b200aefcab6ac7dafd9acaf2fa10b3180235b8f46b4503e4693c670fccc885ef2f3aebf5b317475336256768f7c19efb7352d27e4cccadc85b6b8ab922c72", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2SsgCu/Ktqx9r9msry+hCzGAI1uP\nRrRQPkaTxnD8zIhe8vOuv1sxdHUzYlZ2j3wZ77c1LSfkzMrchba4q5Iscg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 162, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdab40193f9b5d76c064a27940469d9fffd31d7c925fbe05c919491d3057d66cd2", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "Cog2HrkuzKJiWzjl-Yu6u5a_F5s9dvxIFAo7zYgVI80", + "y" : "5r31YDP4SlBUA1WXN12QhmqiyWuGpBzPbt6_RymK1Ik" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040a88361eb92ecca2625b38e5f98bbabb96bf179b3d76fc48140a3bcd881523cde6bdf56033f84a5054035597375d90866aa2c96b86a41ccf6edebf47298ad489", + "wx" : "0a88361eb92ecca2625b38e5f98bbabb96bf179b3d76fc48140a3bcd881523cd", + "wy" : "00e6bdf56033f84a5054035597375d90866aa2c96b86a41ccf6edebf47298ad489" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040a88361eb92ecca2625b38e5f98bbabb96bf179b3d76fc48140a3bcd881523cde6bdf56033f84a5054035597375d90866aa2c96b86a41ccf6edebf47298ad489", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECog2HrkuzKJiWzjl+Yu6u5a/F5s9\ndvxIFAo7zYgVI83mvfVgM/hKUFQDVZc3XZCGaqLJa4akHM9u3r9HKYrUiQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 163, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdca0234ebb5fdcb13ca0234ecffffffffcb0dadbbc7f549f8a26b4408d0dc8600", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "0PsXzNj6_oJ-DBr8XY2ANm4rIOfxSlY6K6UEadhDdeg", + "y" : "aGEladOeK7n1VDVVZGRt6ZrGAsxjSc-MHiNqfedjfZM" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d0fb17ccd8fafe827e0c1afc5d8d80366e2b20e7f14a563a2ba50469d84375e868612569d39e2bb9f554355564646de99ac602cc6349cf8c1e236a7de7637d93", + "wx" : "00d0fb17ccd8fafe827e0c1afc5d8d80366e2b20e7f14a563a2ba50469d84375e8", + "wy" : "68612569d39e2bb9f554355564646de99ac602cc6349cf8c1e236a7de7637d93" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d0fb17ccd8fafe827e0c1afc5d8d80366e2b20e7f14a563a2ba50469d84375e868612569d39e2bb9f554355564646de99ac602cc6349cf8c1e236a7de7637d93", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0PsXzNj6/oJ+DBr8XY2ANm4rIOfx\nSlY6K6UEadhDdehoYSVp054rufVUNVVkZG3pmsYCzGNJz4weI2p952N9kw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 164, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbfffffff3ea3677e082b9310572620ae19933a9e65b285598711c77298815ad3", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "g28zu8HcDT06u87w2R8R4qxBgQdsmvCiKx5DCdPtsnY", + "y" : "mrRD_2-QHjDHc4Z1gpl8K-wrDLgSDXYCNvOpW76IH3U" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04836f33bbc1dc0d3d3abbcef0d91f11e2ac4181076c9af0a22b1e4309d3edb2769ab443ff6f901e30c773867582997c2bec2b0cb8120d760236f3a95bbe881f75", + "wx" : "00836f33bbc1dc0d3d3abbcef0d91f11e2ac4181076c9af0a22b1e4309d3edb276", + "wy" : "009ab443ff6f901e30c773867582997c2bec2b0cb8120d760236f3a95bbe881f75" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004836f33bbc1dc0d3d3abbcef0d91f11e2ac4181076c9af0a22b1e4309d3edb2769ab443ff6f901e30c773867582997c2bec2b0cb8120d760236f3a95bbe881f75", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEg28zu8HcDT06u87w2R8R4qxBgQds\nmvCiKx5DCdPtsnaatEP/b5AeMMdzhnWCmXwr7CsMuBINdgI286lbvogfdQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 165, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd266666663bbbbbbbe6666666666666665b37902e023fab7c8f055d86e5cc41f4", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "kvmfvpc-1KKZcZuu5LQydBI3A03sjXK6UQPLM-Vf7rg", + "y" : "Az3Q6RE0xzQXSInz688behrAV2cokoDuenlM69bmlpc" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0492f99fbe973ed4a299719baee4b432741237034dec8d72ba5103cb33e55feeb8033dd0e91134c734174889f3ebcf1b7a1ac05767289280ee7a794cebd6e69697", + "wx" : "0092f99fbe973ed4a299719baee4b432741237034dec8d72ba5103cb33e55feeb8", + "wy" : "033dd0e91134c734174889f3ebcf1b7a1ac05767289280ee7a794cebd6e69697" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000492f99fbe973ed4a299719baee4b432741237034dec8d72ba5103cb33e55feeb8033dd0e91134c734174889f3ebcf1b7a1ac05767289280ee7a794cebd6e69697", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkvmfvpc+1KKZcZuu5LQydBI3A03s\njXK6UQPLM+Vf7rgDPdDpETTHNBdIifPrzxt6GsBXZyiSgO56eUzr1uaWlw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 166, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbfffffff36db6db7a492492492492492146c573f4c6dfc8d08a443e258970b09", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "01uljaMBl9N45hjsD6fi4tEs_9c-u7IEnRMLukNK8J4", + "y" : "_4OYbmh15B6kMrdYWkmzpsd8uzxHkZ-OgodMeUY1wdI" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04d35ba58da30197d378e618ec0fa7e2e2d12cffd73ebbb2049d130bba434af09eff83986e6875e41ea432b7585a49b3a6c77cbb3c47919f8e82874c794635c1d2", + "wx" : "00d35ba58da30197d378e618ec0fa7e2e2d12cffd73ebbb2049d130bba434af09e", + "wy" : "00ff83986e6875e41ea432b7585a49b3a6c77cbb3c47919f8e82874c794635c1d2" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004d35ba58da30197d378e618ec0fa7e2e2d12cffd73ebbb2049d130bba434af09eff83986e6875e41ea432b7585a49b3a6c77cbb3c47919f8e82874c794635c1d2", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01uljaMBl9N45hjsD6fi4tEs/9c+\nu7IEnRMLukNK8J7/g5huaHXkHqQyt1haSbOmx3y7PEeRn46Ch0x5RjXB0g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 167, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbfffffff2aaaaaab7fffffffffffffffc815d0e60b3e596ecb1ad3a27cfd49c4", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "hlHOSQ8bRtc_P_R1FJvikTZpczSlGdfdqwclyNB5MiQ", + "y" : "4RxlvYypLci8mugpEfC1J1HOId2QA65gkAvYJfWQzCg" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "048651ce490f1b46d73f3ff475149be29136697334a519d7ddab0725c8d0793224e11c65bd8ca92dc8bc9ae82911f0b52751ce21dd9003ae60900bd825f590cc28", + "wx" : "008651ce490f1b46d73f3ff475149be29136697334a519d7ddab0725c8d0793224", + "wy" : "00e11c65bd8ca92dc8bc9ae82911f0b52751ce21dd9003ae60900bd825f590cc28" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200048651ce490f1b46d73f3ff475149be29136697334a519d7ddab0725c8d0793224e11c65bd8ca92dc8bc9ae82911f0b52751ce21dd9003ae60900bd825f590cc28", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhlHOSQ8bRtc/P/R1FJvikTZpczSl\nGdfdqwclyNB5MiThHGW9jKktyLya6CkR8LUnUc4h3ZADrmCQC9gl9ZDMKA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 168, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd7fffffff55555555ffffffffffffffffd344a71e6f651458a27bdc81fd976e37", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "bY4bEsgxoNqHlWUP-V8QHtkh2eL3KxWxzaypgmuc_G0", + "y" : "721j4rxcCJVwOUpLyfiS1ebHpqY3sgRppYwQatSGvzc" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046d8e1b12c831a0da8795650ff95f101ed921d9e2f72b15b1cdaca9826b9cfc6def6d63e2bc5c089570394a4bc9f892d5e6c7a6a637b20469a58c106ad486bf37", + "wx" : "6d8e1b12c831a0da8795650ff95f101ed921d9e2f72b15b1cdaca9826b9cfc6d", + "wy" : "00ef6d63e2bc5c089570394a4bc9f892d5e6c7a6a637b20469a58c106ad486bf37" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046d8e1b12c831a0da8795650ff95f101ed921d9e2f72b15b1cdaca9826b9cfc6def6d63e2bc5c089570394a4bc9f892d5e6c7a6a637b20469a58c106ad486bf37", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbY4bEsgxoNqHlWUP+V8QHtkh2eL3\nKxWxzaypgmuc/G3vbWPivFwIlXA5SkvJ+JLV5sempjeyBGmljBBq1Ia/Nw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 169, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd3fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192aa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "CuWAuukztO8pl8vbsJIjKMqaQQ9ieg99_yTLTZIOFUI", + "y" : "iRHn-Mw2WoqI64FCGjYczCuZ4wnY3Nmpi6g8OUnYk-M" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040ae580bae933b4ef2997cbdbb0922328ca9a410f627a0f7dff24cb4d920e15428911e7f8cc365a8a88eb81421a361ccc2b99e309d8dcd9a98ba83c3949d893e3", + "wx" : "0ae580bae933b4ef2997cbdbb0922328ca9a410f627a0f7dff24cb4d920e1542", + "wy" : "008911e7f8cc365a8a88eb81421a361ccc2b99e309d8dcd9a98ba83c3949d893e3" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040ae580bae933b4ef2997cbdbb0922328ca9a410f627a0f7dff24cb4d920e15428911e7f8cc365a8a88eb81421a361ccc2b99e309d8dcd9a98ba83c3949d893e3", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECuWAuukztO8pl8vbsJIjKMqaQQ9i\neg99/yTLTZIOFUKJEef4zDZaiojrgUIaNhzMK5njCdjc2amLqDw5SdiT4w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 170, + "comment" : "edge case for u2", + "msg" : "313233343030", + "sig" : "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd5d8ecd64a4eeba466815ddf3a4de9a8e6abd9c5db0a01eb80343553da648428f", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "W4Ev1SGq-mmDWoSczm-962mDtELSRE_nDhNMAn_EaWM", + "y" : "g4pA8qNgkukATpLY2UDPVjhVDOZyzouNThXrpUmSSek" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963838a40f2a36092e9004e92d8d940cf5638550ce672ce8b8d4e15eba5499249e9", + "wx" : "5b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963", + "wy" : "00838a40f2a36092e9004e92d8d940cf5638550ce672ce8b8d4e15eba5499249e9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963838a40f2a36092e9004e92d8d940cf5638550ce672ce8b8d4e15eba5499249e9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW4Ev1SGq+mmDWoSczm+962mDtELS\nRE/nDhNMAn/EaWODikDyo2CS6QBOktjZQM9WOFUM5nLOi41OFeulSZJJ6Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 171, + "comment" : "point duplication during verification", + "msg" : "313233343030", + "sig" : "6f2347cab7dd76858fe0555ac3bc99048c4aacafdfb6bcbe05ea6c42c4934569bb726660235793aa9957a61e76e00c2c435109cf9a15dd624d53f4301047856b", + "result" : "valid", + "flags" : [ + "PointDuplication" + ] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "W4Ev1SGq-mmDWoSczm-962mDtELSRE_nDhNMAn_EaWM", + "y" : "fHW_DFyfbRf_sW0nJr8wqceq8xqNMXRyseoUWrZtthY" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc469637c75bf0c5c9f6d17ffb16d2726bf30a9c7aaf31a8d317472b1ea145ab66db616", + "wx" : "5b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc46963", + "wy" : "7c75bf0c5c9f6d17ffb16d2726bf30a9c7aaf31a8d317472b1ea145ab66db616" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045b812fd521aafa69835a849cce6fbdeb6983b442d2444fe70e134c027fc469637c75bf0c5c9f6d17ffb16d2726bf30a9c7aaf31a8d317472b1ea145ab66db616", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW4Ev1SGq+mmDWoSczm+962mDtELS\nRE/nDhNMAn/EaWN8db8MXJ9tF/+xbScmvzCpx6rzGo0xdHKx6hRatm22Fg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 172, + "comment" : "duplication bug", + "msg" : "313233343030", + "sig" : "6f2347cab7dd76858fe0555ac3bc99048c4aacafdfb6bcbe05ea6c42c4934569bb726660235793aa9957a61e76e00c2c435109cf9a15dd624d53f4301047856b", + "result" : "invalid", + "flags" : [ + "PointDuplication" + ] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "at2oK5AmGw8xn6oNh4ZlprbaSX8JyQMXYiLDSs_vcqY", + "y" : "R-b1DcxArV2bWfdgK7Ii-tcaQb9eH530lZo2TGLkiNk" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046adda82b90261b0f319faa0d878665a6b6da497f09c903176222c34acfef72a647e6f50dcc40ad5d9b59f7602bb222fad71a41bf5e1f9df4959a364c62e488d9", + "wx" : "6adda82b90261b0f319faa0d878665a6b6da497f09c903176222c34acfef72a6", + "wy" : "47e6f50dcc40ad5d9b59f7602bb222fad71a41bf5e1f9df4959a364c62e488d9" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046adda82b90261b0f319faa0d878665a6b6da497f09c903176222c34acfef72a647e6f50dcc40ad5d9b59f7602bb222fad71a41bf5e1f9df4959a364c62e488d9", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEat2oK5AmGw8xn6oNh4ZlprbaSX8J\nyQMXYiLDSs/vcqZH5vUNzECtXZtZ92ArsiL61xpBv14fnfSVmjZMYuSI2Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 173, + "comment" : "point with x-coordinate 0", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000001555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "L8oNCkeRTed-1W5-zMMnamARIMbfAGnIJcj2oByfOCA", + "y" : "ZfNFCh0XxrJJiaOb6xx97PyoOE-9wpRBjl2Aezxu194" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042fca0d0a47914de77ed56e7eccc3276a601120c6df0069c825c8f6a01c9f382065f3450a1d17c6b24989a39beb1c7decfca8384fbdc294418e5d807b3c6ed7de", + "wx" : "2fca0d0a47914de77ed56e7eccc3276a601120c6df0069c825c8f6a01c9f3820", + "wy" : "65f3450a1d17c6b24989a39beb1c7decfca8384fbdc294418e5d807b3c6ed7de" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042fca0d0a47914de77ed56e7eccc3276a601120c6df0069c825c8f6a01c9f382065f3450a1d17c6b24989a39beb1c7decfca8384fbdc294418e5d807b3c6ed7de", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL8oNCkeRTed+1W5+zMMnamARIMbf\nAGnIJcj2oByfOCBl80UKHRfGskmJo5vrHH3s/Kg4T73ClEGOXYB7PG7X3g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 174, + "comment" : "point with x-coordinate 0", + "msg" : "313233343030", + "sig" : "010000000000000000000000000000000000000000000000000000000000000000003333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aa9", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "3YbTtfShPoURCDt4ACCBxT_0Z_EevZilGmM9t2Zl0lA", + "y" : "RdXIIAyJ8voQ2Ek0kibSHY367W_41cs-G34XR068GPc" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04dd86d3b5f4a13e8511083b78002081c53ff467f11ebd98a51a633db76665d25045d5c8200c89f2fa10d849349226d21d8dfaed6ff8d5cb3e1b7e17474ebc18f7", + "wx" : "00dd86d3b5f4a13e8511083b78002081c53ff467f11ebd98a51a633db76665d250", + "wy" : "45d5c8200c89f2fa10d849349226d21d8dfaed6ff8d5cb3e1b7e17474ebc18f7" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004dd86d3b5f4a13e8511083b78002081c53ff467f11ebd98a51a633db76665d25045d5c8200c89f2fa10d849349226d21d8dfaed6ff8d5cb3e1b7e17474ebc18f7", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3YbTtfShPoURCDt4ACCBxT/0Z/Ee\nvZilGmM9t2Zl0lBF1cggDIny+hDYSTSSJtIdjfrtb/jVyz4bfhdHTrwY9w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 175, + "comment" : "comparison with point at infinity ", + "msg" : "313233343030", + "sig" : "555555550000000055555555555555553ef7a8e48d07df81a693439654210c703333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aa9", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "T-pVsyyzKsoMEsTNCr-05ksPWlFuV4wBZZGpP1oPvMU", + "y" : "19P9ELK-ZoxUeyEva7FMiPD-zTiopLLHhe075izksoA" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044fea55b32cb32aca0c12c4cd0abfb4e64b0f5a516e578c016591a93f5a0fbcc5d7d3fd10b2be668c547b212f6bb14c88f0fecd38a8a4b2c785ed3be62ce4b280", + "wx" : "4fea55b32cb32aca0c12c4cd0abfb4e64b0f5a516e578c016591a93f5a0fbcc5", + "wy" : "00d7d3fd10b2be668c547b212f6bb14c88f0fecd38a8a4b2c785ed3be62ce4b280" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044fea55b32cb32aca0c12c4cd0abfb4e64b0f5a516e578c016591a93f5a0fbcc5d7d3fd10b2be668c547b212f6bb14c88f0fecd38a8a4b2c785ed3be62ce4b280", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAET+pVsyyzKsoMEsTNCr+05ksPWlFu\nV4wBZZGpP1oPvMXX0/0Qsr5mjFR7IS9rsUyI8P7NOKiksseF7TvmLOSygA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 176, + "comment" : "extreme value for k and edgecase s", + "msg" : "313233343030", + "sig" : "7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "xqdxUnAkIneSFwpvju5zW_Mrf5ivZp6tKZgC4y18MQc", + "y" : "vDtLXmWriHu9NDVys-VhkmH-Ogc-L_14QS9yaGfbWJ4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04c6a771527024227792170a6f8eee735bf32b7f98af669ead299802e32d7c3107bc3b4b5e65ab887bbd343572b3e5619261fe3a073e2ffd78412f726867db589e", + "wx" : "00c6a771527024227792170a6f8eee735bf32b7f98af669ead299802e32d7c3107", + "wy" : "00bc3b4b5e65ab887bbd343572b3e5619261fe3a073e2ffd78412f726867db589e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004c6a771527024227792170a6f8eee735bf32b7f98af669ead299802e32d7c3107bc3b4b5e65ab887bbd343572b3e5619261fe3a073e2ffd78412f726867db589e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExqdxUnAkIneSFwpvju5zW/Mrf5iv\nZp6tKZgC4y18MQe8O0teZauIe700NXKz5WGSYf46Bz4v/XhBL3JoZ9tYng==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 177, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978b6db6db6249249254924924924924924625bd7a09bec4ca81bcdd9f8fd6b63cc", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "hRwrutCOVOx6mvmfSfA2RNbsbVmyB_7JjehafRW5Vu8", + "y" : "zumWAoMEUHVoS0EL6ND3SUuRqiN59gcnMZ8Q3esP6dY" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04851c2bbad08e54ec7a9af99f49f03644d6ec6d59b207fec98de85a7d15b956efcee9960283045075684b410be8d0f7494b91aa2379f60727319f10ddeb0fe9d6", + "wx" : "00851c2bbad08e54ec7a9af99f49f03644d6ec6d59b207fec98de85a7d15b956ef", + "wy" : "00cee9960283045075684b410be8d0f7494b91aa2379f60727319f10ddeb0fe9d6" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004851c2bbad08e54ec7a9af99f49f03644d6ec6d59b207fec98de85a7d15b956efcee9960283045075684b410be8d0f7494b91aa2379f60727319f10ddeb0fe9d6", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhRwrutCOVOx6mvmfSfA2RNbsbVmy\nB/7JjehafRW5Vu/O6ZYCgwRQdWhLQQvo0PdJS5GqI3n2BycxnxDd6w/p1g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 178, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978cccccccc00000000cccccccccccccccc971f2ef152794b9d8fc7d568c9e8eaa7", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "9kF8imcFhOOIZ2lJ5T2n_FWRH_aDGNG_MGEgWssZxI8", + "y" : "jyt0PfNK0PcmdKy3UFkpeEd5zZrJFsNmnq1DAmq21D8" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04f6417c8a670584e388676949e53da7fc55911ff68318d1bf3061205acb19c48f8f2b743df34ad0f72674acb7505929784779cd9ac916c3669ead43026ab6d43f", + "wx" : "00f6417c8a670584e388676949e53da7fc55911ff68318d1bf3061205acb19c48f", + "wy" : "008f2b743df34ad0f72674acb7505929784779cd9ac916c3669ead43026ab6d43f" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004f6417c8a670584e388676949e53da7fc55911ff68318d1bf3061205acb19c48f8f2b743df34ad0f72674acb7505929784779cd9ac916c3669ead43026ab6d43f", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9kF8imcFhOOIZ2lJ5T2n/FWRH/aD\nGNG/MGEgWssZxI+PK3Q980rQ9yZ0rLdQWSl4R3nNmskWw2aerUMCarbUPw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 179, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc476699783333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aaa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "UBQhJ3vkWl7v7GxjmTDWNgMlZa9CDPM3P1V_qn-KBkM", + "y" : "hnPWy2B24c_Nx9_nOEyOXKwI10UB8q5uicrRldCqE3E" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04501421277be45a5eefec6c639930d636032565af420cf3373f557faa7f8a06438673d6cb6076e1cfcdc7dfe7384c8e5cac08d74501f2ae6e89cad195d0aa1371", + "wx" : "501421277be45a5eefec6c639930d636032565af420cf3373f557faa7f8a0643", + "wy" : "008673d6cb6076e1cfcdc7dfe7384c8e5cac08d74501f2ae6e89cad195d0aa1371" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004501421277be45a5eefec6c639930d636032565af420cf3373f557faa7f8a06438673d6cb6076e1cfcdc7dfe7384c8e5cac08d74501f2ae6e89cad195d0aa1371", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUBQhJ3vkWl7v7GxjmTDWNgMlZa9C\nDPM3P1V/qn+KBkOGc9bLYHbhz83H3+c4TI5crAjXRQHyrm6JytGV0KoTcQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 180, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc4766997849249248db6db6dbb6db6db6db6db6db5a8b230d0b2b51dcd7ebf0c9fef7c185", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "DZNb-f_BFaUnc19ynKikyiPuAaSJSt8ONBWshOgIuzQ", + "y" : "MZWjdi_qKe04kSvZ6mxP3nDDBQiTpDdYUM5h2C66M8U" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "040d935bf9ffc115a527735f729ca8a4ca23ee01a4894adf0e3415ac84e808bb343195a3762fea29ed38912bd9ea6c4fde70c3050893a4375850ce61d82eba33c5", + "wx" : "0d935bf9ffc115a527735f729ca8a4ca23ee01a4894adf0e3415ac84e808bb34", + "wy" : "3195a3762fea29ed38912bd9ea6c4fde70c3050893a4375850ce61d82eba33c5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200040d935bf9ffc115a527735f729ca8a4ca23ee01a4894adf0e3415ac84e808bb343195a3762fea29ed38912bd9ea6c4fde70c3050893a4375850ce61d82eba33c5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDZNb+f/BFaUnc19ynKikyiPuAaSJ\nSt8ONBWshOgIuzQxlaN2L+op7TiRK9nqbE/ecMMFCJOkN1hQzmHYLrozxQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 181, + "comment" : "extreme value for k", + "msg" : "313233343030", + "sig" : "7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc4766997816a4502e2781e11ac82cbc9d1edd8c981584d13e18411e2f6e0478c34416e3bb", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "Xln1Bwhka-iliTVQFDCOYLZo-2cBliBsQedI5k5NyiE", + "y" : "XeN_7lyXvK9xRNW0WZgvUu7q-98Dqsuv7zjiE2JKAd4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "045e59f50708646be8a589355014308e60b668fb670196206c41e748e64e4dca215de37fee5c97bcaf7144d5b459982f52eeeafbdf03aacbafef38e213624a01de", + "wx" : "5e59f50708646be8a589355014308e60b668fb670196206c41e748e64e4dca21", + "wy" : "5de37fee5c97bcaf7144d5b459982f52eeeafbdf03aacbafef38e213624a01de" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200045e59f50708646be8a589355014308e60b668fb670196206c41e748e64e4dca215de37fee5c97bcaf7144d5b459982f52eeeafbdf03aacbafef38e213624a01de", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXln1Bwhka+iliTVQFDCOYLZo+2cB\nliBsQedI5k5NyiFd43/uXJe8r3FE1bRZmC9S7ur73wOqy6/vOOITYkoB3g==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 182, + "comment" : "extreme value for k and edgecase s", + "msg" : "313233343030", + "sig" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296555555550000000055555555555555553ef7a8e48d07df81a693439654210c70", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "Fp-3lzJYQ_r_L3pbVEXani_WIm9--Q7wv-kkEEsC244", + "y" : "e7uN5mLHubHPmyL3ouWCvUbVgdaIeO-yuGGxMdih1mc" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04169fb797325843faff2f7a5b5445da9e2fd6226f7ef90ef0bfe924104b02db8e7bbb8de662c7b9b1cf9b22f7a2e582bd46d581d68878efb2b861b131d8a1d667", + "wx" : "169fb797325843faff2f7a5b5445da9e2fd6226f7ef90ef0bfe924104b02db8e", + "wy" : "7bbb8de662c7b9b1cf9b22f7a2e582bd46d581d68878efb2b861b131d8a1d667" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004169fb797325843faff2f7a5b5445da9e2fd6226f7ef90ef0bfe924104b02db8e7bbb8de662c7b9b1cf9b22f7a2e582bd46d581d68878efb2b861b131d8a1d667", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFp+3lzJYQ/r/L3pbVEXani/WIm9+\n+Q7wv+kkEEsC2457u43mYse5sc+bIvei5YK9RtWB1oh477K4YbEx2KHWZw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 183, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296b6db6db6249249254924924924924924625bd7a09bec4ca81bcdd9f8fd6b63cc", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "JxzYnAABQwlrYtTp5MqIWu8vcCPRiv_a-Le1SJgUh1Q", + "y" : "ChxulU4yEIQ1tV-jhbD3ZIGmCbkUnMtLArLKR_6OTaU" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04271cd89c000143096b62d4e9e4ca885aef2f7023d18affdaf8b7b548981487540a1c6e954e32108435b55fa385b0f76481a609b9149ccb4b02b2ca47fe8e4da5", + "wx" : "271cd89c000143096b62d4e9e4ca885aef2f7023d18affdaf8b7b54898148754", + "wy" : "0a1c6e954e32108435b55fa385b0f76481a609b9149ccb4b02b2ca47fe8e4da5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004271cd89c000143096b62d4e9e4ca885aef2f7023d18affdaf8b7b548981487540a1c6e954e32108435b55fa385b0f76481a609b9149ccb4b02b2ca47fe8e4da5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJxzYnAABQwlrYtTp5MqIWu8vcCPR\niv/a+Le1SJgUh1QKHG6VTjIQhDW1X6OFsPdkgaYJuRScy0sCsspH/o5NpQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 184, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296cccccccc00000000cccccccccccccccc971f2ef152794b9d8fc7d568c9e8eaa7", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "PQvH7Y8J0st920brwe15mrFWOpq4S_UkWHoiCv5JnBI", + "y" : "4i3Ds8EDgkpPN42WrbCkCKvxnOfWiqYkT3jLIW-j-N8" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043d0bc7ed8f09d2cb7ddb46ebc1ed799ab1563a9ab84bf524587a220afe499c12e22dc3b3c103824a4f378d96adb0a408abf19ce7d68aa6244f78cb216fa3f8df", + "wx" : "3d0bc7ed8f09d2cb7ddb46ebc1ed799ab1563a9ab84bf524587a220afe499c12", + "wy" : "00e22dc3b3c103824a4f378d96adb0a408abf19ce7d68aa6244f78cb216fa3f8df" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043d0bc7ed8f09d2cb7ddb46ebc1ed799ab1563a9ab84bf524587a220afe499c12e22dc3b3c103824a4f378d96adb0a408abf19ce7d68aa6244f78cb216fa3f8df", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPQvH7Y8J0st920brwe15mrFWOpq4\nS/UkWHoiCv5JnBLiLcOzwQOCSk83jZatsKQIq/Gc59aKpiRPeMshb6P43w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 185, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2963333333300000000333333333333333325c7cbbc549e52e763f1f55a327a3aaa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "psiFreGkxWb5uwENBml0q7KBeX-nASiMchvL0jZjqbc", + "y" : "LkJLaQlXFo0ZOmCW_HeisASpx9Rn4Afh8gWEWPmK8xY" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04a6c885ade1a4c566f9bb010d066974abb281797fa701288c721bcbd23663a9b72e424b690957168d193a6096fc77a2b004a9c7d467e007e1f2058458f98af316", + "wx" : "00a6c885ade1a4c566f9bb010d066974abb281797fa701288c721bcbd23663a9b7", + "wy" : "2e424b690957168d193a6096fc77a2b004a9c7d467e007e1f2058458f98af316" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004a6c885ade1a4c566f9bb010d066974abb281797fa701288c721bcbd23663a9b72e424b690957168d193a6096fc77a2b004a9c7d467e007e1f2058458f98af316", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpsiFreGkxWb5uwENBml0q7KBeX+n\nASiMchvL0jZjqbcuQktpCVcWjRk6YJb8d6KwBKnH1GfgB+HyBYRY+YrzFg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 186, + "comment" : "extreme value for k and s^-1", + "msg" : "313233343030", + "sig" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29649249248db6db6dbb6db6db6db6db6db5a8b230d0b2b51dcd7ebf0c9fef7c185", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "jTwsLDt2W6gonmrDgSVyolv3XfYth6tzMMO9utnr-lw", + "y" : "TGhFRC1mk1sjhXjUOuxU98qhYh0a8kHUYy4LeAxCP10" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "048d3c2c2c3b765ba8289e6ac3812572a25bf75df62d87ab7330c3bdbad9ebfa5c4c6845442d66935b238578d43aec54f7caa1621d1af241d4632e0b780c423f5d", + "wx" : "008d3c2c2c3b765ba8289e6ac3812572a25bf75df62d87ab7330c3bdbad9ebfa5c", + "wy" : "4c6845442d66935b238578d43aec54f7caa1621d1af241d4632e0b780c423f5d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200048d3c2c2c3b765ba8289e6ac3812572a25bf75df62d87ab7330c3bdbad9ebfa5c4c6845442d66935b238578d43aec54f7caa1621d1af241d4632e0b780c423f5d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjTwsLDt2W6gonmrDgSVyolv3XfYt\nh6tzMMO9utnr+lxMaEVELWaTWyOFeNQ67FT3yqFiHRryQdRjLgt4DEI/XQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 187, + "comment" : "extreme value for k", + "msg" : "313233343030", + "sig" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29616a4502e2781e11ac82cbc9d1edd8c981584d13e18411e2f6e0478c34416e3bb", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpY", + "y" : "T-NC4v4af5uO5-tKfA-eFivOM1drMV7Oy7ZAaDe_UfU" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", + "wx" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "wy" : "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaxfR8uEsQkf4vOblY6RA8ncDfYEt\n6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9Q==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 188, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 189, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "44a5ad0ad0636d9f12bc9e0a6bdd5e1cbcb012ea7bf091fcec15b0c43202d52e249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpY", + "y" : "sBy9HAHlgGVxGBS1g_Bh6dQxzKmUzqExNEm_l8hArgo" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296b01cbd1c01e58065711814b583f061e9d431cca994cea1313449bf97c840ae0a", + "wx" : "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "wy" : "00b01cbd1c01e58065711814b583f061e9d431cca994cea1313449bf97c840ae0a" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296b01cbd1c01e58065711814b583f061e9d431cca994cea1313449bf97c840ae0a", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaxfR8uEsQkf4vOblY6RA8ncDfYEt\n6zOg9KE5RdiYwpawHL0cAeWAZXEYFLWD8GHp1DHMqZTOoTE0Sb+XyECuCg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 190, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "bb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 191, + "comment" : "testing point duplication", + "msg" : "313233343030", + "sig" : "44a5ad0ad0636d9f12bc9e0a6bdd5e1cbcb012ea7bf091fcec15b0c43202d52e249249246db6db6ddb6db6db6db6db6dad4591868595a8ee6bf5f864ff7be0c2", + "result" : "invalid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "BKrsc2NXJvIT-4qeZNo7hjLkFJWpRNAEW1IuunJA-tU", + "y" : "h9kxV5iqo6W6AXdXh87QXqr3tOCfyB1tGqVG6DZdUl0" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0404aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad587d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d", + "wx" : "04aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad5", + "wy" : "0087d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000404aaec73635726f213fb8a9e64da3b8632e41495a944d0045b522eba7240fad587d9315798aaa3a5ba01775787ced05eaaf7b4e09fc81d6d1aa546e8365d525d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBKrsc2NXJvIT+4qeZNo7hjLkFJWp\nRNAEW1IuunJA+tWH2TFXmKqjpboBd1eHztBeqve04J/IHW0apUboNl1SXQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 192, + "comment" : "pseudorandom signature", + "msg" : "", + "sig" : "b292a619339f6e567a305c951c0dcbcc42d16e47f219f9e98e76e09d8770b34a0177e60492c5a8242f76f07bfe3661bde59ec2a17ce5bd2dab2abebdf89a62e2", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 193, + "comment" : "pseudorandom signature", + "msg" : "4d7367", + "sig" : "530bd6b0c9af2d69ba897f6b5fb59695cfbf33afe66dbadcf5b8d2a2a6538e23d85e489cb7a161fd55ededcedbf4cc0c0987e3e3f0f242cae934c72caa3f43e9", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 194, + "comment" : "pseudorandom signature", + "msg" : "313233343030", + "sig" : "a8ea150cb80125d7381c4c1f1da8e9de2711f9917060406a73d7904519e51388f3ab9fa68bd47973a73b2d40480c2ba50c22c9d76ec217257288293285449b86", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 195, + "comment" : "pseudorandom signature", + "msg" : "0000000000000000000000000000000000000000", + "sig" : "986e65933ef2ed4ee5aada139f52b70539aaf63f00a91f29c69178490d57fb713dafedfb8da6189d372308cbf1489bbbdabf0c0217d1c0ff0f701aaa7a694b9c", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "TzN8z9Z3JqgF5PFgCuKEnfOAfsoRc4Ajn72BaQAAAAA", + "y" : "7Z3qEkzIw5ZBZBHpiMMPQn61BK9DoxRs1d9-pgZm1oU" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "044f337ccfd67726a805e4f1600ae2849df3807eca117380239fbd816900000000ed9dea124cc8c396416411e988c30f427eb504af43a3146cd5df7ea60666d685", + "wx" : "4f337ccfd67726a805e4f1600ae2849df3807eca117380239fbd816900000000", + "wy" : "00ed9dea124cc8c396416411e988c30f427eb504af43a3146cd5df7ea60666d685" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200044f337ccfd67726a805e4f1600ae2849df3807eca117380239fbd816900000000ed9dea124cc8c396416411e988c30f427eb504af43a3146cd5df7ea60666d685", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETzN8z9Z3JqgF5PFgCuKEnfOAfsoR\nc4Ajn72BaQAAAADtneoSTMjDlkFkEemIww9CfrUEr0OjFGzV336mBmbWhQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 196, + "comment" : "x-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "d434e262a49eab7781e353a3565e482550dd0fd5defa013c7f29745eff3569f19b0c0a93f267fb6052fd8077be769c2b98953195d7bc10de844218305c6ba17a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 197, + "comment" : "x-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "0fe774355c04d060f76d79fd7a772e421463489221bf0a33add0be9b1979110b500dcba1c69a8fbd43fa4f57f743ce124ca8b91a1f325f3fac6181175df55737", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 198, + "comment" : "x-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "bb40bf217bed3fb3950c7d39f03d36dc8e3b2cd79693f125bfd06595ee1135e3541bf3532351ebb032710bdb6a1bf1bfc89a1e291ac692b3fa4780745bb55677", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "PPA9YU2JOc_UmaB4c_rCgWGPBrj_h-gBXD9JcmUASTU", + "y" : "hPoXTXkccr8s44gKiWDdKnx6EzioL4Wp5Zzb3oAAAAA" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f49726500493584fa174d791c72bf2ce3880a8960dd2a7c7a1338a82f85a9e59cdbde80000000", + "wx" : "3cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f497265004935", + "wy" : "0084fa174d791c72bf2ce3880a8960dd2a7c7a1338a82f85a9e59cdbde80000000" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f49726500493584fa174d791c72bf2ce3880a8960dd2a7c7a1338a82f85a9e59cdbde80000000", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPPA9YU2JOc/UmaB4c/rCgWGPBrj/\nh+gBXD9JcmUASTWE+hdNeRxyvyzjiAqJYN0qfHoTOKgvhanlnNvegAAAAA==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 199, + "comment" : "y-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "664eb7ee6db84a34df3c86ea31389a5405badd5ca99231ff556d3e75a233e73a59f3c752e52eca46137642490a51560ce0badc678754b8f72e51a2901426a1bd", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 200, + "comment" : "y-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "4cd0429bbabd2827009d6fcd843d4ce39c3e42e2d1631fd001985a79d1fd8b439638bf12dd682f60be7ef1d0e0d98f08b7bca77a1a2b869ae466189d2acdabe3", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 201, + "comment" : "y-coordinate of the public key has many trailing 0's", + "msg" : "4d657373616765", + "sig" : "e56c6ea2d1b017091c44d8b6cb62b9f460e3ce9aed5e5fd41e8added97c56c04a308ec31f281e955be20b457e463440b4fcf2b80258078207fc1378180f89b55", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "PPA9YU2JOc_UmaB4c_rCgWGPBrj_h-gBXD9JcmUASTU", + "y" : "ewXosYbjjUHTHHf1dp8i1YOF7MhX0HpWGmMkIX____8" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f4972650049357b05e8b186e38d41d31c77f5769f22d58385ecc857d07a561a6324217fffffff", + "wx" : "3cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f497265004935", + "wy" : "7b05e8b186e38d41d31c77f5769f22d58385ecc857d07a561a6324217fffffff" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200043cf03d614d8939cfd499a07873fac281618f06b8ff87e8015c3f4972650049357b05e8b186e38d41d31c77f5769f22d58385ecc857d07a561a6324217fffffff", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPPA9YU2JOc/UmaB4c/rCgWGPBrj/\nh+gBXD9JcmUASTV7BeixhuONQdMcd/V2nyLVg4XsyFfQelYaYyQhf////w==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 202, + "comment" : "y-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "1158a08d291500b4cabed3346d891eee57c176356a2624fb011f8fbbf3466830228a8c486a736006e082325b85290c5bc91f378b75d487dda46798c18f285519", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 203, + "comment" : "y-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "b1db9289649f59410ea36b0c0fc8d6aa2687b29176939dd23e0dde56d309fa9d3e1535e4280559015b0dbd987366dcf43a6d1af5c23c7d584e1c3f48a1251336", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 204, + "comment" : "y-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "b7b16e762286cb96446aa8d4e6e7578b0a341a79f2dd1a220ac6f0ca4e24ed86ddc60a700a139b04661c547d07bbb0721780146df799ccf55e55234ecb8f12bc", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "KCnDH6ouQA40TtlLyj_NBUWVbrz-itD236X_jv____8", + "y" : "oBqvrwAOUlhYVa-nZ2reKEETCZBS31fn6zvTfr65Ii4" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "042829c31faa2e400e344ed94bca3fcd0545956ebcfe8ad0f6dfa5ff8effffffffa01aafaf000e52585855afa7676ade284113099052df57e7eb3bd37ebeb9222e", + "wx" : "2829c31faa2e400e344ed94bca3fcd0545956ebcfe8ad0f6dfa5ff8effffffff", + "wy" : "00a01aafaf000e52585855afa7676ade284113099052df57e7eb3bd37ebeb9222e" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d030107034200042829c31faa2e400e344ed94bca3fcd0545956ebcfe8ad0f6dfa5ff8effffffffa01aafaf000e52585855afa7676ade284113099052df57e7eb3bd37ebeb9222e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKCnDH6ouQA40TtlLyj/NBUWVbrz+\nitD236X/jv////+gGq+vAA5SWFhVr6dnat4oQRMJkFLfV+frO9N+vrkiLg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 205, + "comment" : "x-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "d82a7c2717261187c8e00d8df963ff35d796edad36bc6e6bd1c91c670d9105b43dcabddaf8fcaa61f4603e7cbac0f3c0351ecd5988efb23f680d07debd139929", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 206, + "comment" : "x-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "5eb9c8845de68eb13d5befe719f462d77787802baff30ce96a5cba063254af782c026ae9be2e2a5e7ca0ff9bbd92fb6e44972186228ee9a62b87ddbe2ef66fb5", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 207, + "comment" : "x-coordinate of the public key has many trailing 1's", + "msg" : "4d657373616765", + "sig" : "96843dd03c22abd2f3b782b170239f90f277921becc117d0404a8e4e36230c28f2be378f526f74a543f67165976de9ed9a31214eb4d7e6db19e1ede123dd991d", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "____-UgIHmoEWN2PnnOPJmX_kFmtaqwHCDGMTKmnpPU", + "y" : "Woq8ui3ahHQxHuVBSblzyuDA-4lVetC_eOZSmhZjvXM" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04fffffff948081e6a0458dd8f9e738f2665ff9059ad6aac0708318c4ca9a7a4f55a8abcba2dda8474311ee54149b973cae0c0fb89557ad0bf78e6529a1663bd73", + "wx" : "00fffffff948081e6a0458dd8f9e738f2665ff9059ad6aac0708318c4ca9a7a4f5", + "wy" : "5a8abcba2dda8474311ee54149b973cae0c0fb89557ad0bf78e6529a1663bd73" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004fffffff948081e6a0458dd8f9e738f2665ff9059ad6aac0708318c4ca9a7a4f55a8abcba2dda8474311ee54149b973cae0c0fb89557ad0bf78e6529a1663bd73", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE////+UgIHmoEWN2PnnOPJmX/kFmt\naqwHCDGMTKmnpPVairy6LdqEdDEe5UFJuXPK4MD7iVV60L945lKaFmO9cw==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 208, + "comment" : "x-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "766456dce1857c906f9996af729339464d27e9d98edc2d0e3b760297067421f6402385ecadae0d8081dccaf5d19037ec4e55376eced699e93646bfbbf19d0b41", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 209, + "comment" : "x-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "c605c4b2edeab20419e6518a11b2dbc2b97ed8b07cced0b19c34f777de7b9fd9edf0f612c5f46e03c719647bc8af1b29b2cde2eda700fb1cff5e159d47326dba", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 210, + "comment" : "x-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "d48b68e6cabfe03cf6141c9ac54141f210e64485d9929ad7b732bfe3b7eb8a84feedae50c61bd00e19dc26f9b7e2265e4508c389109ad2f208f0772315b6c941", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "AAAAA_oV-WOUnV8DpvXH-G-eABXusjrrv_EXOTe6dI4", + "y" : "EJmHIHDo6HxVX6E2Wcyl1_rc_LACPqiJVIykivK6fnE" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "0400000003fa15f963949d5f03a6f5c7f86f9e0015eeb23aebbff1173937ba748e1099872070e8e87c555fa13659cca5d7fadcfcb0023ea889548ca48af2ba7e71", + "wx" : "03fa15f963949d5f03a6f5c7f86f9e0015eeb23aebbff1173937ba748e", + "wy" : "1099872070e8e87c555fa13659cca5d7fadcfcb0023ea889548ca48af2ba7e71" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d0301070342000400000003fa15f963949d5f03a6f5c7f86f9e0015eeb23aebbff1173937ba748e1099872070e8e87c555fa13659cca5d7fadcfcb0023ea889548ca48af2ba7e71", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAAAAA/oV+WOUnV8DpvXH+G+eABXu\nsjrrv/EXOTe6dI4QmYcgcOjofFVfoTZZzKXX+tz8sAI+qIlUjKSK8rp+cQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 211, + "comment" : "x-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "b7c81457d4aeb6aa65957098569f0479710ad7f6595d5874c35a93d12a5dd4c7b7961a0b652878c2d568069a432ca18a1a9199f2ca574dad4b9e3a05c0a1cdb3", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 212, + "comment" : "x-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "6b01332ddb6edfa9a30a1321d5858e1ee3cf97e263e669f8de5e9652e76ff3f75939545fced457309a6a04ace2bd0f70139c8f7d86b02cb1cc58f9e69e96cd5a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 213, + "comment" : "x-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "efdb884720eaeadc349f9fc356b6c0344101cd2fd8436b7d0e6a4fb93f106361f24bee6ad5dc05f7613975473aadf3aacba9e77de7d69b6ce48cb60d8113385d", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "vLspFMefBF6qbsu8YSgWs75dLWeWcH2BJen4UcGK8BU", + "y" : "AAAAABNSu0oPoupMzrmrY91oSt5aESe88wCmmKcZO8I" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015000000001352bb4a0fa2ea4cceb9ab63dd684ade5a1127bcf300a698a7193bc2", + "wx" : "00bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015", + "wy" : "1352bb4a0fa2ea4cceb9ab63dd684ade5a1127bcf300a698a7193bc2" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015000000001352bb4a0fa2ea4cceb9ab63dd684ade5a1127bcf300a698a7193bc2", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvLspFMefBF6qbsu8YSgWs75dLWeW\ncH2BJen4UcGK8BUAAAAAE1K7Sg+i6kzOuatj3WhK3loRJ7zzAKaYpxk7wg==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 214, + "comment" : "y-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "31230428405560dcb88fb5a646836aea9b23a23dd973dcbe8014c87b8b20eb070f9344d6e812ce166646747694a41b0aaf97374e19f3c5fb8bd7ae3d9bd0beff", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 215, + "comment" : "y-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "caa797da65b320ab0d5c470cda0b36b294359c7db9841d679174db34c4855743cf543a62f23e212745391aaf7505f345123d2685ee3b941d3de6d9b36242e5a0", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 216, + "comment" : "y-coordinate of the public key is small", + "msg" : "4d657373616765", + "sig" : "7e5f0ab5d900d3d3d7867657e5d6d36519bc54084536e7d21c336ed8001859459450c07f201faec94b82dfb322e5ac676688294aad35aa72e727ff0b19b646aa", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "jwk" : { + "crv" : "P-256", + "kid" : "none", + "kty" : "EC", + "x" : "vLspFMefBF6qbsu8YSgWs75dLWeWcH2BJen4UcGK8BU", + "y" : "_____uytRLbwXRWzMUZUnCKXtSKl7thDDP9ZZ1jmxD0" + }, + "key" : { + "curve" : "secp256r1", + "keySize" : 256, + "type" : "EcPublicKey", + "uncompressed" : "04bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015fffffffeecad44b6f05d15b33146549c2297b522a5eed8430cff596758e6c43d", + "wx" : "00bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015", + "wy" : "00fffffffeecad44b6f05d15b33146549c2297b522a5eed8430cff596758e6c43d" + }, + "keyDer" : "3059301306072a8648ce3d020106082a8648ce3d03010703420004bcbb2914c79f045eaa6ecbbc612816b3be5d2d6796707d8125e9f851c18af015fffffffeecad44b6f05d15b33146549c2297b522a5eed8430cff596758e6c43d", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvLspFMefBF6qbsu8YSgWs75dLWeW\ncH2BJen4UcGK8BX////+7K1EtvBdFbMxRlScIpe1IqXu2EMM/1lnWObEPQ==\n-----END PUBLIC KEY-----", + "sha" : "SHA-256", + "type" : "EcdsaP1363Verify", + "tests" : [ + { + "tcId" : 217, + "comment" : "y-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "d7d70c581ae9e3f66dc6a480bf037ae23f8a1e4a2136fe4b03aa69f0ca25b35689c460f8a5a5c2bbba962c8a3ee833a413e85658e62a59e2af41d9127cc47224", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 218, + "comment" : "y-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "341c1b9ff3c83dd5e0dfa0bf68bcdf4bb7aa20c625975e5eeee34bb396266b3472b69f061b750fd5121b22b11366fad549c634e77765a017902a67099e0a4469", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 219, + "comment" : "y-coordinate of the public key is large", + "msg" : "4d657373616765", + "sig" : "70bebe684cdcb5ca72a42f0d873879359bd1781a591809947628d313a3814f67aec03aca8f5587a4d535fa31027bbe9cc0e464b1c3577f4c2dcde6b2094798a9", + "result" : "valid", + "flags" : [] + } + ] + } + ] +} From 4b33d326fa818082649830b2dc8dab84419852d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 13:31:57 +0200 Subject: [PATCH 02/81] Update dependency rimraf to v6 (#5108) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 159 ++++++++++++++++++++++++++++++++++++++-------- package.json | 2 +- 2 files changed, 135 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index db7e9efe085..3fde7447b83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "p-limit": "^3.1.0", "prettier": "^3.0.0", "prettier-plugin-solidity": "^1.1.0", - "rimraf": "^5.0.1", + "rimraf": "^6.0.0", "semver": "^7.3.5", "solhint": "^5.0.0", "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", @@ -5338,15 +5338,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob/node_modules/minipass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", - "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -7128,6 +7119,16 @@ "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", "dev": true }, + "node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -7322,6 +7323,16 @@ "node": ">= 6" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mixme": { "version": "0.5.9", "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.9.tgz", @@ -7995,6 +8006,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8083,15 +8101,6 @@ "node": "14 || >=16.14" } }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", - "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8670,18 +8679,118 @@ } }, "node_modules/rimraf": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", - "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.0.tgz", + "integrity": "sha512-u+yqhM92LW+89cxUQK0SRyvXYQmyuKHx0jkx4W7KfwLGLqJnQM5031Uv1trE4gB9XEXBM/s6MxKlfW95IidqaA==", "dev": true, + "license": "ISC", "dependencies": { - "glob": "^10.2.5" + "glob": "^11.0.0" }, "bin": { - "rimraf": "dist/cjs/src/bin.js" + "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=14" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", + "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.0.tgz", + "integrity": "sha512-l/mOwLWs7BQIgOKrL46dIAbyCKvPV7YJPDspkuc88rHsZRlg3hptUGdU7Trv0VFP4d3xnSGBQrKu5ZvGB7UeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^3.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.0.tgz", + "integrity": "sha512-S4phymWe5NHWbTV8sAlyNQfkmdhvaoHX43x4yLtJBjw2zJtEuzkihDjV5uKq+D/EoMkjbG6msw3ubbSd1pGkyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^4.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" diff --git a/package.json b/package.json index f5ab048374c..2c39a108d95 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "p-limit": "^3.1.0", "prettier": "^3.0.0", "prettier-plugin-solidity": "^1.1.0", - "rimraf": "^5.0.1", + "rimraf": "^6.0.0", "semver": "^7.3.5", "solhint": "^5.0.0", "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", From b73bcb231fb6f5e7bc973edc75ab7f6c812a2255 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 15 Jul 2024 18:08:58 +0200 Subject: [PATCH 03/81] Process and verify merkle proofs (and multiproof) with custom hash function (#4887) Co-authored-by: ernestognw --- .changeset/spotty-queens-own.md | 5 + contracts/mocks/MerkleProofCustomHashMock.sol | 62 +++ contracts/utils/cryptography/MerkleProof.sol | 330 ++++++++++++++-- contracts/utils/structs/MerkleTree.sol | 4 + package-lock.json | 69 +--- package.json | 2 +- scripts/generate/run.js | 1 + scripts/generate/templates/MerkleProof.js | 178 +++++++++ .../generate/templates/MerkleProof.opts.js | 11 + test/utils/cryptography/MerkleProof.test.js | 372 ++++++++++-------- 10 files changed, 772 insertions(+), 262 deletions(-) create mode 100644 .changeset/spotty-queens-own.md create mode 100644 contracts/mocks/MerkleProofCustomHashMock.sol create mode 100644 scripts/generate/templates/MerkleProof.js create mode 100644 scripts/generate/templates/MerkleProof.opts.js diff --git a/.changeset/spotty-queens-own.md b/.changeset/spotty-queens-own.md new file mode 100644 index 00000000000..98fb2fbc0ed --- /dev/null +++ b/.changeset/spotty-queens-own.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`MerkleProof`: Add variations of `verify`, `processProof`, `multiProofVerify` and `processMultiProof` (and equivalent calldata version) with support for custom hashing functions. diff --git a/contracts/mocks/MerkleProofCustomHashMock.sol b/contracts/mocks/MerkleProofCustomHashMock.sol new file mode 100644 index 00000000000..1039af32fb5 --- /dev/null +++ b/contracts/mocks/MerkleProofCustomHashMock.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; + +// This could be a library, but then we would have to add it to the Stateless.sol mock for upgradeable tests +abstract contract MerkleProofCustomHashMock { + function customHash(bytes32 a, bytes32 b) internal pure returns (bytes32) { + return a < b ? sha256(abi.encode(a, b)) : sha256(abi.encode(b, a)); + } + + function verify(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal view returns (bool) { + return MerkleProof.verify(proof, root, leaf, customHash); + } + + function processProof(bytes32[] calldata proof, bytes32 leaf) internal view returns (bytes32) { + return MerkleProof.processProof(proof, leaf, customHash); + } + + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal view returns (bool) { + return MerkleProof.verifyCalldata(proof, root, leaf, customHash); + } + + function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal view returns (bytes32) { + return MerkleProof.processProofCalldata(proof, leaf, customHash); + } + + function multiProofVerify( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32 root, + bytes32[] calldata leaves + ) internal view returns (bool) { + return MerkleProof.multiProofVerify(proof, proofFlags, root, leaves, customHash); + } + + function processMultiProof( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32[] calldata leaves + ) internal view returns (bytes32) { + return MerkleProof.processMultiProof(proof, proofFlags, leaves, customHash); + } + + function multiProofVerifyCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32 root, + bytes32[] calldata leaves + ) internal view returns (bool) { + return MerkleProof.multiProofVerifyCalldata(proof, proofFlags, root, leaves, customHash); + } + + function processMultiProofCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32[] calldata leaves + ) internal view returns (bytes32) { + return MerkleProof.processMultiProofCalldata(proof, proofFlags, leaves, customHash); + } +} diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index dce05a0dc05..be1910062bb 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol) +// This file was procedurally generated from scripts/generate/templates/MerkleProof.js. pragma solidity ^0.8.20; @@ -18,6 +19,11 @@ import {Hashes} from "./Hashes.sol"; * the Merkle tree could be reinterpreted as a leaf value. * OpenZeppelin's JavaScript library generates Merkle trees that are safe * against this attack out of the box. + * + * NOTE: This library supports proof verification for merkle trees built using + * custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving + * leaf inclusion in trees built using non-commutative hashing functions requires + * additional logic that is not supported by this library. */ library MerkleProof { /** @@ -30,16 +36,44 @@ library MerkleProof { * defined by `root`. For this, a `proof` must be provided, containing * sibling hashes on the branch from the leaf to the root of the tree. Each * pair of leaves and each pair of pre-images are assumed to be sorted. + * + * This version handles proofs in memory with the default hashing function. */ function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { return processProof(proof, leaf) == root; } /** - * @dev Calldata version of {verify} + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. + * + * This version handles proofs in memory with the default hashing function. */ - function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { - return processProofCalldata(proof, leaf) == root; + function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]); + } + return computedHash; + } + + /** + * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree + * defined by `root`. For this, a `proof` must be provided, containing + * sibling hashes on the branch from the leaf to the root of the tree. Each + * pair of leaves and each pair of pre-images are assumed to be sorted. + * + * This version handles proofs in memory with a custom hashing function. + */ + function verify( + bytes32[] memory proof, + bytes32 root, + bytes32 leaf, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bool) { + return processProof(proof, leaf, hasher) == root; } /** @@ -47,17 +81,40 @@ library MerkleProof { * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs * of leafs & pre-images are assumed to be sorted. + * + * This version handles proofs in memory with a custom hashing function. */ - function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + function processProof( + bytes32[] memory proof, + bytes32 leaf, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bytes32) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { - computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]); + computedHash = hasher(computedHash, proof[i]); } return computedHash; } /** - * @dev Calldata version of {processProof} + * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree + * defined by `root`. For this, a `proof` must be provided, containing + * sibling hashes on the branch from the leaf to the root of the tree. Each + * pair of leaves and each pair of pre-images are assumed to be sorted. + * + * This version handles proofs in calldata with the default hashing function. + */ + function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { + return processProof(proof, leaf) == root; + } + + /** + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. + * + * This version handles proofs in calldata with the default hashing function. */ function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) { bytes32 computedHash = leaf; @@ -67,10 +124,49 @@ library MerkleProof { return computedHash; } + /** + * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree + * defined by `root`. For this, a `proof` must be provided, containing + * sibling hashes on the branch from the leaf to the root of the tree. Each + * pair of leaves and each pair of pre-images are assumed to be sorted. + * + * This version handles proofs in calldata with a custom hashing function. + */ + function verifyCalldata( + bytes32[] calldata proof, + bytes32 root, + bytes32 leaf, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bool) { + return processProof(proof, leaf, hasher) == root; + } + + /** + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. + * + * This version handles proofs in calldata with a custom hashing function. + */ + function processProofCalldata( + bytes32[] calldata proof, + bytes32 leaf, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = hasher(computedHash, proof[i]); + } + return computedHash; + } + /** * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. * + * This version handles multiproofs in memory with the default hashing function. + * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. */ function multiProofVerify( @@ -83,7 +179,151 @@ library MerkleProof { } /** - * @dev Calldata version of {multiProofVerify} + * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false + * respectively. + * + * This version handles multiproofs in memory with the default hashing function. + * + * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree + * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the + * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + */ + function processMultiProof( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32[] memory leaves + ) internal pure returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the Merkle tree. + uint256 leavesLen = leaves.length; + + // Check proof validity. + if (leavesLen + proof.length != proofFlags.length + 1) { + revert MerkleProofInvalidMultiproof(); + } + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](proofFlags.length); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < proofFlags.length; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = Hashes.commutativeKeccak256(a, b); + } + + if (proofFlags.length > 0) { + if (proofPos != proof.length) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[proofFlags.length - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by + * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + * + * This version handles multiproofs in memory with a custom hashing function. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ + function multiProofVerify( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32 root, + bytes32[] memory leaves, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bool) { + return processMultiProof(proof, proofFlags, leaves, hasher) == root; + } + + /** + * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false + * respectively. + * + * This version handles multiproofs in memory with a custom hashing function. + * + * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree + * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the + * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + */ + function processMultiProof( + bytes32[] memory proof, + bool[] memory proofFlags, + bytes32[] memory leaves, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the + // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of + // the Merkle tree. + uint256 leavesLen = leaves.length; + + // Check proof validity. + if (leavesLen + proof.length != proofFlags.length + 1) { + revert MerkleProofInvalidMultiproof(); + } + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](proofFlags.length); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // `proof` array. + for (uint256 i = 0; i < proofFlags.length; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = hasher(a, b); + } + + if (proofFlags.length > 0) { + if (proofPos != proof.length) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[proofFlags.length - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } + } + + /** + * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by + * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + * + * This version handles multiproofs in calldata with the default hashing function. * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. */ @@ -91,9 +331,9 @@ library MerkleProof { bytes32[] calldata proof, bool[] calldata proofFlags, bytes32 root, - bytes32[] memory leaves + bytes32[] calldata leaves ) internal pure returns (bool) { - return processMultiProofCalldata(proof, proofFlags, leaves) == root; + return processMultiProof(proof, proofFlags, leaves) == root; } /** @@ -102,31 +342,31 @@ library MerkleProof { * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false * respectively. * + * This version handles multiproofs in calldata with the default hashing function. + * * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). */ - function processMultiProof( - bytes32[] memory proof, - bool[] memory proofFlags, - bytes32[] memory leaves + function processMultiProofCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32[] calldata leaves ) internal pure returns (bytes32 merkleRoot) { // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the Merkle tree. uint256 leavesLen = leaves.length; - uint256 proofLen = proof.length; - uint256 totalHashes = proofFlags.length; // Check proof validity. - if (leavesLen + proofLen != totalHashes + 1) { + if (leavesLen + proof.length != proofFlags.length + 1) { revert MerkleProofInvalidMultiproof(); } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". - bytes32[] memory hashes = new bytes32[](totalHashes); + bytes32[] memory hashes = new bytes32[](proofFlags.length); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; @@ -135,7 +375,7 @@ library MerkleProof { // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. - for (uint256 i = 0; i < totalHashes; i++) { + for (uint256 i = 0; i < proofFlags.length; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) @@ -143,12 +383,12 @@ library MerkleProof { hashes[i] = Hashes.commutativeKeccak256(a, b); } - if (totalHashes > 0) { - if (proofPos != proofLen) { + if (proofFlags.length > 0) { + if (proofPos != proof.length) { revert MerkleProofInvalidMultiproof(); } unchecked { - return hashes[totalHashes - 1]; + return hashes[proofFlags.length - 1]; } } else if (leavesLen > 0) { return leaves[0]; @@ -158,31 +398,55 @@ library MerkleProof { } /** - * @dev Calldata version of {processMultiProof}. + * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by + * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}. + * + * This version handles multiproofs in calldata with a custom hashing function. * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. */ + function multiProofVerifyCalldata( + bytes32[] calldata proof, + bool[] calldata proofFlags, + bytes32 root, + bytes32[] calldata leaves, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bool) { + return processMultiProof(proof, proofFlags, leaves, hasher) == root; + } + + /** + * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false + * respectively. + * + * This version handles multiproofs in calldata with a custom hashing function. + * + * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree + * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the + * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + */ function processMultiProofCalldata( bytes32[] calldata proof, bool[] calldata proofFlags, - bytes32[] memory leaves - ) internal pure returns (bytes32 merkleRoot) { + bytes32[] calldata leaves, + function(bytes32, bytes32) view returns (bytes32) hasher + ) internal view returns (bytes32 merkleRoot) { // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the Merkle tree. uint256 leavesLen = leaves.length; - uint256 proofLen = proof.length; - uint256 totalHashes = proofFlags.length; // Check proof validity. - if (leavesLen + proofLen != totalHashes + 1) { + if (leavesLen + proof.length != proofFlags.length + 1) { revert MerkleProofInvalidMultiproof(); } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". - bytes32[] memory hashes = new bytes32[](totalHashes); + bytes32[] memory hashes = new bytes32[](proofFlags.length); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; @@ -191,20 +455,20 @@ library MerkleProof { // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. - for (uint256 i = 0; i < totalHashes; i++) { + for (uint256 i = 0; i < proofFlags.length; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) : proof[proofPos++]; - hashes[i] = Hashes.commutativeKeccak256(a, b); + hashes[i] = hasher(a, b); } - if (totalHashes > 0) { - if (proofPos != proofLen) { + if (proofFlags.length > 0) { + if (proofPos != proof.length) { revert MerkleProofInvalidMultiproof(); } unchecked { - return hashes[totalHashes - 1]; + return hashes[proofFlags.length - 1]; } } else if (leavesLen > 0) { return leaves[0]; diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index 2fb99402594..93f59ace585 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -19,6 +19,10 @@ import {Panic} from "../Panic.sol"; * * Zero value: The value that represents an empty leaf. Used to avoid regular zero values to be part of the tree. * * Hashing function: A cryptographic hash function used to produce internal nodes. Defaults to {Hashes-commutativeKeccak256}. * + * NOTE: Building trees using non-commutative hashing functions (i.e. `H(a, b) != H(b, a)`) is supported. However, + * proving the inclusion of a leaf in such trees is not possible with the {MerkleProof} library since it only supports + * _commutative_ hashing functions. + * * _Available since v5.1._ */ library MerkleTree { diff --git a/package-lock.json b/package-lock.json index 3fde7447b83..7326aebeaa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", "@openzeppelin/docs-utils": "^0.1.5", - "@openzeppelin/merkle-tree": "^1.0.6", + "@openzeppelin/merkle-tree": "^1.0.7", "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", @@ -1990,70 +1990,15 @@ } }, "node_modules/@openzeppelin/merkle-tree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@openzeppelin/merkle-tree/-/merkle-tree-1.0.6.tgz", - "integrity": "sha512-cGWOb2WBWbJhqvupzxjnKAwGLxxAEYPg51sk76yZ5nVe5D03mw7Vx5yo8llaIEqYhP5O39M8QlrNWclgLfKVrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@openzeppelin/merkle-tree/-/merkle-tree-1.0.7.tgz", + "integrity": "sha512-i93t0YYv6ZxTCYU3CdO5Q+DXK0JH10A4dCBOMlzYbX+ujTXm+k1lXiEyVqmf94t3sqmv8sm/XT5zTa0+efnPgQ==", "dev": true, "dependencies": { "@ethersproject/abi": "^5.7.0", - "ethereum-cryptography": "^1.1.2" - } - }, - "node_modules/@openzeppelin/merkle-tree/node_modules/@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@openzeppelin/merkle-tree/node_modules/@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/@openzeppelin/merkle-tree/node_modules/@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/@openzeppelin/merkle-tree/node_modules/ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0" } }, "node_modules/@openzeppelin/upgrade-safe-transpiler": { diff --git a/package.json b/package.json index 2c39a108d95..868a4d3d0e2 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", "@openzeppelin/docs-utils": "^0.1.5", - "@openzeppelin/merkle-tree": "^1.0.6", + "@openzeppelin/merkle-tree": "^1.0.7", "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", diff --git a/scripts/generate/run.js b/scripts/generate/run.js index f8ec17606ac..b3215ad1133 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -32,6 +32,7 @@ function generateFromTemplate(file, template, outputPrefix = '') { // Contracts for (const [file, template] of Object.entries({ + 'utils/cryptography/MerkleProof.sol': './templates/MerkleProof.js', 'utils/math/SafeCast.sol': './templates/SafeCast.js', 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js new file mode 100644 index 00000000000..768a981ee6b --- /dev/null +++ b/scripts/generate/templates/MerkleProof.js @@ -0,0 +1,178 @@ +const format = require('../format-lines'); +const { OPTS } = require('./MerkleProof.opts'); + +const DEFAULT_HASH = 'Hashes.commutativeKeccak256'; + +const formatArgsSingleLine = (...args) => args.filter(Boolean).join(', '); +const formatArgsMultiline = (...args) => '\n' + format(args.filter(Boolean).join(',\0').split('\0')); + +// TEMPLATE +const header = `\ +pragma solidity ^0.8.20; + +import {Hashes} from "./Hashes.sol"; + +/** + * @dev These functions deal with verification of Merkle Tree proofs. + * + * The tree and the proofs can be generated using our + * https://github.com/OpenZeppelin/merkle-tree[JavaScript library]. + * You will find a quickstart guide in the readme. + * + * WARNING: You should avoid using leaf values that are 64 bytes long prior to + * hashing, or use a hash function other than keccak256 for hashing leaves. + * This is because the concatenation of a sorted pair of internal nodes in + * the Merkle tree could be reinterpreted as a leaf value. + * OpenZeppelin's JavaScript library generates Merkle trees that are safe + * against this attack out of the box. + * + * NOTE: This library supports proof verification for merkle trees built using + * custom _commutative_ hashing functions (i.e. \`H(a, b) == H(b, a)\`). Proving + * leaf inclusion in trees built using non-commutative hashing functions requires + * additional logic that is not supported by this library. + */ +`; + +const errors = `\ +/** + *@dev The multiproof provided is not valid. + */ +error MerkleProofInvalidMultiproof(); +`; + +/* eslint-disable max-len */ +const templateProof = ({ suffix, location, visibility, hash }) => `\ +/** + * @dev Returns true if a \`leaf\` can be proved to be a part of a Merkle tree + * defined by \`root\`. For this, a \`proof\` must be provided, containing + * sibling hashes on the branch from the leaf to the root of the tree. Each + * pair of leaves and each pair of pre-images are assumed to be sorted. + * + * This version handles proofs in ${location} with ${hash ? 'a custom' : 'the default'} hashing function. + */ +function verify${suffix}(${(hash ? formatArgsMultiline : formatArgsSingleLine)( + `bytes32[] ${location} proof`, + 'bytes32 root', + 'bytes32 leaf', + hash && `function(bytes32, bytes32) view returns (bytes32) ${hash}`, +)}) internal ${visibility} returns (bool) { + return processProof(proof, leaf${hash ? `, ${hash}` : ''}) == root; +} + +/** + * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up + * from \`leaf\` using \`proof\`. A \`proof\` is valid if and only if the rebuilt + * hash matches the root of the tree. When processing the proof, the pairs + * of leafs & pre-images are assumed to be sorted. + * + * This version handles proofs in ${location} with ${hash ? 'a custom' : 'the default'} hashing function. + */ +function processProof${suffix}(${(hash ? formatArgsMultiline : formatArgsSingleLine)( + `bytes32[] ${location} proof`, + 'bytes32 leaf', + hash && `function(bytes32, bytes32) view returns (bytes32) ${hash}`, +)}) internal ${visibility} returns (bytes32) { + bytes32 computedHash = leaf; + for (uint256 i = 0; i < proof.length; i++) { + computedHash = ${hash ?? DEFAULT_HASH}(computedHash, proof[i]); + } + return computedHash; +} +`; + +const templateMultiProof = ({ suffix, location, visibility, hash }) => `\ +/** + * @dev Returns true if the \`leaves\` can be simultaneously proven to be a part of a Merkle tree defined by + * \`root\`, according to \`proof\` and \`proofFlags\` as described in {processMultiProof}. + * + * This version handles multiproofs in ${location} with ${hash ? 'a custom' : 'the default'} hashing function. + * + * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + */ +function multiProofVerify${suffix}(${formatArgsMultiline( + `bytes32[] ${location} proof`, + `bool[] ${location} proofFlags`, + 'bytes32 root', + `bytes32[] ${location} leaves`, + hash && `function(bytes32, bytes32) view returns (bytes32) ${hash}`, +)}) internal ${visibility} returns (bool) { + return processMultiProof(proof, proofFlags, leaves${hash ? `, ${hash}` : ''}) == root; +} + +/** + * @dev Returns the root of a tree reconstructed from \`leaves\` and sibling nodes in \`proof\`. The reconstruction + * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another + * leaf/inner node or a proof sibling node, depending on whether each \`proofFlags\` item is true or false + * respectively. + * + * This version handles multiproofs in ${location} with ${hash ? 'a custom' : 'the default'} hashing function. + * + * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree + * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the + * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + */ +function processMultiProof${suffix}(${formatArgsMultiline( + `bytes32[] ${location} proof`, + `bool[] ${location} proofFlags`, + `bytes32[] ${location} leaves`, + hash && `function(bytes32, bytes32) view returns (bytes32) ${hash}`, +)}) internal ${visibility} returns (bytes32 merkleRoot) { + // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by + // consuming and producing values on a queue. The queue starts with the \`leaves\` array, then goes onto the + // \`hashes\` array. At the end of the process, the last hash in the \`hashes\` array should contain the root of + // the Merkle tree. + uint256 leavesLen = leaves.length; + + // Check proof validity. + if (leavesLen + proof.length != proofFlags.length + 1) { + revert MerkleProofInvalidMultiproof(); + } + + // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using + // \`xxx[xxxPos++]\`, which return the current value and increment the pointer, thus mimicking a queue's "pop". + bytes32[] memory hashes = new bytes32[](proofFlags.length); + uint256 leafPos = 0; + uint256 hashPos = 0; + uint256 proofPos = 0; + // At each step, we compute the next hash using two values: + // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we + // get the next hash. + // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the + // \`proof\` array. + for (uint256 i = 0; i < proofFlags.length; i++) { + bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; + bytes32 b = proofFlags[i] + ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) + : proof[proofPos++]; + hashes[i] = ${hash ?? DEFAULT_HASH}(a, b); + } + + if (proofFlags.length > 0) { + if (proofPos != proof.length) { + revert MerkleProofInvalidMultiproof(); + } + unchecked { + return hashes[proofFlags.length - 1]; + } + } else if (leavesLen > 0) { + return leaves[0]; + } else { + return proof[0]; + } +} +`; +/* eslint-enable max-len */ + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library MerkleProof {', + format( + [].concat( + errors, + OPTS.flatMap(opts => templateProof(opts)), + OPTS.flatMap(opts => templateMultiProof(opts)), + ), + ).trimEnd(), + '}', +); diff --git a/scripts/generate/templates/MerkleProof.opts.js b/scripts/generate/templates/MerkleProof.opts.js new file mode 100644 index 00000000000..911f2392257 --- /dev/null +++ b/scripts/generate/templates/MerkleProof.opts.js @@ -0,0 +1,11 @@ +const { product } = require('../../helpers'); + +const OPTS = product( + [ + { suffix: '', location: 'memory' }, + { suffix: 'Calldata', location: 'calldata' }, + ], + [{ visibility: 'pure' }, { visibility: 'view', hash: 'hasher' }], +).map(objs => Object.assign({}, ...objs)); + +module.exports = { OPTS }; diff --git a/test/utils/cryptography/MerkleProof.test.js b/test/utils/cryptography/MerkleProof.test.js index 9d5502d5981..93ee964a860 100644 --- a/test/utils/cryptography/MerkleProof.test.js +++ b/test/utils/cryptography/MerkleProof.test.js @@ -1,173 +1,213 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { StandardMerkleTree } = require('@openzeppelin/merkle-tree'); +const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); +const { SimpleMerkleTree } = require('@openzeppelin/merkle-tree'); -const toElements = str => str.split('').map(e => [e]); -const hashPair = (a, b) => ethers.keccak256(Buffer.concat([a, b].sort(Buffer.compare))); - -async function fixture() { - const mock = await ethers.deployContract('$MerkleProof'); - return { mock }; -} +// generate bytes32 leaves from a string +const toLeaves = (str, separator = '') => str.split(separator).map(e => ethers.keccak256(ethers.toUtf8Bytes(e))); +// internal node hashes +const concatSorted = (...elements) => Buffer.concat(elements.map(ethers.getBytes).sort(Buffer.compare)); +const defaultHash = (a, b) => ethers.keccak256(concatSorted(a, b)); +const customHash = (a, b) => ethers.sha256(concatSorted(a, b)); describe('MerkleProof', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('verify', function () { - it('returns true for a valid Merkle proof', async function () { - const merkleTree = StandardMerkleTree.of( - toElements('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='), - ['string'], - ); - - const root = merkleTree.root; - const hash = merkleTree.leafHash(['A']); - const proof = merkleTree.getProof(['A']); - - expect(await this.mock.$verify(proof, root, hash)).to.be.true; - expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.true; - - // For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements: - const noSuchLeaf = hashPair( - ethers.toBeArray(merkleTree.leafHash(['A'])), - ethers.toBeArray(merkleTree.leafHash(['B'])), - ); - expect(await this.mock.$verify(proof.slice(1), root, noSuchLeaf)).to.be.true; - expect(await this.mock.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.be.true; - }); - - it('returns false for an invalid Merkle proof', async function () { - const correctMerkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); - const otherMerkleTree = StandardMerkleTree.of(toElements('def'), ['string']); - - const root = correctMerkleTree.root; - const hash = correctMerkleTree.leafHash(['a']); - const proof = otherMerkleTree.getProof(['d']); - - expect(await this.mock.$verify(proof, root, hash)).to.be.false; - expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.false; - }); - - it('returns false for a Merkle proof of invalid length', async function () { - const merkleTree = StandardMerkleTree.of(toElements('abc'), ['string']); - - const root = merkleTree.root; - const hash = merkleTree.leafHash(['a']); - const proof = merkleTree.getProof(['a']); - const badProof = proof.slice(0, -1); - - expect(await this.mock.$verify(badProof, root, hash)).to.be.false; - expect(await this.mock.$verifyCalldata(badProof, root, hash)).to.be.false; - }); - }); - - describe('multiProofVerify', function () { - it('returns true for a valid Merkle multi proof', async function () { - const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); - - const root = merkleTree.root; - const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf')); - const hashes = leaves.map(e => merkleTree.leafHash(e)); - - expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true; - expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true; - }); - - it('returns false for an invalid Merkle multi proof', async function () { - const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']); - const otherMerkleTree = StandardMerkleTree.of(toElements('ghi'), ['string']); - - const root = merkleTree.root; - const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi')); - const hashes = leaves.map(e => merkleTree.leafHash(e)); - - expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.false; - expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.false; - }); - - it('revert with invalid multi proof #1', async function () { - const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); - - const root = merkleTree.root; - const hashA = merkleTree.leafHash(['a']); - const hashB = merkleTree.leafHash(['b']); - const hashCD = hashPair( - ethers.toBeArray(merkleTree.leafHash(['c'])), - ethers.toBeArray(merkleTree.leafHash(['d'])), - ); - const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) - const fill = ethers.randomBytes(32); - - await expect( - this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]), - ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); - - await expect( - this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]), - ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); - }); - - it('revert with invalid multi proof #2', async function () { - const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); - - const root = merkleTree.root; - const hashA = merkleTree.leafHash(['a']); - const hashB = merkleTree.leafHash(['b']); - const hashCD = hashPair( - ethers.toBeArray(merkleTree.leafHash(['c'])), - ethers.toBeArray(merkleTree.leafHash(['d'])), - ); - const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) - const fill = ethers.randomBytes(32); - - await expect( - this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]), - ).to.be.revertedWithPanic(0x32); - - await expect( - this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]), - ).to.be.revertedWithPanic(0x32); - }); - - it('limit case: works for tree containing a single leaf', async function () { - const merkleTree = StandardMerkleTree.of(toElements('a'), ['string']); - - const root = merkleTree.root; - const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a')); - const hashes = leaves.map(e => merkleTree.leafHash(e)); - - expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true; - expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true; - }); - - it('limit case: can prove empty leaves', async function () { - const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); - - const root = merkleTree.root; - expect(await this.mock.$multiProofVerify([root], [], root, [])).to.be.true; - expect(await this.mock.$multiProofVerifyCalldata([root], [], root, [])).to.be.true; - }); - - it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () { - // Create a merkle tree that contains a zero leaf at depth 1 - const leave = ethers.id('real leaf'); - const root = hashPair(ethers.toBeArray(leave), Buffer.alloc(32, 0)); - - // Now we can pass any **malicious** fake leaves as valid! - const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id).map(ethers.toBeArray).sort(Buffer.compare); - const maliciousProof = [leave, leave]; - const maliciousProofFlags = [true, true, false]; - - await expect( - this.mock.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves), - ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); - - await expect( - this.mock.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves), - ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + for (const { title, contractName, nodeHash } of [ + { title: 'default hash', contractName: '$MerkleProof', nodeHash: defaultHash }, + { title: 'custom hash', contractName: '$MerkleProofCustomHashMock', nodeHash: customHash }, + ]) { + describe(title, function () { + // stateless: no need for a fixture, just use before + before(async function () { + this.mock = await ethers.deployContract(contractName); + this.makeTree = str => SimpleMerkleTree.of(toLeaves(str), { nodeHash }); + }); + + describe('verify', function () { + it('returns true for a valid Merkle proof', async function () { + const merkleTree = this.makeTree('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='); + + const root = merkleTree.root; + const hash = merkleTree.at(0); + const proof = merkleTree.getProof(0); + + expect(await this.mock.$processProof(proof, hash)).to.equal(root); + expect(await this.mock.$processProofCalldata(proof, hash)).to.equal(root); + expect(await this.mock.$verify(proof, root, hash)).to.be.true; + expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.true; + + // For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements: + const noSuchLeaf = nodeHash(hash, proof.at(0)); + + expect(await this.mock.$processProof(proof.slice(1), noSuchLeaf)).to.equal(root); + expect(await this.mock.$processProofCalldata(proof.slice(1), noSuchLeaf)).to.equal(root); + expect(await this.mock.$verify(proof.slice(1), root, noSuchLeaf)).to.be.true; + expect(await this.mock.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.be.true; + }); + + it('returns false for an invalid Merkle proof', async function () { + const correctMerkleTree = this.makeTree('abc'); + const otherMerkleTree = this.makeTree('def'); + + const root = correctMerkleTree.root; + const hash = correctMerkleTree.at(0); + const proof = otherMerkleTree.getProof(0); + + expect(await this.mock.$processProof(proof, hash)).to.not.equal(root); + expect(await this.mock.$processProofCalldata(proof, hash)).to.not.equal(root); + expect(await this.mock.$verify(proof, root, hash)).to.be.false; + expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.false; + }); + + it('returns false for a Merkle proof of invalid length', async function () { + const merkleTree = this.makeTree('abc'); + + const root = merkleTree.root; + const hash = merkleTree.at(0); + const proof = merkleTree.getProof(0); + const badProof = proof.slice(0, -1); + + expect(await this.mock.$processProof(badProof, hash)).to.not.equal(root); + expect(await this.mock.$processProofCalldata(badProof, hash)).to.not.equal(root); + expect(await this.mock.$verify(badProof, root, hash)).to.be.false; + expect(await this.mock.$verifyCalldata(badProof, root, hash)).to.be.false; + }); + }); + + describe('multiProofVerify', function () { + it('returns true for a valid Merkle multi proof', async function () { + const merkleTree = this.makeTree('abcdef'); + + const root = merkleTree.root; + const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toLeaves('bdf')); + const hashes = leaves.map(e => merkleTree.leafHash(e)); + + expect(await this.mock.$processMultiProof(proof, proofFlags, hashes)).to.equal(root); + expect(await this.mock.$processMultiProofCalldata(proof, proofFlags, hashes)).to.equal(root); + expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true; + expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true; + }); + + it('returns false for an invalid Merkle multi proof', async function () { + const merkleTree = this.makeTree('abcdef'); + const otherMerkleTree = this.makeTree('ghi'); + + const root = merkleTree.root; + const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toLeaves('ghi')); + const hashes = leaves.map(e => merkleTree.leafHash(e)); + + expect(await this.mock.$processMultiProof(proof, proofFlags, hashes)).to.not.equal(root); + expect(await this.mock.$processMultiProofCalldata(proof, proofFlags, hashes)).to.not.equal(root); + expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.false; + expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.false; + }); + + it('revert with invalid multi proof #1', async function () { + const merkleTree = this.makeTree('abcd'); + + const root = merkleTree.root; + const hashA = merkleTree.at(0); + const hashB = merkleTree.at(1); + const hashCD = nodeHash(merkleTree.at(2), merkleTree.at(3)); + const hashE = ethers.randomBytes(32); // incorrect (not part of the tree) + const fill = ethers.randomBytes(32); + + await expect( + this.mock.$processMultiProof([hashB, fill, hashCD], [false, false, false], [hashA, hashE]), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + + await expect( + this.mock.$processMultiProofCalldata([hashB, fill, hashCD], [false, false, false], [hashA, hashE]), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + + await expect( + this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + + await expect( + this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + }); + + it('revert with invalid multi proof #2', async function () { + const merkleTree = this.makeTree('abcd'); + + const root = merkleTree.root; + const hashA = merkleTree.at(0); + const hashB = merkleTree.at(1); + const hashCD = nodeHash(merkleTree.at(2), merkleTree.at(3)); + const hashE = ethers.randomBytes(32); // incorrect (not part of the tree) + const fill = ethers.randomBytes(32); + + await expect( + this.mock.$processMultiProof([hashB, fill, hashCD], [false, false, false, false], [hashE, hashA]), + ).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + + await expect( + this.mock.$processMultiProofCalldata([hashB, fill, hashCD], [false, false, false, false], [hashE, hashA]), + ).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + + await expect( + this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]), + ).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + + await expect( + this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false, false], root, [ + hashE, + hashA, + ]), + ).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + }); + + it('limit case: works for tree containing a single leaf', async function () { + const merkleTree = this.makeTree('a'); + + const root = merkleTree.root; + const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toLeaves('a')); + const hashes = leaves.map(e => merkleTree.leafHash(e)); + + expect(await this.mock.$processMultiProof(proof, proofFlags, hashes)).to.equal(root); + expect(await this.mock.$processMultiProofCalldata(proof, proofFlags, hashes)).to.equal(root); + expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true; + expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true; + }); + + it('limit case: can prove empty leaves', async function () { + const merkleTree = this.makeTree('abcd'); + + const root = merkleTree.root; + expect(await this.mock.$processMultiProof([root], [], [])).to.equal(root); + expect(await this.mock.$processMultiProofCalldata([root], [], [])).to.equal(root); + expect(await this.mock.$multiProofVerify([root], [], root, [])).to.be.true; + expect(await this.mock.$multiProofVerifyCalldata([root], [], root, [])).to.be.true; + }); + + it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () { + // Create a merkle tree that contains a zero leaf at depth 1 + const leave = ethers.id('real leaf'); + const root = nodeHash(leave, ethers.ZeroHash); + + // Now we can pass any **malicious** fake leaves as valid! + const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id).map(ethers.toBeArray).sort(Buffer.compare); + const maliciousProof = [leave, leave]; + const maliciousProofFlags = [true, true, false]; + + await expect( + this.mock.$processMultiProof(maliciousProof, maliciousProofFlags, maliciousLeaves), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + + await expect( + this.mock.$processMultiProofCalldata(maliciousProof, maliciousProofFlags, maliciousLeaves), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + + await expect( + this.mock.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + + await expect( + this.mock.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves), + ).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof'); + }); + }); }); - }); + } }); From 19a657bef8354f2a655900654955739b70dfbde9 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sun, 21 Jul 2024 20:56:15 +0200 Subject: [PATCH 04/81] Protect Packing.pack and Packing.replace against values that include dirty bits (#5117) --- contracts/utils/Packing.sol | 125 ++++++++++++++++++++++++++ scripts/generate/templates/Packing.js | 3 + 2 files changed, 128 insertions(+) diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index a5a38b50397..8a8e3fee86f 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -35,210 +35,280 @@ library Packing { function pack_1_1(bytes1 left, bytes1 right) internal pure returns (bytes2 result) { assembly ("memory-safe") { + left := and(left, shl(248, not(0))) + right := and(right, shl(248, not(0))) result := or(left, shr(8, right)) } } function pack_2_2(bytes2 left, bytes2 right) internal pure returns (bytes4 result) { assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(240, not(0))) result := or(left, shr(16, right)) } } function pack_2_4(bytes2 left, bytes4 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(16, right)) } } function pack_2_6(bytes2 left, bytes6 right) internal pure returns (bytes8 result) { assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(208, not(0))) result := or(left, shr(16, right)) } } function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(240, not(0))) result := or(left, shr(32, right)) } } function pack_4_4(bytes4 left, bytes4 right) internal pure returns (bytes8 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(32, right)) } } function pack_4_8(bytes4 left, bytes8 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(192, not(0))) result := or(left, shr(32, right)) } } function pack_4_12(bytes4 left, bytes12 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(160, not(0))) result := or(left, shr(32, right)) } } function pack_4_16(bytes4 left, bytes16 right) internal pure returns (bytes20 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(128, not(0))) result := or(left, shr(32, right)) } } function pack_4_20(bytes4 left, bytes20 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(96, not(0))) result := or(left, shr(32, right)) } } function pack_4_24(bytes4 left, bytes24 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(64, not(0))) result := or(left, shr(32, right)) } } function pack_4_28(bytes4 left, bytes28 right) internal pure returns (bytes32 result) { assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(32, not(0))) result := or(left, shr(32, right)) } } function pack_6_2(bytes6 left, bytes2 right) internal pure returns (bytes8 result) { assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(240, not(0))) result := or(left, shr(48, right)) } } function pack_6_6(bytes6 left, bytes6 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(208, not(0))) result := or(left, shr(48, right)) } } function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(64, right)) } } function pack_8_8(bytes8 left, bytes8 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(192, not(0))) result := or(left, shr(64, right)) } } function pack_8_12(bytes8 left, bytes12 right) internal pure returns (bytes20 result) { assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(160, not(0))) result := or(left, shr(64, right)) } } function pack_8_16(bytes8 left, bytes16 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(128, not(0))) result := or(left, shr(64, right)) } } function pack_8_20(bytes8 left, bytes20 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(96, not(0))) result := or(left, shr(64, right)) } } function pack_8_24(bytes8 left, bytes24 right) internal pure returns (bytes32 result) { assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(64, not(0))) result := or(left, shr(64, right)) } } function pack_12_4(bytes12 left, bytes4 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(96, right)) } } function pack_12_8(bytes12 left, bytes8 right) internal pure returns (bytes20 result) { assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(192, not(0))) result := or(left, shr(96, right)) } } function pack_12_12(bytes12 left, bytes12 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(160, not(0))) result := or(left, shr(96, right)) } } function pack_12_16(bytes12 left, bytes16 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(128, not(0))) result := or(left, shr(96, right)) } } function pack_12_20(bytes12 left, bytes20 right) internal pure returns (bytes32 result) { assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(96, not(0))) result := or(left, shr(96, right)) } } function pack_16_4(bytes16 left, bytes4 right) internal pure returns (bytes20 result) { assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(128, right)) } } function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(192, not(0))) result := or(left, shr(128, right)) } } function pack_16_12(bytes16 left, bytes12 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(160, not(0))) result := or(left, shr(128, right)) } } function pack_16_16(bytes16 left, bytes16 right) internal pure returns (bytes32 result) { assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(128, not(0))) result := or(left, shr(128, right)) } } function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(160, right)) } } function pack_20_8(bytes20 left, bytes8 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(192, not(0))) result := or(left, shr(160, right)) } } function pack_20_12(bytes20 left, bytes12 right) internal pure returns (bytes32 result) { assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(160, not(0))) result := or(left, shr(160, right)) } } function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { + left := and(left, shl(64, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(192, right)) } } function pack_24_8(bytes24 left, bytes8 right) internal pure returns (bytes32 result) { assembly ("memory-safe") { + left := and(left, shl(64, not(0))) + right := and(right, shl(192, not(0))) result := or(left, shr(192, right)) } } function pack_28_4(bytes28 left, bytes4 right) internal pure returns (bytes32 result) { assembly ("memory-safe") { + left := and(left, shl(32, not(0))) + right := and(right, shl(224, not(0))) result := or(left, shr(224, right)) } } @@ -253,6 +323,7 @@ library Packing { function replace_2_1(bytes2 self, bytes1 value, uint8 offset) internal pure returns (bytes2 result) { bytes1 oldValue = extract_2_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -267,6 +338,7 @@ library Packing { function replace_4_1(bytes4 self, bytes1 value, uint8 offset) internal pure returns (bytes4 result) { bytes1 oldValue = extract_4_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -281,6 +353,7 @@ library Packing { function replace_4_2(bytes4 self, bytes2 value, uint8 offset) internal pure returns (bytes4 result) { bytes2 oldValue = extract_4_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -295,6 +368,7 @@ library Packing { function replace_6_1(bytes6 self, bytes1 value, uint8 offset) internal pure returns (bytes6 result) { bytes1 oldValue = extract_6_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -309,6 +383,7 @@ library Packing { function replace_6_2(bytes6 self, bytes2 value, uint8 offset) internal pure returns (bytes6 result) { bytes2 oldValue = extract_6_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -323,6 +398,7 @@ library Packing { function replace_6_4(bytes6 self, bytes4 value, uint8 offset) internal pure returns (bytes6 result) { bytes4 oldValue = extract_6_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -337,6 +413,7 @@ library Packing { function replace_8_1(bytes8 self, bytes1 value, uint8 offset) internal pure returns (bytes8 result) { bytes1 oldValue = extract_8_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -351,6 +428,7 @@ library Packing { function replace_8_2(bytes8 self, bytes2 value, uint8 offset) internal pure returns (bytes8 result) { bytes2 oldValue = extract_8_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -365,6 +443,7 @@ library Packing { function replace_8_4(bytes8 self, bytes4 value, uint8 offset) internal pure returns (bytes8 result) { bytes4 oldValue = extract_8_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -379,6 +458,7 @@ library Packing { function replace_8_6(bytes8 self, bytes6 value, uint8 offset) internal pure returns (bytes8 result) { bytes6 oldValue = extract_8_6(self, offset); assembly ("memory-safe") { + value := and(value, shl(208, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -393,6 +473,7 @@ library Packing { function replace_12_1(bytes12 self, bytes1 value, uint8 offset) internal pure returns (bytes12 result) { bytes1 oldValue = extract_12_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -407,6 +488,7 @@ library Packing { function replace_12_2(bytes12 self, bytes2 value, uint8 offset) internal pure returns (bytes12 result) { bytes2 oldValue = extract_12_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -421,6 +503,7 @@ library Packing { function replace_12_4(bytes12 self, bytes4 value, uint8 offset) internal pure returns (bytes12 result) { bytes4 oldValue = extract_12_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -435,6 +518,7 @@ library Packing { function replace_12_6(bytes12 self, bytes6 value, uint8 offset) internal pure returns (bytes12 result) { bytes6 oldValue = extract_12_6(self, offset); assembly ("memory-safe") { + value := and(value, shl(208, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -449,6 +533,7 @@ library Packing { function replace_12_8(bytes12 self, bytes8 value, uint8 offset) internal pure returns (bytes12 result) { bytes8 oldValue = extract_12_8(self, offset); assembly ("memory-safe") { + value := and(value, shl(192, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -463,6 +548,7 @@ library Packing { function replace_16_1(bytes16 self, bytes1 value, uint8 offset) internal pure returns (bytes16 result) { bytes1 oldValue = extract_16_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -477,6 +563,7 @@ library Packing { function replace_16_2(bytes16 self, bytes2 value, uint8 offset) internal pure returns (bytes16 result) { bytes2 oldValue = extract_16_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -491,6 +578,7 @@ library Packing { function replace_16_4(bytes16 self, bytes4 value, uint8 offset) internal pure returns (bytes16 result) { bytes4 oldValue = extract_16_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -505,6 +593,7 @@ library Packing { function replace_16_6(bytes16 self, bytes6 value, uint8 offset) internal pure returns (bytes16 result) { bytes6 oldValue = extract_16_6(self, offset); assembly ("memory-safe") { + value := and(value, shl(208, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -519,6 +608,7 @@ library Packing { function replace_16_8(bytes16 self, bytes8 value, uint8 offset) internal pure returns (bytes16 result) { bytes8 oldValue = extract_16_8(self, offset); assembly ("memory-safe") { + value := and(value, shl(192, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -533,6 +623,7 @@ library Packing { function replace_16_12(bytes16 self, bytes12 value, uint8 offset) internal pure returns (bytes16 result) { bytes12 oldValue = extract_16_12(self, offset); assembly ("memory-safe") { + value := and(value, shl(160, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -547,6 +638,7 @@ library Packing { function replace_20_1(bytes20 self, bytes1 value, uint8 offset) internal pure returns (bytes20 result) { bytes1 oldValue = extract_20_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -561,6 +653,7 @@ library Packing { function replace_20_2(bytes20 self, bytes2 value, uint8 offset) internal pure returns (bytes20 result) { bytes2 oldValue = extract_20_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -575,6 +668,7 @@ library Packing { function replace_20_4(bytes20 self, bytes4 value, uint8 offset) internal pure returns (bytes20 result) { bytes4 oldValue = extract_20_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -589,6 +683,7 @@ library Packing { function replace_20_6(bytes20 self, bytes6 value, uint8 offset) internal pure returns (bytes20 result) { bytes6 oldValue = extract_20_6(self, offset); assembly ("memory-safe") { + value := and(value, shl(208, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -603,6 +698,7 @@ library Packing { function replace_20_8(bytes20 self, bytes8 value, uint8 offset) internal pure returns (bytes20 result) { bytes8 oldValue = extract_20_8(self, offset); assembly ("memory-safe") { + value := and(value, shl(192, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -617,6 +713,7 @@ library Packing { function replace_20_12(bytes20 self, bytes12 value, uint8 offset) internal pure returns (bytes20 result) { bytes12 oldValue = extract_20_12(self, offset); assembly ("memory-safe") { + value := and(value, shl(160, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -631,6 +728,7 @@ library Packing { function replace_20_16(bytes20 self, bytes16 value, uint8 offset) internal pure returns (bytes20 result) { bytes16 oldValue = extract_20_16(self, offset); assembly ("memory-safe") { + value := and(value, shl(128, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -645,6 +743,7 @@ library Packing { function replace_24_1(bytes24 self, bytes1 value, uint8 offset) internal pure returns (bytes24 result) { bytes1 oldValue = extract_24_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -659,6 +758,7 @@ library Packing { function replace_24_2(bytes24 self, bytes2 value, uint8 offset) internal pure returns (bytes24 result) { bytes2 oldValue = extract_24_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -673,6 +773,7 @@ library Packing { function replace_24_4(bytes24 self, bytes4 value, uint8 offset) internal pure returns (bytes24 result) { bytes4 oldValue = extract_24_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -687,6 +788,7 @@ library Packing { function replace_24_6(bytes24 self, bytes6 value, uint8 offset) internal pure returns (bytes24 result) { bytes6 oldValue = extract_24_6(self, offset); assembly ("memory-safe") { + value := and(value, shl(208, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -701,6 +803,7 @@ library Packing { function replace_24_8(bytes24 self, bytes8 value, uint8 offset) internal pure returns (bytes24 result) { bytes8 oldValue = extract_24_8(self, offset); assembly ("memory-safe") { + value := and(value, shl(192, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -715,6 +818,7 @@ library Packing { function replace_24_12(bytes24 self, bytes12 value, uint8 offset) internal pure returns (bytes24 result) { bytes12 oldValue = extract_24_12(self, offset); assembly ("memory-safe") { + value := and(value, shl(160, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -729,6 +833,7 @@ library Packing { function replace_24_16(bytes24 self, bytes16 value, uint8 offset) internal pure returns (bytes24 result) { bytes16 oldValue = extract_24_16(self, offset); assembly ("memory-safe") { + value := and(value, shl(128, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -743,6 +848,7 @@ library Packing { function replace_24_20(bytes24 self, bytes20 value, uint8 offset) internal pure returns (bytes24 result) { bytes20 oldValue = extract_24_20(self, offset); assembly ("memory-safe") { + value := and(value, shl(96, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -757,6 +863,7 @@ library Packing { function replace_28_1(bytes28 self, bytes1 value, uint8 offset) internal pure returns (bytes28 result) { bytes1 oldValue = extract_28_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -771,6 +878,7 @@ library Packing { function replace_28_2(bytes28 self, bytes2 value, uint8 offset) internal pure returns (bytes28 result) { bytes2 oldValue = extract_28_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -785,6 +893,7 @@ library Packing { function replace_28_4(bytes28 self, bytes4 value, uint8 offset) internal pure returns (bytes28 result) { bytes4 oldValue = extract_28_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -799,6 +908,7 @@ library Packing { function replace_28_6(bytes28 self, bytes6 value, uint8 offset) internal pure returns (bytes28 result) { bytes6 oldValue = extract_28_6(self, offset); assembly ("memory-safe") { + value := and(value, shl(208, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -813,6 +923,7 @@ library Packing { function replace_28_8(bytes28 self, bytes8 value, uint8 offset) internal pure returns (bytes28 result) { bytes8 oldValue = extract_28_8(self, offset); assembly ("memory-safe") { + value := and(value, shl(192, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -827,6 +938,7 @@ library Packing { function replace_28_12(bytes28 self, bytes12 value, uint8 offset) internal pure returns (bytes28 result) { bytes12 oldValue = extract_28_12(self, offset); assembly ("memory-safe") { + value := and(value, shl(160, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -841,6 +953,7 @@ library Packing { function replace_28_16(bytes28 self, bytes16 value, uint8 offset) internal pure returns (bytes28 result) { bytes16 oldValue = extract_28_16(self, offset); assembly ("memory-safe") { + value := and(value, shl(128, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -855,6 +968,7 @@ library Packing { function replace_28_20(bytes28 self, bytes20 value, uint8 offset) internal pure returns (bytes28 result) { bytes20 oldValue = extract_28_20(self, offset); assembly ("memory-safe") { + value := and(value, shl(96, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -869,6 +983,7 @@ library Packing { function replace_28_24(bytes28 self, bytes24 value, uint8 offset) internal pure returns (bytes28 result) { bytes24 oldValue = extract_28_24(self, offset); assembly ("memory-safe") { + value := and(value, shl(64, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -883,6 +998,7 @@ library Packing { function replace_32_1(bytes32 self, bytes1 value, uint8 offset) internal pure returns (bytes32 result) { bytes1 oldValue = extract_32_1(self, offset); assembly ("memory-safe") { + value := and(value, shl(248, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -897,6 +1013,7 @@ library Packing { function replace_32_2(bytes32 self, bytes2 value, uint8 offset) internal pure returns (bytes32 result) { bytes2 oldValue = extract_32_2(self, offset); assembly ("memory-safe") { + value := and(value, shl(240, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -911,6 +1028,7 @@ library Packing { function replace_32_4(bytes32 self, bytes4 value, uint8 offset) internal pure returns (bytes32 result) { bytes4 oldValue = extract_32_4(self, offset); assembly ("memory-safe") { + value := and(value, shl(224, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -925,6 +1043,7 @@ library Packing { function replace_32_6(bytes32 self, bytes6 value, uint8 offset) internal pure returns (bytes32 result) { bytes6 oldValue = extract_32_6(self, offset); assembly ("memory-safe") { + value := and(value, shl(208, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -939,6 +1058,7 @@ library Packing { function replace_32_8(bytes32 self, bytes8 value, uint8 offset) internal pure returns (bytes32 result) { bytes8 oldValue = extract_32_8(self, offset); assembly ("memory-safe") { + value := and(value, shl(192, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -953,6 +1073,7 @@ library Packing { function replace_32_12(bytes32 self, bytes12 value, uint8 offset) internal pure returns (bytes32 result) { bytes12 oldValue = extract_32_12(self, offset); assembly ("memory-safe") { + value := and(value, shl(160, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -967,6 +1088,7 @@ library Packing { function replace_32_16(bytes32 self, bytes16 value, uint8 offset) internal pure returns (bytes32 result) { bytes16 oldValue = extract_32_16(self, offset); assembly ("memory-safe") { + value := and(value, shl(128, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -981,6 +1103,7 @@ library Packing { function replace_32_20(bytes32 self, bytes20 value, uint8 offset) internal pure returns (bytes32 result) { bytes20 oldValue = extract_32_20(self, offset); assembly ("memory-safe") { + value := and(value, shl(96, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -995,6 +1118,7 @@ library Packing { function replace_32_24(bytes32 self, bytes24 value, uint8 offset) internal pure returns (bytes32 result) { bytes24 oldValue = extract_32_24(self, offset); assembly ("memory-safe") { + value := and(value, shl(64, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } @@ -1009,6 +1133,7 @@ library Packing { function replace_32_28(bytes32 self, bytes28 value, uint8 offset) internal pure returns (bytes32 result) { bytes28 oldValue = extract_32_28(self, offset); assembly ("memory-safe") { + value := and(value, shl(32, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } diff --git a/scripts/generate/templates/Packing.js b/scripts/generate/templates/Packing.js index a5ab2d4b5a2..b9422ef4833 100644 --- a/scripts/generate/templates/Packing.js +++ b/scripts/generate/templates/Packing.js @@ -44,6 +44,8 @@ function pack_${left}_${right}(bytes${left} left, bytes${right} right) internal left + right } result) { assembly ("memory-safe") { + left := and(left, shl(${256 - 8 * left}, not(0))) + right := and(right, shl(${256 - 8 * right}, not(0))) result := or(left, shr(${8 * left}, right)) } } @@ -62,6 +64,7 @@ const replace = (outer, inner) => `\ function replace_${outer}_${inner}(bytes${outer} self, bytes${inner} value, uint8 offset) internal pure returns (bytes${outer} result) { bytes${inner} oldValue = extract_${outer}_${inner}(self, offset); assembly ("memory-safe") { + value := and(value, shl(${256 - 8 * inner}, not(0))) result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } From e30b390d84ea27e430f56efcf39d19c1ec4d30eb Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 22 Jul 2024 17:23:08 +0200 Subject: [PATCH 05/81] Add `ERC7674` (draft) (#5071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: cairo --- .changeset/serious-carrots-provide.md | 5 + contracts/interfaces/README.adoc | 3 + contracts/interfaces/draft-IERC7674.sol | 16 ++ contracts/mocks/BatchCaller.sol | 20 +++ contracts/mocks/token/ERC20GetterHelper.sol | 38 +++++ contracts/token/ERC20/README.adoc | 3 + .../draft-ERC20TemporaryApproval.sol | 119 +++++++++++++++ test/token/ERC20/ERC20.behavior.js | 11 +- .../draft-ERC20TemporaryApproval.test.js | 142 ++++++++++++++++++ 9 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 .changeset/serious-carrots-provide.md create mode 100644 contracts/interfaces/draft-IERC7674.sol create mode 100644 contracts/mocks/BatchCaller.sol create mode 100644 contracts/mocks/token/ERC20GetterHelper.sol create mode 100644 contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol create mode 100644 test/token/ERC20/extensions/draft-ERC20TemporaryApproval.test.js diff --git a/.changeset/serious-carrots-provide.md b/.changeset/serious-carrots-provide.md new file mode 100644 index 00000000000..60a16580d8a --- /dev/null +++ b/.changeset/serious-carrots-provide.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC20TemporaryApproval`: Add an ERC-20 extension that implements temporary approval using transient storage, based on ERC7674 (draft). diff --git a/contracts/interfaces/README.adoc b/contracts/interfaces/README.adoc index 379a24a1e26..61aae05d167 100644 --- a/contracts/interfaces/README.adoc +++ b/contracts/interfaces/README.adoc @@ -40,6 +40,7 @@ are useful to interact with third party contracts that implement them. - {IERC5313} - {IERC5805} - {IERC6372} +- {IERC7674} == Detailed ABI @@ -80,3 +81,5 @@ are useful to interact with third party contracts that implement them. {{IERC5805}} {{IERC6372}} + +{{IERC7674}} diff --git a/contracts/interfaces/draft-IERC7674.sol b/contracts/interfaces/draft-IERC7674.sol new file mode 100644 index 00000000000..949ec806e84 --- /dev/null +++ b/contracts/interfaces/draft-IERC7674.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; + +/** + * @dev Temporary Approval Extension for ERC-20 (https://github.com/ethereum/ERCs/pull/358[ERC-7674]) + */ +interface IERC7674 is IERC20 { + /** + * @dev Set the temporary allowance, allowing `spender` to withdraw (within the same transaction) assets + * held by the caller. + */ + function temporaryApprove(address spender, uint256 value) external returns (bool success); +} diff --git a/contracts/mocks/BatchCaller.sol b/contracts/mocks/BatchCaller.sol new file mode 100644 index 00000000000..740691ba4f0 --- /dev/null +++ b/contracts/mocks/BatchCaller.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Address} from "../utils/Address.sol"; + +contract BatchCaller { + struct Call { + address target; + uint256 value; + bytes data; + } + + function execute(Call[] calldata calls) external returns (bytes[] memory) { + bytes[] memory returndata = new bytes[](calls.length); + for (uint256 i = 0; i < calls.length; ++i) { + returndata[i] = Address.functionCallWithValue(calls[i].target, calls[i].data, calls[i].value); + } + return returndata; + } +} diff --git a/contracts/mocks/token/ERC20GetterHelper.sol b/contracts/mocks/token/ERC20GetterHelper.sol new file mode 100644 index 00000000000..acdcced9fa1 --- /dev/null +++ b/contracts/mocks/token/ERC20GetterHelper.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20} from "../../token/ERC20/IERC20.sol"; +import {IERC20Metadata} from "../../token/ERC20/extensions/IERC20Metadata.sol"; + +contract ERC20GetterHelper { + event ERC20TotalSupply(IERC20 token, uint256 totalSupply); + event ERC20BalanceOf(IERC20 token, address account, uint256 balanceOf); + event ERC20Allowance(IERC20 token, address owner, address spender, uint256 allowance); + event ERC20Name(IERC20Metadata token, string name); + event ERC20Symbol(IERC20Metadata token, string symbol); + event ERC20Decimals(IERC20Metadata token, uint8 decimals); + + function totalSupply(IERC20 token) external { + emit ERC20TotalSupply(token, token.totalSupply()); + } + + function balanceOf(IERC20 token, address account) external { + emit ERC20BalanceOf(token, account, token.balanceOf(account)); + } + + function allowance(IERC20 token, address owner, address spender) external { + emit ERC20Allowance(token, owner, spender, token.allowance(owner, spender)); + } + + function name(IERC20Metadata token) external { + emit ERC20Name(token, token.name()); + } + + function symbol(IERC20Metadata token) external { + emit ERC20Symbol(token, token.symbol()); + } + + function decimals(IERC20Metadata token) external { + emit ERC20Decimals(token, token.decimals()); + } +} diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index 938784ff996..faaacc57667 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -22,6 +22,7 @@ Additionally there are multiple custom extensions, including: * {ERC20FlashMint}: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC-3156). * {ERC20Votes}: support for voting and vote delegation. * {ERC20Wrapper}: wrapper to create an ERC-20 backed by another ERC-20, with deposit and withdraw methods. Useful in conjunction with {ERC20Votes}. +* {ERC20TemporaryApproval}: support for approvals lasting for only one transaction, as defined in ERC-7674. * {ERC1363}: support for calling the target of a transfer or approval, enabling code execution on the receiver within a single transaction. * {ERC4626}: tokenized vault that manages shares (represented as ERC-20) that are backed by assets (another ERC-20). @@ -61,6 +62,8 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel {{ERC20FlashMint}} +{{ERC20TemporaryApproval}} + {{ERC1363}} {{ERC4626}} diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol new file mode 100644 index 00000000000..74da55758cb --- /dev/null +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20, ERC20} from "../ERC20.sol"; +import {IERC7674} from "../../../interfaces/draft-IERC7674.sol"; +import {Math} from "../../../utils/math/Math.sol"; +import {SlotDerivation} from "../../../utils/SlotDerivation.sol"; +import {StorageSlot} from "../../../utils/StorageSlot.sol"; + +/** + * @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674. + * + * WARNING: This is a draft contract. The corresponding ERC is still subject to changes. + */ +abstract contract ERC20TemporaryApproval is ERC20, IERC7674 { + using SlotDerivation for bytes32; + using StorageSlot for bytes32; + using StorageSlot for StorageSlot.Uint256SlotType; + + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE = + 0xea2d0e77a01400d0111492b1321103eed560d8fe44b9a7c2410407714583c400; + + /** + * @dev {allowance} override that includes the temporary allowance when looking up the current allowance. If + * adding up the persistent and the temporary allowances result in an overflow, type(uint256).max is returned. + */ + function allowance(address owner, address spender) public view virtual override(IERC20, ERC20) returns (uint256) { + (bool success, uint256 amount) = Math.tryAdd( + super.allowance(owner, spender), + _temporaryAllowance(owner, spender) + ); + return success ? amount : type(uint256).max; + } + + /** + * @dev Internal getter for the current temporary allowance that `spender` has over `owner` tokens. + */ + function _temporaryAllowance(address owner, address spender) internal view virtual returns (uint256) { + return _temporaryAllowanceSlot(owner, spender).tload(); + } + + /** + * @dev Alternative to {approve} that sets a `value` amount of tokens as the temporary allowance of `spender` over + * the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Requirements: + * - `spender` cannot be the zero address. + * + * Does NOT emit an {Approval} event. + */ + function temporaryApprove(address spender, uint256 value) public virtual returns (bool) { + _temporaryApprove(_msgSender(), spender, value); + return true; + } + + /** + * @dev Sets `value` as the temporary allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `temporaryApprove`, and can be used to e.g. set automatic allowances + * for certain subsystems, etc. + * + * Requirements: + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + * + * Does NOT emit an {Approval} event. + */ + function _temporaryApprove(address owner, address spender, uint256 value) internal virtual { + if (owner == address(0)) { + revert ERC20InvalidApprover(address(0)); + } + if (spender == address(0)) { + revert ERC20InvalidSpender(address(0)); + } + _temporaryAllowanceSlot(owner, spender).tstore(value); + } + + /** + * @dev {_spendAllowance} override that consumes the temporary allowance (if any) before eventually falling back + * to consuming the persistent allowance. + * NOTE: This function skips calling `super._spendAllowance` if the temporary allowance + * is enough to cover the spending. + */ + function _spendAllowance(address owner, address spender, uint256 value) internal virtual override { + // load transient allowance + uint256 currentTemporaryAllowance = _temporaryAllowance(owner, spender); + + // Check and update (if needed) the temporary allowance + set remaining value + if (currentTemporaryAllowance > 0) { + // All value is covered by the infinite allowance. nothing left to spend, we can return early + if (currentTemporaryAllowance == type(uint256).max) { + return; + } + // check how much of the value is covered by the transient allowance + uint256 spendTemporaryAllowance = Math.min(currentTemporaryAllowance, value); + unchecked { + // decrease transient allowance accordingly + _temporaryApprove(owner, spender, currentTemporaryAllowance - spendTemporaryAllowance); + // update value necessary + value -= spendTemporaryAllowance; + } + } + // reduce any remaining value from the persistent allowance + if (value > 0) { + super._spendAllowance(owner, spender, value); + } + } + + function _temporaryAllowanceSlot( + address owner, + address spender + ) private pure returns (StorageSlot.Uint256SlotType) { + return ERC20_TEMPORARY_APPROVAL_STORAGE.deriveMapping(owner).deriveMapping(spender).asUint256(); + } +} diff --git a/test/token/ERC20/ERC20.behavior.js b/test/token/ERC20/ERC20.behavior.js index 6754bff336a..748df4b85ae 100644 --- a/test/token/ERC20/ERC20.behavior.js +++ b/test/token/ERC20/ERC20.behavior.js @@ -132,9 +132,18 @@ function shouldBehaveLikeERC20(initialSupply, opts = {}) { }); it('reverts when the token owner is the zero address', async function () { + // transferFrom does a spendAllowance before moving the assets + // - default behavior (ERC20) is to always update the approval using `_approve`. This will fail because the + // approver (owner) is address(0). This happens even if the amount transferred is zero, and the approval update + // is not actually necessary. + // - in ERC20TemporaryAllowance, transfer of 0 value will not update allowance (temporary or persistent) + // therefore the spendAllowance does not revert. However, the transfer of asset will revert because the sender + // is address(0) + const errorName = this.token.temporaryApprove ? 'ERC20InvalidSender' : 'ERC20InvalidApprover'; + const value = 0n; await expect(this.token.connect(this.recipient).transferFrom(ethers.ZeroAddress, this.recipient, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidApprover') + .to.be.revertedWithCustomError(this.token, errorName) .withArgs(ethers.ZeroAddress); }); }); diff --git a/test/token/ERC20/extensions/draft-ERC20TemporaryApproval.test.js b/test/token/ERC20/extensions/draft-ERC20TemporaryApproval.test.js new file mode 100644 index 00000000000..a1f6362add6 --- /dev/null +++ b/test/token/ERC20/extensions/draft-ERC20TemporaryApproval.test.js @@ -0,0 +1,142 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { max, min } = require('../../../helpers/math.js'); + +const { shouldBehaveLikeERC20 } = require('../ERC20.behavior.js'); + +const name = 'My Token'; +const symbol = 'MTKN'; +const initialSupply = 100n; + +async function fixture() { + // this.accounts is used by shouldBehaveLikeERC20 + const accounts = await ethers.getSigners(); + const [holder, recipient, other] = accounts; + + const token = await ethers.deployContract('$ERC20TemporaryApproval', [name, symbol]); + await token.$_mint(holder, initialSupply); + + const spender = await ethers.deployContract('$Address'); + const batch = await ethers.deployContract('BatchCaller'); + const getter = await ethers.deployContract('ERC20GetterHelper'); + + return { accounts, holder, recipient, other, token, spender, batch, getter }; +} + +describe('ERC20TemporaryApproval', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeERC20(initialSupply); + + describe('setting and spending temporary allowance', function () { + beforeEach(async function () { + await this.token.connect(this.holder).transfer(this.batch, initialSupply); + }); + + for (let { + description, + persistentAllowance, + temporaryAllowance, + amount, + temporaryExpected, + persistentExpected, + } of [ + { description: 'can set temporary allowance', temporaryAllowance: 42n }, + { + description: 'can set temporary allowance on top of persistent allowance', + temporaryAllowance: 42n, + persistentAllowance: 17n, + }, + { description: 'support allowance overflow', temporaryAllowance: ethers.MaxUint256, persistentAllowance: 17n }, + { description: 'consuming temporary allowance alone', temporaryAllowance: 42n, amount: 2n }, + { + description: 'fallback to persistent allowance if temporary allowance is not sufficient', + temporaryAllowance: 42n, + persistentAllowance: 17n, + amount: 50n, + }, + { + description: 'do not reduce infinite temporary allowance #1', + temporaryAllowance: ethers.MaxUint256, + amount: 50n, + temporaryExpected: ethers.MaxUint256, + }, + { + description: 'do not reduce infinite temporary allowance #2', + temporaryAllowance: 17n, + persistentAllowance: ethers.MaxUint256, + amount: 50n, + temporaryExpected: ethers.MaxUint256, + persistentExpected: ethers.MaxUint256, + }, + ]) { + persistentAllowance ??= 0n; + temporaryAllowance ??= 0n; + amount ??= 0n; + temporaryExpected ??= min(persistentAllowance + temporaryAllowance - amount, ethers.MaxUint256); + persistentExpected ??= persistentAllowance - max(amount - temporaryAllowance, 0n); + + it(description, async function () { + await expect( + this.batch.execute( + [ + persistentAllowance && { + target: this.token, + value: 0n, + data: this.token.interface.encodeFunctionData('approve', [this.spender.target, persistentAllowance]), + }, + temporaryAllowance && { + target: this.token, + value: 0n, + data: this.token.interface.encodeFunctionData('temporaryApprove', [ + this.spender.target, + temporaryAllowance, + ]), + }, + amount && { + target: this.spender, + value: 0n, + data: this.spender.interface.encodeFunctionData('$functionCall', [ + this.token.target, + this.token.interface.encodeFunctionData('transferFrom', [ + this.batch.target, + this.recipient.address, + amount, + ]), + ]), + }, + { + target: this.getter, + value: 0n, + data: this.getter.interface.encodeFunctionData('allowance', [ + this.token.target, + this.batch.target, + this.spender.target, + ]), + }, + ].filter(Boolean), + ), + ) + .to.emit(this.getter, 'ERC20Allowance') + .withArgs(this.token, this.batch, this.spender, temporaryExpected); + + expect(await this.token.allowance(this.batch, this.spender)).to.equal(persistentExpected); + }); + } + + it('reverts when the recipient is the zero address', async function () { + await expect(this.token.connect(this.holder).temporaryApprove(ethers.ZeroAddress, 1n)) + .to.be.revertedWithCustomError(this.token, 'ERC20InvalidSpender') + .withArgs(ethers.ZeroAddress); + }); + + it('reverts when the token owner is the zero address', async function () { + await expect(this.token.$_temporaryApprove(ethers.ZeroAddress, this.recipient, 1n)) + .to.be.revertedWithCustomError(this.token, 'ERC20InvalidApprover') + .withArgs(ethers.ZeroAddress); + }); + }); +}); From 9e73c4b58120c17135bf269945d0f148810bf7f1 Mon Sep 17 00:00:00 2001 From: "Marco @Paladin" <88670196+marcoatpaladin@users.noreply.github.com> Date: Tue, 23 Jul 2024 06:09:13 +0200 Subject: [PATCH 06/81] Typographic error in Math.sol comment fix (#5115) --- contracts/utils/math/Math.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index d5ae271339e..ff74f1358db 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -144,7 +144,7 @@ library Math { function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use - // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2²⁵⁶ + prod0. uint256 prod0 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product From 231fae33f065b1bef5042183ffd7c6b38a8cc0f1 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 23 Jul 2024 19:31:26 +0200 Subject: [PATCH 07/81] Add Binary heap structure (#5084) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: cairo --- .changeset/fluffy-buses-jump.md | 5 + .changeset/great-pianos-work.md | 5 + .githooks/pre-push | 1 + contracts/mocks/Stateless.sol | 1 + contracts/utils/Arrays.sol | 72 ++- contracts/utils/Comparators.sol | 13 + contracts/utils/README.adoc | 6 + contracts/utils/structs/Heap.sol | 578 ++++++++++++++++++++++++ docs/modules/ROOT/pages/utilities.adoc | 29 +- scripts/generate/run.js | 4 +- scripts/generate/templates/Arrays.js | 43 +- scripts/generate/templates/Heap.js | 328 ++++++++++++++ scripts/generate/templates/Heap.opts.js | 13 + scripts/generate/templates/Heap.t.js | 89 ++++ test/utils/structs/Heap.t.sol | 153 +++++++ test/utils/structs/Heap.test.js | 131 ++++++ 16 files changed, 1406 insertions(+), 65 deletions(-) create mode 100644 .changeset/fluffy-buses-jump.md create mode 100644 .changeset/great-pianos-work.md create mode 100644 contracts/utils/Comparators.sol create mode 100644 contracts/utils/structs/Heap.sol create mode 100644 scripts/generate/templates/Heap.js create mode 100644 scripts/generate/templates/Heap.opts.js create mode 100644 scripts/generate/templates/Heap.t.js create mode 100644 test/utils/structs/Heap.t.sol create mode 100644 test/utils/structs/Heap.test.js diff --git a/.changeset/fluffy-buses-jump.md b/.changeset/fluffy-buses-jump.md new file mode 100644 index 00000000000..0525a4d8e43 --- /dev/null +++ b/.changeset/fluffy-buses-jump.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Comparator`: A library of comparator functions, useful for customizing the behavior of the Heap structure. diff --git a/.changeset/great-pianos-work.md b/.changeset/great-pianos-work.md new file mode 100644 index 00000000000..da54483e47e --- /dev/null +++ b/.changeset/great-pianos-work.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Heap`: A data structure that implements a heap-based priority queue. diff --git a/.githooks/pre-push b/.githooks/pre-push index a51f3884a5d..f028ce58e0b 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -3,5 +3,6 @@ set -euo pipefail if [ "${CI:-"false"}" != "true" ]; then + npm run test:generation npm run lint fi diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 7f18d573fda..846c77d98e8 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -22,6 +22,7 @@ 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 {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol"; diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index d67ae90ba2b..fe54bafee7c 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; +import {Comparators} from "./Comparators.sol"; import {SlotDerivation} from "./SlotDerivation.sol"; import {StorageSlot} from "./StorageSlot.sol"; import {Math} from "./math/Math.sol"; @@ -16,7 +17,7 @@ library Arrays { using StorageSlot for bytes32; /** - * @dev Sort an array of bytes32 (in memory) following the provided comparator function. + * @dev Sort an array of uint256 (in memory) following the provided comparator function. * * This function does the sorting "in place", meaning that it overrides the input. The object is returned for * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. @@ -27,18 +28,18 @@ library Arrays { * consume more gas than is available in a block, leading to potential DoS. */ function sort( - bytes32[] memory array, - function(bytes32, bytes32) pure returns (bool) comp - ) internal pure returns (bytes32[] memory) { + uint256[] memory array, + function(uint256, uint256) pure returns (bool) comp + ) internal pure returns (uint256[] memory) { _quickSort(_begin(array), _end(array), comp); return array; } /** - * @dev Variant of {sort} that sorts an array of bytes32 in increasing order. + * @dev Variant of {sort} that sorts an array of uint256 in increasing order. */ - function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) { - sort(array, _defaultComp); + function sort(uint256[] memory array) internal pure returns (uint256[] memory) { + sort(array, Comparators.lt); return array; } @@ -57,7 +58,7 @@ library Arrays { address[] memory array, function(address, address) pure returns (bool) comp ) internal pure returns (address[] memory) { - sort(_castToBytes32Array(array), _castToBytes32Comp(comp)); + sort(_castToUint256Array(array), _castToUint256Comp(comp)); return array; } @@ -65,12 +66,12 @@ library Arrays { * @dev Variant of {sort} that sorts an array of address in increasing order. */ function sort(address[] memory array) internal pure returns (address[] memory) { - sort(_castToBytes32Array(array), _defaultComp); + sort(_castToUint256Array(array), Comparators.lt); return array; } /** - * @dev Sort an array of uint256 (in memory) following the provided comparator function. + * @dev Sort an array of bytes32 (in memory) following the provided comparator function. * * This function does the sorting "in place", meaning that it overrides the input. The object is returned for * convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. @@ -81,18 +82,18 @@ library Arrays { * consume more gas than is available in a block, leading to potential DoS. */ function sort( - uint256[] memory array, - function(uint256, uint256) pure returns (bool) comp - ) internal pure returns (uint256[] memory) { - sort(_castToBytes32Array(array), _castToBytes32Comp(comp)); + bytes32[] memory array, + function(bytes32, bytes32) pure returns (bool) comp + ) internal pure returns (bytes32[] memory) { + sort(_castToUint256Array(array), _castToUint256Comp(comp)); return array; } /** - * @dev Variant of {sort} that sorts an array of uint256 in increasing order. + * @dev Variant of {sort} that sorts an array of bytes32 in increasing order. */ - function sort(uint256[] memory array) internal pure returns (uint256[] memory) { - sort(_castToBytes32Array(array), _defaultComp); + function sort(bytes32[] memory array) internal pure returns (bytes32[] memory) { + sort(_castToUint256Array(array), Comparators.lt); return array; } @@ -105,12 +106,12 @@ library Arrays { * IMPORTANT: Memory locations between `begin` and `end` are not validated/zeroed. This function should * be used only if the limits are within a memory array. */ - function _quickSort(uint256 begin, uint256 end, function(bytes32, bytes32) pure returns (bool) comp) private pure { + function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure { unchecked { if (end - begin < 0x40) return; // Use first element as pivot - bytes32 pivot = _mload(begin); + uint256 pivot = _mload(begin); // Position where the pivot should be at the end of the loop uint256 pos = begin; @@ -132,7 +133,7 @@ library Arrays { /** * @dev Pointer to the memory location of the first element of `array`. */ - function _begin(bytes32[] memory array) private pure returns (uint256 ptr) { + function _begin(uint256[] memory array) private pure returns (uint256 ptr) { /// @solidity memory-safe-assembly assembly { ptr := add(array, 0x20) @@ -143,16 +144,16 @@ library Arrays { * @dev Pointer to the memory location of the first memory word (32bytes) after `array`. This is the memory word * that comes just after the last element of the array. */ - function _end(bytes32[] memory array) private pure returns (uint256 ptr) { + function _end(uint256[] memory array) private pure returns (uint256 ptr) { unchecked { return _begin(array) + array.length * 0x20; } } /** - * @dev Load memory word (as a bytes32) at location `ptr`. + * @dev Load memory word (as a uint256) at location `ptr`. */ - function _mload(uint256 ptr) private pure returns (bytes32 value) { + function _mload(uint256 ptr) private pure returns (uint256 value) { assembly { value := mload(ptr) } @@ -170,38 +171,33 @@ library Arrays { } } - /// @dev Comparator for sorting arrays in increasing order. - function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) { - return a < b; - } - /// @dev Helper: low level cast address memory array to uint256 memory array - function _castToBytes32Array(address[] memory input) private pure returns (bytes32[] memory output) { + function _castToUint256Array(address[] memory input) private pure returns (uint256[] memory output) { assembly { output := input } } - /// @dev Helper: low level cast uint256 memory array to uint256 memory array - function _castToBytes32Array(uint256[] memory input) private pure returns (bytes32[] memory output) { + /// @dev Helper: low level cast bytes32 memory array to uint256 memory array + function _castToUint256Array(bytes32[] memory input) private pure returns (uint256[] memory output) { assembly { output := input } } - /// @dev Helper: low level cast address comp function to bytes32 comp function - function _castToBytes32Comp( + /// @dev Helper: low level cast address comp function to uint256 comp function + function _castToUint256Comp( function(address, address) pure returns (bool) input - ) private pure returns (function(bytes32, bytes32) pure returns (bool) output) { + ) private pure returns (function(uint256, uint256) pure returns (bool) output) { assembly { output := input } } - /// @dev Helper: low level cast uint256 comp function to bytes32 comp function - function _castToBytes32Comp( - function(uint256, uint256) pure returns (bool) input - ) private pure returns (function(bytes32, bytes32) pure returns (bool) output) { + /// @dev Helper: low level cast bytes32 comp function to uint256 comp function + function _castToUint256Comp( + function(bytes32, bytes32) pure returns (bool) input + ) private pure returns (function(uint256, uint256) pure returns (bool) output) { assembly { output := input } diff --git a/contracts/utils/Comparators.sol b/contracts/utils/Comparators.sol new file mode 100644 index 00000000000..3a63aa0e8ee --- /dev/null +++ b/contracts/utils/Comparators.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +library Comparators { + function lt(uint256 a, uint256 b) internal pure returns (bool) { + return a < b; + } + + function gt(uint256 a, uint256 b) internal pure returns (bool) { + return a > b; + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 71c39dfa9c5..da9eae6a887 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -25,6 +25,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {DoubleEndedQueue}: An implementation of a https://en.wikipedia.org/wiki/Double-ended_queue[double ended queue] whose values can be removed added or remove from both sides. Useful for FIFO and LIFO structures. * {CircularBuffer}: A data structure to store the last N values pushed to it. * {Checkpoints}: A data structure to store values mapped to an strictly increasing key. Can be used for storing and accessing values over time. + * {Heap}: A library that implements a https://en.wikipedia.org/wiki/Binary_heap[binary heap] in storage. * {MerkleTree}: A library with https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] data structures and helper functions. * {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly. * {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type. @@ -38,6 +39,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Context}: An utility for abstracting the sender and calldata in the current execution context. * {Packing}: A library for packing and unpacking multiple values into bytes32 * {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes]. + * {Comparators}: A library that contains comparator functions to use with with the {Heap} library. [NOTE] ==== @@ -102,6 +104,8 @@ Ethereum contracts have no native concept of an interface, so applications must {{Checkpoints}} +{{Heap}} + {{MerkleTree}} == Libraries @@ -129,3 +133,5 @@ Ethereum contracts have no native concept of an interface, so applications must {{Packing}} {{Panic}} + +{{Comparators}} diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol new file mode 100644 index 00000000000..ad684d40bdb --- /dev/null +++ b/contracts/utils/structs/Heap.sol @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/Heap.js. + +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; +import {SafeCast} from "../math/SafeCast.sol"; +import {Comparators} from "../Comparators.sol"; +import {Panic} from "../Panic.sol"; + +/** + * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as + * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. + * + * Heaps are represented as an array of Node objects. This array stores two overlapping structures: + * * A tree structure where the first element (index 0) is the root, and where the node at index i is the child of the + * node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node stores the index (in the array) + * where the corresponding value is stored. + * * A list of payloads values where each index contains a value and a lookup index. The type of the value depends on + * the variant being used. The lookup is the index of the node (in the tree) that points to this value. + * + * Some invariants: + * ``` + * i == heap.data[heap.data[i].index].lookup // for all indices i + * i == heap.data[heap.data[i].lookup].index // for all indices i + * ``` + * + * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the + * highest priority value is the one at the root. This value can be lookup up in constant time (O(1)) at + * `heap.data[heap.data[0].index].value` + * + * The structure is designed to perform the following operations with the corresponding complexities: + * + * * peek (get the highest priority in set): O(1) + * * insert (insert a value in the set): 0(log(n)) + * * pop (remove the highest priority value in set): O(log(n)) + * * replace (replace the highest priority value in set with a new value): O(log(n)) + * * length (get the number of elements in the set): O(1) + * * clear (remove all elements in the set): O(1) + */ +library Heap { + using Math for *; + using SafeCast for *; + + /** + * @dev Binary heap that support values of type uint256. + * + * Each element of that structures uses 2 storage slots. + */ + struct Uint256Heap { + Uint256HeapNode[] data; + } + + /** + * @dev Internal node type for Uint256Heap. Stores a value of type uint256. + */ + struct Uint256HeapNode { + uint256 value; + uint64 index; // position -> value + uint64 lookup; // value -> position + } + + /** + * @dev Lookup the root element of the heap. + */ + function peek(Uint256Heap storage self) internal view returns (uint256) { + // self.data[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. + return _unsafeNodeAccess(self, self.data[0].index).value; + } + + /** + * @dev Remove (and return) the root element for the heap using the default comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function pop(Uint256Heap storage self) internal returns (uint256) { + return pop(self, Comparators.lt); + } + + /** + * @dev Remove (and return) the root element for the heap using the provided comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function pop( + Uint256Heap storage self, + function(uint256, uint256) view returns (bool) comp + ) internal returns (uint256) { + unchecked { + uint64 size = length(self); + if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); + + uint64 last = size - 1; + + // get root location (in the data array) and value + Uint256HeapNode storage rootNode = _unsafeNodeAccess(self, 0); + uint64 rootIdx = rootNode.index; + Uint256HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); + Uint256HeapNode storage lastNode = _unsafeNodeAccess(self, last); + uint256 rootDataValue = rootData.value; + + // if root is not the last element of the data array (that will get pop-ed), reorder the data array. + if (rootIdx != last) { + // get details about the value stored in the last element of the array (that will get pop-ed) + uint64 lastDataIdx = lastNode.lookup; + uint256 lastDataValue = lastNode.value; + // copy these values to the location of the root (that is safe, and that we no longer use) + rootData.value = lastDataValue; + rootData.lookup = lastDataIdx; + // update the tree node that used to point to that last element (value now located where the root was) + _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; + } + + // get last leaf location (in the data array) and value + uint64 lastIdx = lastNode.index; + uint256 lastValue = _unsafeNodeAccess(self, lastIdx).value; + + // move the last leaf to the root, pop last leaf ... + rootNode.index = lastIdx; + _unsafeNodeAccess(self, lastIdx).lookup = 0; + self.data.pop(); + + // ... and heapify + _siftDown(self, last, 0, lastValue, comp); + + // return root value + return rootDataValue; + } + } + + /** + * @dev Insert a new element in the heap using the default comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function insert(Uint256Heap storage self, uint256 value) internal { + insert(self, value, Comparators.lt); + } + + /** + * @dev Insert a new element in the heap using the provided comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function insert( + Uint256Heap storage self, + uint256 value, + function(uint256, uint256) view returns (bool) comp + ) internal { + uint64 size = length(self); + if (size == type(uint64).max) Panic.panic(Panic.RESOURCE_ERROR); + + self.data.push(Uint256HeapNode({index: size, lookup: size, value: value})); + _siftUp(self, size, value, comp); + } + + /** + * @dev Return the root element for the heap, and replace it with a new value, using the default comparator. + * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function replace(Uint256Heap storage self, uint256 newValue) internal returns (uint256) { + return replace(self, newValue, Comparators.lt); + } + + /** + * @dev Return the root element for the heap, and replace it with a new value, using the provided comparator. + * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function replace( + Uint256Heap storage self, + uint256 newValue, + function(uint256, uint256) view returns (bool) comp + ) internal returns (uint256) { + uint64 size = length(self); + if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); + + // position of the node that holds the data for the root + uint64 rootIdx = _unsafeNodeAccess(self, 0).index; + // storage pointer to the node that holds the data for the root + Uint256HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); + + // cache old value and replace it + uint256 oldValue = rootData.value; + rootData.value = newValue; + + // re-heapify + _siftDown(self, size, 0, newValue, comp); + + // return old root value + return oldValue; + } + + /** + * @dev Returns the number of elements in the heap. + */ + function length(Uint256Heap storage self) internal view returns (uint64) { + return self.data.length.toUint64(); + } + + /** + * @dev Removes all elements in the heap. + */ + function clear(Uint256Heap storage self) internal { + Uint256HeapNode[] storage data = self.data; + /// @solidity memory-safe-assembly + assembly { + sstore(data.slot, 0) + } + } + + /* + * @dev Swap node `i` and `j` in the tree. + */ + function _swap(Uint256Heap storage self, uint64 i, uint64 j) private { + Uint256HeapNode storage ni = _unsafeNodeAccess(self, i); + Uint256HeapNode storage nj = _unsafeNodeAccess(self, j); + uint64 ii = ni.index; + uint64 jj = nj.index; + // update pointers to the data (swap the value) + ni.index = jj; + nj.index = ii; + // update lookup pointers for consistency + _unsafeNodeAccess(self, ii).lookup = j; + _unsafeNodeAccess(self, jj).lookup = i; + } + + /** + * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * comparator, and moving toward the leafs of the underlying tree. + * + * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` + * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These + * parameters are not verified. It is the caller role to make sure the parameters are correct. + */ + function _siftDown( + Uint256Heap storage self, + uint64 size, + uint64 pos, + uint256 value, + function(uint256, uint256) view returns (bool) comp + ) private { + uint256 left = 2 * pos + 1; // this could overflow uint64 + uint256 right = 2 * pos + 2; // this could overflow uint64 + + if (right < size) { + // the check guarantees that `left` and `right` are both valid uint32 + uint64 lIndex = uint64(left); + uint64 rIndex = uint64(right); + uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + uint256 rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; + if (comp(lValue, value) || comp(rValue, value)) { + uint64 index = uint64(comp(lValue, rValue).ternary(lIndex, rIndex)); + _swap(self, pos, index); + _siftDown(self, size, index, value, comp); + } + } else if (left < size) { + // the check guarantees that `left` is a valid uint32 + uint64 lIndex = uint64(left); + uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + if (comp(lValue, value)) { + _swap(self, pos, lIndex); + _siftDown(self, size, lIndex, value, comp); + } + } + } + + /** + * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * comparator, and moving toward the root of the underlying tree. + * + * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` + * could be extracted from `self` and `pos`, but that would require redundant storage read. This parameters is not + * verified. It is the caller role to make sure the parameters are correct. + */ + function _siftUp( + Uint256Heap storage self, + uint64 pos, + uint256 value, + function(uint256, uint256) view returns (bool) comp + ) private { + unchecked { + while (pos > 0) { + uint64 parent = (pos - 1) / 2; + uint256 parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; + if (comp(parentValue, value)) break; + _swap(self, pos, parent); + pos = parent; + } + } + } + + function _unsafeNodeAccess( + Uint256Heap storage self, + uint64 pos + ) private pure returns (Uint256HeapNode storage result) { + assembly ("memory-safe") { + mstore(0x00, self.slot) + result.slot := add(keccak256(0x00, 0x20), mul(pos, 2)) + } + } + + /** + * @dev Binary heap that support values of type uint208. + * + * Each element of that structures uses 1 storage slots. + */ + struct Uint208Heap { + Uint208HeapNode[] data; + } + + /** + * @dev Internal node type for Uint208Heap. Stores a value of type uint208. + */ + struct Uint208HeapNode { + uint208 value; + uint24 index; // position -> value + uint24 lookup; // value -> position + } + + /** + * @dev Lookup the root element of the heap. + */ + function peek(Uint208Heap storage self) internal view returns (uint208) { + // self.data[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. + return _unsafeNodeAccess(self, self.data[0].index).value; + } + + /** + * @dev Remove (and return) the root element for the heap using the default comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function pop(Uint208Heap storage self) internal returns (uint208) { + return pop(self, Comparators.lt); + } + + /** + * @dev Remove (and return) the root element for the heap using the provided comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function pop( + Uint208Heap storage self, + function(uint256, uint256) view returns (bool) comp + ) internal returns (uint208) { + unchecked { + uint24 size = length(self); + if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); + + uint24 last = size - 1; + + // get root location (in the data array) and value + Uint208HeapNode storage rootNode = _unsafeNodeAccess(self, 0); + uint24 rootIdx = rootNode.index; + Uint208HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); + Uint208HeapNode storage lastNode = _unsafeNodeAccess(self, last); + uint208 rootDataValue = rootData.value; + + // if root is not the last element of the data array (that will get pop-ed), reorder the data array. + if (rootIdx != last) { + // get details about the value stored in the last element of the array (that will get pop-ed) + uint24 lastDataIdx = lastNode.lookup; + uint208 lastDataValue = lastNode.value; + // copy these values to the location of the root (that is safe, and that we no longer use) + rootData.value = lastDataValue; + rootData.lookup = lastDataIdx; + // update the tree node that used to point to that last element (value now located where the root was) + _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; + } + + // get last leaf location (in the data array) and value + uint24 lastIdx = lastNode.index; + uint208 lastValue = _unsafeNodeAccess(self, lastIdx).value; + + // move the last leaf to the root, pop last leaf ... + rootNode.index = lastIdx; + _unsafeNodeAccess(self, lastIdx).lookup = 0; + self.data.pop(); + + // ... and heapify + _siftDown(self, last, 0, lastValue, comp); + + // return root value + return rootDataValue; + } + } + + /** + * @dev Insert a new element in the heap using the default comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function insert(Uint208Heap storage self, uint208 value) internal { + insert(self, value, Comparators.lt); + } + + /** + * @dev Insert a new element in the heap using the provided comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function insert( + Uint208Heap storage self, + uint208 value, + function(uint256, uint256) view returns (bool) comp + ) internal { + uint24 size = length(self); + if (size == type(uint24).max) Panic.panic(Panic.RESOURCE_ERROR); + + self.data.push(Uint208HeapNode({index: size, lookup: size, value: value})); + _siftUp(self, size, value, comp); + } + + /** + * @dev Return the root element for the heap, and replace it with a new value, using the default comparator. + * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function replace(Uint208Heap storage self, uint208 newValue) internal returns (uint208) { + return replace(self, newValue, Comparators.lt); + } + + /** + * @dev Return the root element for the heap, and replace it with a new value, using the provided comparator. + * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ + function replace( + Uint208Heap storage self, + uint208 newValue, + function(uint256, uint256) view returns (bool) comp + ) internal returns (uint208) { + uint24 size = length(self); + if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); + + // position of the node that holds the data for the root + uint24 rootIdx = _unsafeNodeAccess(self, 0).index; + // storage pointer to the node that holds the data for the root + Uint208HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); + + // cache old value and replace it + uint208 oldValue = rootData.value; + rootData.value = newValue; + + // re-heapify + _siftDown(self, size, 0, newValue, comp); + + // return old root value + return oldValue; + } + + /** + * @dev Returns the number of elements in the heap. + */ + function length(Uint208Heap storage self) internal view returns (uint24) { + return self.data.length.toUint24(); + } + + /** + * @dev Removes all elements in the heap. + */ + function clear(Uint208Heap storage self) internal { + Uint208HeapNode[] storage data = self.data; + /// @solidity memory-safe-assembly + assembly { + sstore(data.slot, 0) + } + } + + /* + * @dev Swap node `i` and `j` in the tree. + */ + function _swap(Uint208Heap storage self, uint24 i, uint24 j) private { + Uint208HeapNode storage ni = _unsafeNodeAccess(self, i); + Uint208HeapNode storage nj = _unsafeNodeAccess(self, j); + uint24 ii = ni.index; + uint24 jj = nj.index; + // update pointers to the data (swap the value) + ni.index = jj; + nj.index = ii; + // update lookup pointers for consistency + _unsafeNodeAccess(self, ii).lookup = j; + _unsafeNodeAccess(self, jj).lookup = i; + } + + /** + * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * comparator, and moving toward the leafs of the underlying tree. + * + * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` + * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These + * parameters are not verified. It is the caller role to make sure the parameters are correct. + */ + function _siftDown( + Uint208Heap storage self, + uint24 size, + uint24 pos, + uint208 value, + function(uint256, uint256) view returns (bool) comp + ) private { + uint256 left = 2 * pos + 1; // this could overflow uint24 + uint256 right = 2 * pos + 2; // this could overflow uint24 + + if (right < size) { + // the check guarantees that `left` and `right` are both valid uint32 + uint24 lIndex = uint24(left); + uint24 rIndex = uint24(right); + uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + uint208 rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; + if (comp(lValue, value) || comp(rValue, value)) { + uint24 index = uint24(comp(lValue, rValue).ternary(lIndex, rIndex)); + _swap(self, pos, index); + _siftDown(self, size, index, value, comp); + } + } else if (left < size) { + // the check guarantees that `left` is a valid uint32 + uint24 lIndex = uint24(left); + uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + if (comp(lValue, value)) { + _swap(self, pos, lIndex); + _siftDown(self, size, lIndex, value, comp); + } + } + } + + /** + * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * comparator, and moving toward the root of the underlying tree. + * + * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` + * could be extracted from `self` and `pos`, but that would require redundant storage read. This parameters is not + * verified. It is the caller role to make sure the parameters are correct. + */ + function _siftUp( + Uint208Heap storage self, + uint24 pos, + uint208 value, + function(uint256, uint256) view returns (bool) comp + ) private { + unchecked { + while (pos > 0) { + uint24 parent = (pos - 1) / 2; + uint208 parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; + if (comp(parentValue, value)) break; + _swap(self, pos, parent); + pos = parent; + } + } + } + + function _unsafeNodeAccess( + Uint208Heap storage self, + uint24 pos + ) private pure returns (Uint208HeapNode storage result) { + assembly ("memory-safe") { + mstore(0x00, self.slot) + result.slot := add(keccak256(0x00, 0x20), pos) + } + } +} diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 31d4d5e33ed..ecb950caf91 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -189,6 +189,7 @@ Some use cases require more powerful data structures than arrays and mappings of - xref:api:utils.adoc#EnumerableSet[`EnumerableSet`]: A https://en.wikipedia.org/wiki/Set_(abstract_data_type)[set] with enumeration capabilities. - xref:api:utils.adoc#EnumerableMap[`EnumerableMap`]: A `mapping` variant with enumeration capabilities. - xref:api:utils.adoc#MerkleTree[`MerkleTree`]: An on-chain https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] with helper functions. +- xref:api:utils.adoc#Heap.sol[`Heap`]: A The `Enumerable*` structures are similar to mappings in that they store and remove elements in constant time and don't allow for repeated entries, but they also support _enumeration_, which means you can easily query all stored entries both on and off-chain. @@ -240,6 +241,32 @@ function _hashFn(bytes32 a, bytes32 b) internal view returns(bytes32) { } ---- +=== Using a Heap + +A https://en.wikipedia.org/wiki/Binary_heap[binary heap] is a data structure that always store the most important element at its peak and it can be used as a priority queue. + +To define what is most important in a heap, these frequently take comparator functions that tell the binary heap whether a value has more relevance than another. + +OpenZeppelin Contracts implements a Heap data structure with the properties of a binary heap. The heap uses the xref:api:utils.adoc#Comparators-lt-uint256-uint256-[`lt`] function by default but allows to customize its comparator. + +When using a custom comparator, it's recommended to wrap your function to avoid the possibility of mistakenly using a different comparator function: + +[source,solidity] +---- +function pop(Uint256Heap storage self) internal returns (uint256) { + return pop(self, Comparators.gt); +} + +function insert(Uint256Heap storage self, uint256 value) internal { + insert(self, value, Comparators.gt); +} + +function replace(Uint256Heap storage self, uint256 newValue) internal returns (uint256) { + return replace(self, newValue, Comparators.gt); +} +---- + + [[misc]] == Misc @@ -292,7 +319,7 @@ function _setImplementation(address newImplementation) internal { } ---- -The xref:api:utils.adoc#StorageSlot[`StorageSlot`] library also supports transient storage through user defined value types (UDVTs[https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types]), which enables the same value types as in Solidity. +The xref:api:utils.adoc#StorageSlot[`StorageSlot`] library also supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity. [source,solidity] ---- diff --git a/scripts/generate/run.js b/scripts/generate/run.js index b3215ad1133..d49105c5a85 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -34,9 +34,10 @@ function generateFromTemplate(file, template, outputPrefix = '') { for (const [file, template] of Object.entries({ 'utils/cryptography/MerkleProof.sol': './templates/MerkleProof.js', 'utils/math/SafeCast.sol': './templates/SafeCast.js', + 'utils/structs/Checkpoints.sol': './templates/Checkpoints.js', 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', - 'utils/structs/Checkpoints.sol': './templates/Checkpoints.js', + 'utils/structs/Heap.sol': './templates/Heap.js', 'utils/SlotDerivation.sol': './templates/SlotDerivation.js', 'utils/StorageSlot.sol': './templates/StorageSlot.js', 'utils/Arrays.sol': './templates/Arrays.js', @@ -49,6 +50,7 @@ for (const [file, template] of Object.entries({ // Tests for (const [file, template] of Object.entries({ 'utils/structs/Checkpoints.t.sol': './templates/Checkpoints.t.js', + 'utils/structs/Heap.t.sol': './templates/Heap.t.js', 'utils/Packing.t.sol': './templates/Packing.t.js', 'utils/SlotDerivation.t.sol': './templates/SlotDerivation.t.js', })) { diff --git a/scripts/generate/templates/Arrays.js b/scripts/generate/templates/Arrays.js index 30a6e069aa6..9823e4e5d7b 100644 --- a/scripts/generate/templates/Arrays.js +++ b/scripts/generate/templates/Arrays.js @@ -5,6 +5,7 @@ const { TYPES } = require('./Arrays.opts'); const header = `\ pragma solidity ^0.8.20; +import {Comparators} from "./Comparators.sol"; import {SlotDerivation} from "./SlotDerivation.sol"; import {StorageSlot} from "./StorageSlot.sol"; import {Math} from "./math/Math.sol"; @@ -31,9 +32,9 @@ function sort( function(${type}, ${type}) pure returns (bool) comp ) internal pure returns (${type}[] memory) { ${ - type === 'bytes32' + type === 'uint256' ? '_quickSort(_begin(array), _end(array), comp);' - : 'sort(_castToBytes32Array(array), _castToBytes32Comp(comp));' + : 'sort(_castToUint256Array(array), _castToUint256Comp(comp));' } return array; } @@ -42,7 +43,7 @@ function sort( * @dev Variant of {sort} that sorts an array of ${type} in increasing order. */ function sort(${type}[] memory array) internal pure returns (${type}[] memory) { - ${type === 'bytes32' ? 'sort(array, _defaultComp);' : 'sort(_castToBytes32Array(array), _defaultComp);'} + ${type === 'uint256' ? 'sort(array, Comparators.lt);' : 'sort(_castToUint256Array(array), Comparators.lt);'} return array; } `; @@ -57,12 +58,12 @@ const quickSort = `\ * IMPORTANT: Memory locations between \`begin\` and \`end\` are not validated/zeroed. This function should * be used only if the limits are within a memory array. */ -function _quickSort(uint256 begin, uint256 end, function(bytes32, bytes32) pure returns (bool) comp) private pure { +function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure returns (bool) comp) private pure { unchecked { if (end - begin < 0x40) return; // Use first element as pivot - bytes32 pivot = _mload(begin); + uint256 pivot = _mload(begin); // Position where the pivot should be at the end of the loop uint256 pos = begin; @@ -84,7 +85,7 @@ function _quickSort(uint256 begin, uint256 end, function(bytes32, bytes32) pure /** * @dev Pointer to the memory location of the first element of \`array\`. */ -function _begin(bytes32[] memory array) private pure returns (uint256 ptr) { +function _begin(uint256[] memory array) private pure returns (uint256 ptr) { /// @solidity memory-safe-assembly assembly { ptr := add(array, 0x20) @@ -95,16 +96,16 @@ function _begin(bytes32[] memory array) private pure returns (uint256 ptr) { * @dev Pointer to the memory location of the first memory word (32bytes) after \`array\`. This is the memory word * that comes just after the last element of the array. */ -function _end(bytes32[] memory array) private pure returns (uint256 ptr) { +function _end(uint256[] memory array) private pure returns (uint256 ptr) { unchecked { return _begin(array) + array.length * 0x20; } } /** - * @dev Load memory word (as a bytes32) at location \`ptr\`. + * @dev Load memory word (as a uint256) at location \`ptr\`. */ -function _mload(uint256 ptr) private pure returns (bytes32 value) { +function _mload(uint256 ptr) private pure returns (uint256 value) { assembly { value := mload(ptr) } @@ -123,16 +124,9 @@ function _swap(uint256 ptr1, uint256 ptr2) private pure { } `; -const defaultComparator = `\ -/// @dev Comparator for sorting arrays in increasing order. -function _defaultComp(bytes32 a, bytes32 b) private pure returns (bool) { - return a < b; -} -`; - const castArray = type => `\ /// @dev Helper: low level cast ${type} memory array to uint256 memory array -function _castToBytes32Array(${type}[] memory input) private pure returns (bytes32[] memory output) { +function _castToUint256Array(${type}[] memory input) private pure returns (uint256[] memory output) { assembly { output := input } @@ -140,10 +134,10 @@ function _castToBytes32Array(${type}[] memory input) private pure returns (bytes `; const castComparator = type => `\ -/// @dev Helper: low level cast ${type} comp function to bytes32 comp function -function _castToBytes32Comp( +/// @dev Helper: low level cast ${type} comp function to uint256 comp function +function _castToUint256Comp( function(${type}, ${type}) pure returns (bool) input -) private pure returns (function(bytes32, bytes32) pure returns (bool) output) { +) private pure returns (function(uint256, uint256) pure returns (bool) output) { assembly { output := input } @@ -374,12 +368,11 @@ module.exports = format( 'using StorageSlot for bytes32;', '', // sorting, comparator, helpers and internal - sort('bytes32'), - TYPES.filter(type => type !== 'bytes32').map(sort), + sort('uint256'), + TYPES.filter(type => type !== 'uint256').map(sort), quickSort, - defaultComparator, - TYPES.filter(type => type !== 'bytes32').map(castArray), - TYPES.filter(type => type !== 'bytes32').map(castComparator), + TYPES.filter(type => type !== 'uint256').map(castArray), + TYPES.filter(type => type !== 'uint256').map(castComparator), // lookup search, // unsafe (direct) storage and memory access diff --git a/scripts/generate/templates/Heap.js b/scripts/generate/templates/Heap.js new file mode 100644 index 00000000000..7ed99939bb5 --- /dev/null +++ b/scripts/generate/templates/Heap.js @@ -0,0 +1,328 @@ +const format = require('../format-lines'); +const { TYPES } = require('./Heap.opts'); +const { capitalize } = require('../../helpers'); + +/* eslint-disable max-len */ +const header = `\ +pragma solidity ^0.8.20; + +import {Math} from "../math/Math.sol"; +import {SafeCast} from "../math/SafeCast.sol"; +import {Comparators} from "../Comparators.sol"; +import {Panic} from "../Panic.sol"; + +/** + * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as + * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. + * + * Heaps are represented as an array of Node objects. This array stores two overlapping structures: + * * A tree structure where the first element (index 0) is the root, and where the node at index i is the child of the + * node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node stores the index (in the array) + * where the corresponding value is stored. + * * A list of payloads values where each index contains a value and a lookup index. The type of the value depends on + * the variant being used. The lookup is the index of the node (in the tree) that points to this value. + * + * Some invariants: + * \`\`\` + * i == heap.data[heap.data[i].index].lookup // for all indices i + * i == heap.data[heap.data[i].lookup].index // for all indices i + * \`\`\` + * + * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the + * highest priority value is the one at the root. This value can be lookup up in constant time (O(1)) at + * \`heap.data[heap.data[0].index].value\` + * + * The structure is designed to perform the following operations with the corresponding complexities: + * + * * peek (get the highest priority in set): O(1) + * * insert (insert a value in the set): 0(log(n)) + * * pop (remove the highest priority value in set): O(log(n)) + * * replace (replace the highest priority value in set with a new value): O(log(n)) + * * length (get the number of elements in the set): O(1) + * * clear (remove all elements in the set): O(1) + */ +`; + +const generate = ({ struct, node, valueType, indexType, blockSize }) => `\ +/** + * @dev Binary heap that support values of type ${valueType}. + * + * Each element of that structures uses ${blockSize} storage slots. + */ +struct ${struct} { + ${node}[] data; +} + +/** + * @dev Internal node type for ${struct}. Stores a value of type ${valueType}. + */ +struct ${node} { + ${valueType} value; + ${indexType} index; // position -> value + ${indexType} lookup; // value -> position +} + +/** + * @dev Lookup the root element of the heap. + */ +function peek(${struct} storage self) internal view returns (${valueType}) { + // self.data[0] will \`ARRAY_ACCESS_OUT_OF_BOUNDS\` panic if heap is empty. + return _unsafeNodeAccess(self, self.data[0].index).value; +} + +/** + * @dev Remove (and return) the root element for the heap using the default comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ +function pop(${struct} storage self) internal returns (${valueType}) { + return pop(self, Comparators.lt); +} + +/** + * @dev Remove (and return) the root element for the heap using the provided comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ +function pop( + ${struct} storage self, + function(uint256, uint256) view returns (bool) comp +) internal returns (${valueType}) { + unchecked { + ${indexType} size = length(self); + if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); + + ${indexType} last = size - 1; + + // get root location (in the data array) and value + ${node} storage rootNode = _unsafeNodeAccess(self, 0); + ${indexType} rootIdx = rootNode.index; + ${node} storage rootData = _unsafeNodeAccess(self, rootIdx); + ${node} storage lastNode = _unsafeNodeAccess(self, last); + ${valueType} rootDataValue = rootData.value; + + // if root is not the last element of the data array (that will get pop-ed), reorder the data array. + if (rootIdx != last) { + // get details about the value stored in the last element of the array (that will get pop-ed) + ${indexType} lastDataIdx = lastNode.lookup; + ${valueType} lastDataValue = lastNode.value; + // copy these values to the location of the root (that is safe, and that we no longer use) + rootData.value = lastDataValue; + rootData.lookup = lastDataIdx; + // update the tree node that used to point to that last element (value now located where the root was) + _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; + } + + // get last leaf location (in the data array) and value + ${indexType} lastIdx = lastNode.index; + ${valueType} lastValue = _unsafeNodeAccess(self, lastIdx).value; + + // move the last leaf to the root, pop last leaf ... + rootNode.index = lastIdx; + _unsafeNodeAccess(self, lastIdx).lookup = 0; + self.data.pop(); + + // ... and heapify + _siftDown(self, last, 0, lastValue, comp); + + // return root value + return rootDataValue; + } +} + +/** + * @dev Insert a new element in the heap using the default comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ +function insert(${struct} storage self, ${valueType} value) internal { + insert(self, value, Comparators.lt); +} + +/** + * @dev Insert a new element in the heap using the provided comparator. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ +function insert( + ${struct} storage self, + ${valueType} value, + function(uint256, uint256) view returns (bool) comp +) internal { + ${indexType} size = length(self); + if (size == type(${indexType}).max) Panic.panic(Panic.RESOURCE_ERROR); + + self.data.push(${struct}Node({index: size, lookup: size, value: value})); + _siftUp(self, size, value, comp); +} + +/** + * @dev Return the root element for the heap, and replace it with a new value, using the default comparator. + * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ +function replace(${struct} storage self, ${valueType} newValue) internal returns (${valueType}) { + return replace(self, newValue, Comparators.lt); +} + +/** + * @dev Return the root element for the heap, and replace it with a new value, using the provided comparator. + * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. + * + * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator + * during the lifecycle of a heap will result in undefined behavior. + */ +function replace( + ${struct} storage self, + ${valueType} newValue, + function(uint256, uint256) view returns (bool) comp +) internal returns (${valueType}) { + ${indexType} size = length(self); + if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); + + // position of the node that holds the data for the root + ${indexType} rootIdx = _unsafeNodeAccess(self, 0).index; + // storage pointer to the node that holds the data for the root + ${node} storage rootData = _unsafeNodeAccess(self, rootIdx); + + // cache old value and replace it + ${valueType} oldValue = rootData.value; + rootData.value = newValue; + + // re-heapify + _siftDown(self, size, 0, newValue, comp); + + // return old root value + return oldValue; +} + +/** + * @dev Returns the number of elements in the heap. + */ +function length(${struct} storage self) internal view returns (${indexType}) { + return self.data.length.to${capitalize(indexType)}(); +} + +/** + * @dev Removes all elements in the heap. + */ +function clear(${struct} storage self) internal { + ${struct}Node[] storage data = self.data; + /// @solidity memory-safe-assembly + assembly { + sstore(data.slot, 0) + } +} + +/* + * @dev Swap node \`i\` and \`j\` in the tree. + */ +function _swap(${struct} storage self, ${indexType} i, ${indexType} j) private { + ${node} storage ni = _unsafeNodeAccess(self, i); + ${node} storage nj = _unsafeNodeAccess(self, j); + ${indexType} ii = ni.index; + ${indexType} jj = nj.index; + // update pointers to the data (swap the value) + ni.index = jj; + nj.index = ii; + // update lookup pointers for consistency + _unsafeNodeAccess(self, ii).lookup = j; + _unsafeNodeAccess(self, jj).lookup = i; +} + +/** + * @dev Perform heap maintenance on \`self\`, starting at position \`pos\` (with the \`value\`), using \`comp\` as a + * comparator, and moving toward the leafs of the underlying tree. + * + * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`length\` + * and \`value\` could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. These + * parameters are not verified. It is the caller role to make sure the parameters are correct. + */ +function _siftDown( + ${struct} storage self, + ${indexType} size, + ${indexType} pos, + ${valueType} value, + function(uint256, uint256) view returns (bool) comp +) private { + uint256 left = 2 * pos + 1; // this could overflow ${indexType} + uint256 right = 2 * pos + 2; // this could overflow ${indexType} + + if (right < size) { + // the check guarantees that \`left\` and \`right\` are both valid uint32 + ${indexType} lIndex = ${indexType}(left); + ${indexType} rIndex = ${indexType}(right); + ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + ${valueType} rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; + if (comp(lValue, value) || comp(rValue, value)) { + ${indexType} index = ${indexType}(comp(lValue, rValue).ternary(lIndex, rIndex)); + _swap(self, pos, index); + _siftDown(self, size, index, value, comp); + } + } else if (left < size) { + // the check guarantees that \`left\` is a valid uint32 + ${indexType} lIndex = ${indexType}(left); + ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + if (comp(lValue, value)) { + _swap(self, pos, lIndex); + _siftDown(self, size, lIndex, value, comp); + } + } +} + +/** + * @dev Perform heap maintenance on \`self\`, starting at position \`pos\` (with the \`value\`), using \`comp\` as a + * comparator, and moving toward the root of the underlying tree. + * + * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`value\` + * could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. This parameters is not + * verified. It is the caller role to make sure the parameters are correct. + */ +function _siftUp( + ${struct} storage self, + ${indexType} pos, + ${valueType} value, + function(uint256, uint256) view returns (bool) comp +) private { + unchecked { + while (pos > 0) { + ${indexType} parent = (pos - 1) / 2; + ${valueType} parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; + if (comp(parentValue, value)) break; + _swap(self, pos, parent); + pos = parent; + } + } +} + +function _unsafeNodeAccess( + ${struct} storage self, + ${indexType} pos +) private pure returns (${node} storage result) { + assembly ("memory-safe") { + mstore(0x00, self.slot) + result.slot := add(keccak256(0x00, 0x20), ${blockSize == 1 ? 'pos' : `mul(pos, ${blockSize})`}) + } +} +`; + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library Heap {', + format( + [].concat( + 'using Math for *;', + 'using SafeCast for *;', + '', + TYPES.map(type => generate(type)), + ), + ).trimEnd(), + '}', +); diff --git a/scripts/generate/templates/Heap.opts.js b/scripts/generate/templates/Heap.opts.js new file mode 100644 index 00000000000..8b8be0afdfa --- /dev/null +++ b/scripts/generate/templates/Heap.opts.js @@ -0,0 +1,13 @@ +const makeType = (valueSize, indexSize) => ({ + struct: `Uint${valueSize}Heap`, + node: `Uint${valueSize}HeapNode`, + valueSize, + valueType: `uint${valueSize}`, + indexSize, + indexType: `uint${indexSize}`, + blockSize: Math.ceil((valueSize + 2 * indexSize) / 256), +}); + +module.exports = { + TYPES: [makeType(256, 64), makeType(208, 24)], +}; diff --git a/scripts/generate/templates/Heap.t.js b/scripts/generate/templates/Heap.t.js new file mode 100644 index 00000000000..04b3152ba3a --- /dev/null +++ b/scripts/generate/templates/Heap.t.js @@ -0,0 +1,89 @@ +const format = require('../format-lines'); +const { TYPES } = require('./Heap.opts'); + +/* eslint-disable max-len */ +const header = `\ +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {Heap} from "@openzeppelin/contracts/utils/structs/Heap.sol"; +import {Comparators} from "@openzeppelin/contracts/utils/Comparators.sol"; +`; + +const generate = ({ struct, valueType }) => `\ +contract ${struct}Test is Test { + using Heap for Heap.${struct}; + + Heap.${struct} internal heap; + + function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { + for (uint32 i = 0; i < heap.length(); ++i) { + // lookups + assertEq(i, heap.data[heap.data[i].index].lookup); + assertEq(i, heap.data[heap.data[i].lookup].index); + + // ordering: each node has a value bigger then its parent + if (i > 0) + assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); + } + } + + function testFuzz(${valueType}[] calldata input) public { + vm.assume(input.length < 0x20); + assertEq(heap.length(), 0); + + uint256 min = type(uint256).max; + for (uint256 i = 0; i < input.length; ++i) { + heap.insert(input[i]); + assertEq(heap.length(), i + 1); + _validateHeap(Comparators.lt); + + min = Math.min(min, input[i]); + assertEq(heap.peek(), min); + } + + uint256 max = 0; + for (uint256 i = 0; i < input.length; ++i) { + ${valueType} top = heap.peek(); + ${valueType} pop = heap.pop(); + assertEq(heap.length(), input.length - i - 1); + _validateHeap(Comparators.lt); + + assertEq(pop, top); + assertGe(pop, max); + max = pop; + } + } + + function testFuzzGt(${valueType}[] calldata input) public { + vm.assume(input.length < 0x20); + assertEq(heap.length(), 0); + + uint256 max = 0; + for (uint256 i = 0; i < input.length; ++i) { + heap.insert(input[i], Comparators.gt); + assertEq(heap.length(), i + 1); + _validateHeap(Comparators.gt); + + max = Math.max(max, input[i]); + assertEq(heap.peek(), max); + } + + uint256 min = type(uint256).max; + for (uint256 i = 0; i < input.length; ++i) { + ${valueType} top = heap.peek(); + ${valueType} pop = heap.pop(Comparators.gt); + assertEq(heap.length(), input.length - i - 1); + _validateHeap(Comparators.gt); + + assertEq(pop, top); + assertLe(pop, min); + min = pop; + } + } +} +`; + +// GENERATE +module.exports = format(header, ...TYPES.map(type => generate(type))); diff --git a/test/utils/structs/Heap.t.sol b/test/utils/structs/Heap.t.sol new file mode 100644 index 00000000000..b9d0b98787c --- /dev/null +++ b/test/utils/structs/Heap.t.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/Heap.t.js. + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {Heap} from "@openzeppelin/contracts/utils/structs/Heap.sol"; +import {Comparators} from "@openzeppelin/contracts/utils/Comparators.sol"; + +contract Uint256HeapTest is Test { + using Heap for Heap.Uint256Heap; + + Heap.Uint256Heap internal heap; + + function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { + for (uint32 i = 0; i < heap.length(); ++i) { + // lookups + assertEq(i, heap.data[heap.data[i].index].lookup); + assertEq(i, heap.data[heap.data[i].lookup].index); + + // ordering: each node has a value bigger then its parent + if (i > 0) + assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); + } + } + + function testFuzz(uint256[] calldata input) public { + vm.assume(input.length < 0x20); + assertEq(heap.length(), 0); + + uint256 min = type(uint256).max; + for (uint256 i = 0; i < input.length; ++i) { + heap.insert(input[i]); + assertEq(heap.length(), i + 1); + _validateHeap(Comparators.lt); + + min = Math.min(min, input[i]); + assertEq(heap.peek(), min); + } + + uint256 max = 0; + for (uint256 i = 0; i < input.length; ++i) { + uint256 top = heap.peek(); + uint256 pop = heap.pop(); + assertEq(heap.length(), input.length - i - 1); + _validateHeap(Comparators.lt); + + assertEq(pop, top); + assertGe(pop, max); + max = pop; + } + } + + function testFuzzGt(uint256[] calldata input) public { + vm.assume(input.length < 0x20); + assertEq(heap.length(), 0); + + uint256 max = 0; + for (uint256 i = 0; i < input.length; ++i) { + heap.insert(input[i], Comparators.gt); + assertEq(heap.length(), i + 1); + _validateHeap(Comparators.gt); + + max = Math.max(max, input[i]); + assertEq(heap.peek(), max); + } + + uint256 min = type(uint256).max; + for (uint256 i = 0; i < input.length; ++i) { + uint256 top = heap.peek(); + uint256 pop = heap.pop(Comparators.gt); + assertEq(heap.length(), input.length - i - 1); + _validateHeap(Comparators.gt); + + assertEq(pop, top); + assertLe(pop, min); + min = pop; + } + } +} + +contract Uint208HeapTest is Test { + using Heap for Heap.Uint208Heap; + + Heap.Uint208Heap internal heap; + + function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { + for (uint32 i = 0; i < heap.length(); ++i) { + // lookups + assertEq(i, heap.data[heap.data[i].index].lookup); + assertEq(i, heap.data[heap.data[i].lookup].index); + + // ordering: each node has a value bigger then its parent + if (i > 0) + assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); + } + } + + function testFuzz(uint208[] calldata input) public { + vm.assume(input.length < 0x20); + assertEq(heap.length(), 0); + + uint256 min = type(uint256).max; + for (uint256 i = 0; i < input.length; ++i) { + heap.insert(input[i]); + assertEq(heap.length(), i + 1); + _validateHeap(Comparators.lt); + + min = Math.min(min, input[i]); + assertEq(heap.peek(), min); + } + + uint256 max = 0; + for (uint256 i = 0; i < input.length; ++i) { + uint208 top = heap.peek(); + uint208 pop = heap.pop(); + assertEq(heap.length(), input.length - i - 1); + _validateHeap(Comparators.lt); + + assertEq(pop, top); + assertGe(pop, max); + max = pop; + } + } + + function testFuzzGt(uint208[] calldata input) public { + vm.assume(input.length < 0x20); + assertEq(heap.length(), 0); + + uint256 max = 0; + for (uint256 i = 0; i < input.length; ++i) { + heap.insert(input[i], Comparators.gt); + assertEq(heap.length(), i + 1); + _validateHeap(Comparators.gt); + + max = Math.max(max, input[i]); + assertEq(heap.peek(), max); + } + + uint256 min = type(uint256).max; + for (uint256 i = 0; i < input.length; ++i) { + uint208 top = heap.peek(); + uint208 pop = heap.pop(Comparators.gt); + assertEq(heap.length(), input.length - i - 1); + _validateHeap(Comparators.gt); + + assertEq(pop, top); + assertLe(pop, min); + min = pop; + } + } +} diff --git a/test/utils/structs/Heap.test.js b/test/utils/structs/Heap.test.js new file mode 100644 index 00000000000..7e95a0e7adb --- /dev/null +++ b/test/utils/structs/Heap.test.js @@ -0,0 +1,131 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); + +const { TYPES } = require('../../../scripts/generate/templates/Heap.opts'); + +async function fixture() { + const mock = await ethers.deployContract('$Heap'); + return { mock }; +} + +describe('Heap', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + for (const { struct, valueType } of TYPES) { + describe(struct, function () { + const popEvent = `return$pop_Heap_${struct}`; + const replaceEvent = `return$replace_Heap_${struct}_${valueType}`; + + beforeEach(async function () { + this.helper = { + clear: (...args) => this.mock[`$clear_Heap_${struct}`](0, ...args), + insert: (...args) => this.mock[`$insert(uint256,${valueType})`](0, ...args), + replace: (...args) => this.mock[`$replace(uint256,${valueType})`](0, ...args), + length: (...args) => this.mock[`$length_Heap_${struct}`](0, ...args), + pop: (...args) => this.mock[`$pop_Heap_${struct}`](0, ...args), + peek: (...args) => this.mock[`$peek_Heap_${struct}`](0, ...args), + }; + }); + + it('starts empty', async function () { + expect(await this.helper.length()).to.equal(0n); + }); + + it('peek, pop and replace from empty', async function () { + await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + await expect(this.helper.replace(0n)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + }); + + it('clear', async function () { + await this.helper.insert(42n); + + expect(await this.helper.length()).to.equal(1n); + expect(await this.helper.peek()).to.equal(42n); + + await this.helper.clear(); + + expect(await this.helper.length()).to.equal(0n); + await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + }); + + it('support duplicated items', async function () { + expect(await this.helper.length()).to.equal(0n); + + // insert 5 times + await this.helper.insert(42n); + await this.helper.insert(42n); + await this.helper.insert(42n); + await this.helper.insert(42n); + await this.helper.insert(42n); + + // pop 5 times + await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); + await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); + await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); + await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); + await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); + + // popping a 6th time panics + await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + }); + + it('insert, pop and replace', async function () { + const heap = []; + for (const { op, value } of [ + { op: 'insert', value: 712 }, // [712] + { op: 'insert', value: 20 }, // [20, 712] + { op: 'insert', value: 4337 }, // [20, 712, 4437] + { op: 'pop' }, // 20, [712, 4437] + { op: 'insert', value: 1559 }, // [712, 1559, 4437] + { op: 'insert', value: 165 }, // [165, 712, 1559, 4437] + { op: 'insert', value: 155 }, // [155, 165, 712, 1559, 4437] + { op: 'insert', value: 7702 }, // [155, 165, 712, 1559, 4437, 7702] + { op: 'pop' }, // 155, [165, 712, 1559, 4437, 7702] + { op: 'replace', value: 721 }, // 165, [712, 721, 1559, 4437, 7702] + { op: 'pop' }, // 712, [721, 1559, 4437, 7702] + { op: 'pop' }, // 721, [1559, 4437, 7702] + { op: 'pop' }, // 1559, [4437, 7702] + { op: 'pop' }, // 4437, [7702] + { op: 'pop' }, // 7702, [] + { op: 'pop' }, // panic + { op: 'replace', value: '1363' }, // panic + ]) { + switch (op) { + case 'insert': + await this.helper.insert(value); + heap.push(value); + heap.sort((a, b) => a - b); + break; + case 'pop': + if (heap.length == 0) { + await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + } else { + await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(heap.shift()); + } + break; + case 'replace': + if (heap.length == 0) { + await expect(this.helper.replace(value)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + } else { + await expect(this.helper.replace(value)).to.emit(this.mock, replaceEvent).withArgs(heap.shift()); + heap.push(value); + heap.sort((a, b) => a - b); + } + break; + } + expect(await this.helper.length()).to.equal(heap.length); + if (heap.length == 0) { + await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + } else { + expect(await this.helper.peek()).to.equal(heap[0]); + } + } + }); + }); + } +}); From 659f3063f82422cef820de746444e6f6cba6ca7c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 24 Jul 2024 10:32:55 +0200 Subject: [PATCH 08/81] Fix typo in Heap.sol documentation (#5121) --- contracts/utils/structs/Heap.sol | 38 +++++++++++++++--------------- scripts/generate/templates/Heap.js | 26 ++++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index ad684d40bdb..2d2517ec567 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -26,17 +26,17 @@ import {Panic} from "../Panic.sol"; * ``` * * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the - * highest priority value is the one at the root. This value can be lookup up in constant time (O(1)) at + * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at * `heap.data[heap.data[0].index].value` * * The structure is designed to perform the following operations with the corresponding complexities: * - * * peek (get the highest priority in set): O(1) - * * insert (insert a value in the set): 0(log(n)) - * * pop (remove the highest priority value in set): O(log(n)) - * * replace (replace the highest priority value in set with a new value): O(log(n)) - * * length (get the number of elements in the set): O(1) - * * clear (remove all elements in the set): O(1) + * * peek (get the highest priority value): O(1) + * * insert (insert a value): O(log(n)) + * * pop (remove the highest priority value): O(log(n)) + * * replace (replace the highest priority value with a new value): O(log(n)) + * * length (get the number of elements): O(1) + * * clear (remove all elements): O(1) */ library Heap { using Math for *; @@ -45,7 +45,7 @@ library Heap { /** * @dev Binary heap that support values of type uint256. * - * Each element of that structures uses 2 storage slots. + * Each element of that structure uses 2 storage slots. */ struct Uint256Heap { Uint256HeapNode[] data; @@ -101,9 +101,9 @@ library Heap { Uint256HeapNode storage lastNode = _unsafeNodeAccess(self, last); uint256 rootDataValue = rootData.value; - // if root is not the last element of the data array (that will get pop-ed), reorder the data array. + // if root is not the last element of the data array (that will get popped), reorder the data array. if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get pop-ed) + // get details about the value stored in the last element of the array (that will get popped) uint64 lastDataIdx = lastNode.lookup; uint256 lastDataValue = lastNode.value; // copy these values to the location of the root (that is safe, and that we no longer use) @@ -253,7 +253,7 @@ library Heap { uint256 right = 2 * pos + 2; // this could overflow uint64 if (right < size) { - // the check guarantees that `left` and `right` are both valid uint32 + // the check guarantees that `left` and `right` are both valid uint64 uint64 lIndex = uint64(left); uint64 rIndex = uint64(right); uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; @@ -264,7 +264,7 @@ library Heap { _siftDown(self, size, index, value, comp); } } else if (left < size) { - // the check guarantees that `left` is a valid uint32 + // the check guarantees that `left` is a valid uint64 uint64 lIndex = uint64(left); uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; if (comp(lValue, value)) { @@ -279,7 +279,7 @@ library Heap { * comparator, and moving toward the root of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` - * could be extracted from `self` and `pos`, but that would require redundant storage read. This parameters is not + * could be extracted from `self` and `pos`, but that would require redundant storage read. These parameters are not * verified. It is the caller role to make sure the parameters are correct. */ function _siftUp( @@ -312,7 +312,7 @@ library Heap { /** * @dev Binary heap that support values of type uint208. * - * Each element of that structures uses 1 storage slots. + * Each element of that structure uses 1 storage slots. */ struct Uint208Heap { Uint208HeapNode[] data; @@ -368,9 +368,9 @@ library Heap { Uint208HeapNode storage lastNode = _unsafeNodeAccess(self, last); uint208 rootDataValue = rootData.value; - // if root is not the last element of the data array (that will get pop-ed), reorder the data array. + // if root is not the last element of the data array (that will get popped), reorder the data array. if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get pop-ed) + // get details about the value stored in the last element of the array (that will get popped) uint24 lastDataIdx = lastNode.lookup; uint208 lastDataValue = lastNode.value; // copy these values to the location of the root (that is safe, and that we no longer use) @@ -520,7 +520,7 @@ library Heap { uint256 right = 2 * pos + 2; // this could overflow uint24 if (right < size) { - // the check guarantees that `left` and `right` are both valid uint32 + // the check guarantees that `left` and `right` are both valid uint24 uint24 lIndex = uint24(left); uint24 rIndex = uint24(right); uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; @@ -531,7 +531,7 @@ library Heap { _siftDown(self, size, index, value, comp); } } else if (left < size) { - // the check guarantees that `left` is a valid uint32 + // the check guarantees that `left` is a valid uint24 uint24 lIndex = uint24(left); uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; if (comp(lValue, value)) { @@ -546,7 +546,7 @@ library Heap { * comparator, and moving toward the root of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` - * could be extracted from `self` and `pos`, but that would require redundant storage read. This parameters is not + * could be extracted from `self` and `pos`, but that would require redundant storage read. These parameters are not * verified. It is the caller role to make sure the parameters are correct. */ function _siftUp( diff --git a/scripts/generate/templates/Heap.js b/scripts/generate/templates/Heap.js index 7ed99939bb5..9c8f6ae91da 100644 --- a/scripts/generate/templates/Heap.js +++ b/scripts/generate/templates/Heap.js @@ -29,17 +29,17 @@ import {Panic} from "../Panic.sol"; * \`\`\` * * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the - * highest priority value is the one at the root. This value can be lookup up in constant time (O(1)) at + * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at * \`heap.data[heap.data[0].index].value\` * * The structure is designed to perform the following operations with the corresponding complexities: * - * * peek (get the highest priority in set): O(1) - * * insert (insert a value in the set): 0(log(n)) - * * pop (remove the highest priority value in set): O(log(n)) - * * replace (replace the highest priority value in set with a new value): O(log(n)) - * * length (get the number of elements in the set): O(1) - * * clear (remove all elements in the set): O(1) + * * peek (get the highest priority value): O(1) + * * insert (insert a value): O(log(n)) + * * pop (remove the highest priority value): O(log(n)) + * * replace (replace the highest priority value with a new value): O(log(n)) + * * length (get the number of elements): O(1) + * * clear (remove all elements): O(1) */ `; @@ -47,7 +47,7 @@ const generate = ({ struct, node, valueType, indexType, blockSize }) => `\ /** * @dev Binary heap that support values of type ${valueType}. * - * Each element of that structures uses ${blockSize} storage slots. + * Each element of that structure uses ${blockSize} storage slots. */ struct ${struct} { ${node}[] data; @@ -103,9 +103,9 @@ function pop( ${node} storage lastNode = _unsafeNodeAccess(self, last); ${valueType} rootDataValue = rootData.value; - // if root is not the last element of the data array (that will get pop-ed), reorder the data array. + // if root is not the last element of the data array (that will get popped), reorder the data array. if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get pop-ed) + // get details about the value stored in the last element of the array (that will get popped) ${indexType} lastDataIdx = lastNode.lookup; ${valueType} lastDataValue = lastNode.value; // copy these values to the location of the root (that is safe, and that we no longer use) @@ -255,7 +255,7 @@ function _siftDown( uint256 right = 2 * pos + 2; // this could overflow ${indexType} if (right < size) { - // the check guarantees that \`left\` and \`right\` are both valid uint32 + // the check guarantees that \`left\` and \`right\` are both valid ${indexType} ${indexType} lIndex = ${indexType}(left); ${indexType} rIndex = ${indexType}(right); ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; @@ -266,7 +266,7 @@ function _siftDown( _siftDown(self, size, index, value, comp); } } else if (left < size) { - // the check guarantees that \`left\` is a valid uint32 + // the check guarantees that \`left\` is a valid ${indexType} ${indexType} lIndex = ${indexType}(left); ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; if (comp(lValue, value)) { @@ -281,7 +281,7 @@ function _siftDown( * comparator, and moving toward the root of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`value\` - * could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. This parameters is not + * could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. These parameters are not * verified. It is the caller role to make sure the parameters are correct. */ function _siftUp( From 9d6a0cc7e91131c7043fe1ebc2661836e91a6124 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 29 Jul 2024 18:58:02 +0200 Subject: [PATCH 09/81] Fix documentation of private function `_upperLookupBinary` in Checkpoints.sol (#5128) Co-authored-by: cairo --- contracts/utils/structs/Checkpoints.sol | 24 +++++++++++------------ scripts/generate/templates/Checkpoints.js | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 5ba6ad92d5e..09477ee8e94 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -154,7 +154,7 @@ library Checkpoints { } /** - * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` + * @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high` * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive * `high`. * @@ -178,9 +178,9 @@ library Checkpoints { } /** - * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or - * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and - * exclusive `high`. + * @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high` + * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive + * `high`. * * WARNING: `high` should not be greater than the array's length. */ @@ -349,7 +349,7 @@ library Checkpoints { } /** - * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` + * @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high` * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive * `high`. * @@ -373,9 +373,9 @@ library Checkpoints { } /** - * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or - * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and - * exclusive `high`. + * @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high` + * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive + * `high`. * * WARNING: `high` should not be greater than the array's length. */ @@ -544,7 +544,7 @@ library Checkpoints { } /** - * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` + * @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or `high` * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive * `high`. * @@ -568,9 +568,9 @@ library Checkpoints { } /** - * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or - * `high` if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and - * exclusive `high`. + * @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or `high` + * if there is none. `low` and `high` define a section where to do the search, with inclusive `low` and exclusive + * `high`. * * WARNING: `high` should not be greater than the array's length. */ diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index 2291bab5789..d4e70ee0217 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -159,7 +159,7 @@ function _insert(${opts.checkpointTypeName}[] storage self, ${opts.keyTypeName} } /** - * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or \`high\` + * @dev Return the index of the first (oldest) checkpoint with key strictly bigger than the search key, or \`high\` * if there is none. \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and exclusive * \`high\`. * @@ -183,9 +183,9 @@ function _upperBinaryLookup( } /** - * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or - * \`high\` if there is none. \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and - * exclusive \`high\`. + * @dev Return the index of the first (oldest) checkpoint with key greater or equal than the search key, or \`high\` + * if there is none. \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and exclusive + * \`high\`. * * WARNING: \`high\` should not be greater than the array's length. */ From c1d49a32595bbe095960c43bee34e64a9cfe9f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 29 Jul 2024 14:09:27 -0600 Subject: [PATCH 10/81] Add note about opinionated design to ERC4626Fees (#5129) --- contracts/mocks/docs/ERC4626Fees.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/mocks/docs/ERC4626Fees.sol b/contracts/mocks/docs/ERC4626Fees.sol index b4baef5b0b7..dd499331978 100644 --- a/contracts/mocks/docs/ERC4626Fees.sol +++ b/contracts/mocks/docs/ERC4626Fees.sol @@ -8,6 +8,12 @@ import {SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol"; import {Math} from "../../utils/math/Math.sol"; /// @dev ERC-4626 vault with entry/exit fees expressed in https://en.wikipedia.org/wiki/Basis_point[basis point (bp)]. +/// +/// NOTE: The contract charges fees in terms of assets, not shares. This means that the fees are calculated based on the +/// amount of assets that are being deposited or withdrawn, and not based on the amount of shares that are being minted or +/// redeemed. This is an opinionated design decision that should be taken into account when integrating this contract. +/// +/// WARNING: This contract has not been audited and shouldn't be considered production ready. Consider using it with caution. abstract contract ERC4626Fees is ERC4626 { using Math for uint256; From e3786e63e6def6f3b71ce7b4b30906123bffe67c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 31 Jul 2024 20:18:27 +0200 Subject: [PATCH 11/81] Fix natspec comment missing * (#5136) --- contracts/utils/structs/Heap.sol | 4 ++-- scripts/generate/templates/Heap.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index 2d2517ec567..64c3ecd2b1f 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -218,7 +218,7 @@ library Heap { } } - /* + /** * @dev Swap node `i` and `j` in the tree. */ function _swap(Uint256Heap storage self, uint64 i, uint64 j) private { @@ -485,7 +485,7 @@ library Heap { } } - /* + /** * @dev Swap node `i` and `j` in the tree. */ function _swap(Uint208Heap storage self, uint24 i, uint24 j) private { diff --git a/scripts/generate/templates/Heap.js b/scripts/generate/templates/Heap.js index 9c8f6ae91da..5ef042a98b2 100644 --- a/scripts/generate/templates/Heap.js +++ b/scripts/generate/templates/Heap.js @@ -220,7 +220,7 @@ function clear(${struct} storage self) internal { } } -/* +/** * @dev Swap node \`i\` and \`j\` in the tree. */ function _swap(${struct} storage self, ${indexType} i, ${indexType} j) private { From aec36ddd6addbce37bc9780dade636262320571a Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 2 Aug 2024 21:06:42 +0200 Subject: [PATCH 12/81] Create a ERC1363Utils helper similar to existing ERC721Utils and ERC1155Utils (#5133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: cairo --- .changeset/tricky-bats-pretend.md | 5 + contracts/token/ERC1155/README.adoc | 2 + contracts/token/ERC20/README.adoc | 2 + contracts/token/ERC20/extensions/ERC1363.sol | 82 +---------------- contracts/token/ERC20/utils/ERC1363Utils.sol | 96 ++++++++++++++++++++ contracts/token/ERC721/README.adoc | 2 + 6 files changed, 111 insertions(+), 78 deletions(-) create mode 100644 .changeset/tricky-bats-pretend.md create mode 100644 contracts/token/ERC20/utils/ERC1363Utils.sol diff --git a/.changeset/tricky-bats-pretend.md b/.changeset/tricky-bats-pretend.md new file mode 100644 index 00000000000..2809d329357 --- /dev/null +++ b/.changeset/tricky-bats-pretend.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC1363Utils`: Add helper similar to the existing `ERC721Utils` and `ERC1155Utils` diff --git a/contracts/token/ERC1155/README.adoc b/contracts/token/ERC1155/README.adoc index d911d785d85..f8bf958f623 100644 --- a/contracts/token/ERC1155/README.adoc +++ b/contracts/token/ERC1155/README.adoc @@ -39,3 +39,5 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel == Utilities {{ERC1155Holder}} + +{{ERC1155Utils}} diff --git a/contracts/token/ERC20/README.adoc b/contracts/token/ERC20/README.adoc index faaacc57667..bfbe6790b67 100644 --- a/contracts/token/ERC20/README.adoc +++ b/contracts/token/ERC20/README.adoc @@ -71,3 +71,5 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel == Utilities {{SafeERC20}} + +{{ERC1363Utils}} diff --git a/contracts/token/ERC20/extensions/ERC1363.sol b/contracts/token/ERC20/extensions/ERC1363.sol index 1852ffa810a..253e443d6b1 100644 --- a/contracts/token/ERC20/extensions/ERC1363.sol +++ b/contracts/token/ERC20/extensions/ERC1363.sol @@ -4,10 +4,8 @@ pragma solidity ^0.8.20; import {ERC20} from "../ERC20.sol"; import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol"; - import {IERC1363} from "../../../interfaces/IERC1363.sol"; -import {IERC1363Receiver} from "../../../interfaces/IERC1363Receiver.sol"; -import {IERC1363Spender} from "../../../interfaces/IERC1363Spender.sol"; +import {ERC1363Utils} from "../utils/ERC1363Utils.sol"; /** * @title ERC1363 @@ -16,18 +14,6 @@ import {IERC1363Spender} from "../../../interfaces/IERC1363Spender.sol"; * {ERC1363-transferFromAndCall} methods while calls after approvals can be made with {ERC1363-approveAndCall} */ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { - /** - * @dev Indicates a failure with the token `receiver`. Used in transfers. - * @param receiver Address to which tokens are being transferred. - */ - error ERC1363InvalidReceiver(address receiver); - - /** - * @dev Indicates a failure with the token `spender`. Used in approvals. - * @param spender Address that may be allowed to operate on tokens without being their owner. - */ - error ERC1363InvalidSpender(address spender); - /** * @dev Indicates a failure within the {transfer} part of a transferAndCall operation. * @param receiver Address to which tokens are being transferred. @@ -80,7 +66,7 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { if (!transfer(to, value)) { revert ERC1363TransferFailed(to, value); } - _checkOnTransferReceived(_msgSender(), to, value, data); + ERC1363Utils.checkOnERC1363TransferReceived(_msgSender(), _msgSender(), to, value, data); return true; } @@ -112,7 +98,7 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { if (!transferFrom(from, to, value)) { revert ERC1363TransferFromFailed(from, to, value); } - _checkOnTransferReceived(from, to, value, data); + ERC1363Utils.checkOnERC1363TransferReceived(_msgSender(), from, to, value, data); return true; } @@ -139,67 +125,7 @@ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { if (!approve(spender, value)) { revert ERC1363ApproveFailed(spender, value); } - _checkOnApprovalReceived(spender, value, data); + ERC1363Utils.checkOnERC1363ApprovalReceived(_msgSender(), spender, value, data); return true; } - - /** - * @dev Performs a call to {IERC1363Receiver-onTransferReceived} on a target address. - * - * Requirements: - * - * - The target has code (i.e. is a contract). - * - The target `to` must implement the {IERC1363Receiver} interface. - * - The target must return the {IERC1363Receiver-onTransferReceived} selector to accept the transfer. - */ - function _checkOnTransferReceived(address from, address to, uint256 value, bytes memory data) private { - if (to.code.length == 0) { - revert ERC1363InvalidReceiver(to); - } - - try IERC1363Receiver(to).onTransferReceived(_msgSender(), from, value, data) returns (bytes4 retval) { - if (retval != IERC1363Receiver.onTransferReceived.selector) { - revert ERC1363InvalidReceiver(to); - } - } catch (bytes memory reason) { - if (reason.length == 0) { - revert ERC1363InvalidReceiver(to); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - - /** - * @dev Performs a call to {IERC1363Spender-onApprovalReceived} on a target address. - * - * Requirements: - * - * - The target has code (i.e. is a contract). - * - The target `spender` must implement the {IERC1363Spender} interface. - * - The target must return the {IERC1363Spender-onApprovalReceived} selector to accept the approval. - */ - function _checkOnApprovalReceived(address spender, uint256 value, bytes memory data) private { - if (spender.code.length == 0) { - revert ERC1363InvalidSpender(spender); - } - - try IERC1363Spender(spender).onApprovalReceived(_msgSender(), value, data) returns (bytes4 retval) { - if (retval != IERC1363Spender.onApprovalReceived.selector) { - revert ERC1363InvalidSpender(spender); - } - } catch (bytes memory reason) { - if (reason.length == 0) { - revert ERC1363InvalidSpender(spender); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } } diff --git a/contracts/token/ERC20/utils/ERC1363Utils.sol b/contracts/token/ERC20/utils/ERC1363Utils.sol new file mode 100644 index 00000000000..e6af49de5d3 --- /dev/null +++ b/contracts/token/ERC20/utils/ERC1363Utils.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC1363Receiver} from "../../../interfaces/IERC1363Receiver.sol"; +import {IERC1363Spender} from "../../../interfaces/IERC1363Spender.sol"; + +/** + * @dev Library that provides common ERC-1363 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-1363[ERC-1363]. + */ +library ERC1363Utils { + /** + * @dev Indicates a failure with the token `receiver`. Used in transfers. + * @param receiver Address to which tokens are being transferred. + */ + error ERC1363InvalidReceiver(address receiver); + + /** + * @dev Indicates a failure with the token `spender`. Used in approvals. + * @param spender Address that may be allowed to operate on tokens without being their owner. + */ + error ERC1363InvalidSpender(address spender); + + /** + * @dev Performs a call to {IERC1363Receiver-onTransferReceived} on a target address. + * + * Requirements: + * + * - The target has code (i.e. is a contract). + * - The target `to` must implement the {IERC1363Receiver} interface. + * - The target must return the {IERC1363Receiver-onTransferReceived} selector to accept the transfer. + */ + function checkOnERC1363TransferReceived( + address operator, + address from, + address to, + uint256 value, + bytes memory data + ) internal { + if (to.code.length == 0) { + revert ERC1363InvalidReceiver(to); + } + + try IERC1363Receiver(to).onTransferReceived(operator, from, value, data) returns (bytes4 retval) { + if (retval != IERC1363Receiver.onTransferReceived.selector) { + revert ERC1363InvalidReceiver(to); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + revert ERC1363InvalidReceiver(to); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + + /** + * @dev Performs a call to {IERC1363Spender-onApprovalReceived} on a target address. + * + * Requirements: + * + * - The target has code (i.e. is a contract). + * - The target `spender` must implement the {IERC1363Spender} interface. + * - The target must return the {IERC1363Spender-onApprovalReceived} selector to accept the approval. + */ + function checkOnERC1363ApprovalReceived( + address operator, + address spender, + uint256 value, + bytes memory data + ) internal { + if (spender.code.length == 0) { + revert ERC1363InvalidSpender(spender); + } + + try IERC1363Spender(spender).onApprovalReceived(operator, value, data) returns (bytes4 retval) { + if (retval != IERC1363Spender.onApprovalReceived.selector) { + revert ERC1363InvalidSpender(spender); + } + } catch (bytes memory reason) { + if (reason.length == 0) { + revert ERC1363InvalidSpender(spender); + } else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } +} diff --git a/contracts/token/ERC721/README.adoc b/contracts/token/ERC721/README.adoc index 0f87916f60a..5554720d699 100644 --- a/contracts/token/ERC721/README.adoc +++ b/contracts/token/ERC721/README.adoc @@ -65,3 +65,5 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel == Utilities {{ERC721Holder}} + +{{ERC721Utils}} From 24a641d9c9e0137093592a466c5496315626d98d Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Sat, 3 Aug 2024 00:00:26 +0200 Subject: [PATCH 13/81] Get leaves from memory in processMultiProofCalldata (#5140) --- contracts/utils/cryptography/MerkleProof.sol | 60 +++++++++++--------- scripts/generate/templates/MerkleProof.js | 19 ++++--- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index be1910062bb..ab268364fdf 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -105,7 +105,7 @@ library MerkleProof { * This version handles proofs in calldata with the default hashing function. */ function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) { - return processProof(proof, leaf) == root; + return processProofCalldata(proof, leaf) == root; } /** @@ -138,7 +138,7 @@ library MerkleProof { bytes32 leaf, function(bytes32, bytes32) view returns (bytes32) hasher ) internal view returns (bool) { - return processProof(proof, leaf, hasher) == root; + return processProofCalldata(proof, leaf, hasher) == root; } /** @@ -200,15 +200,16 @@ library MerkleProof { // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the Merkle tree. uint256 leavesLen = leaves.length; + uint256 proofFlagsLen = proofFlags.length; // Check proof validity. - if (leavesLen + proof.length != proofFlags.length + 1) { + if (leavesLen + proof.length != proofFlagsLen + 1) { revert MerkleProofInvalidMultiproof(); } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". - bytes32[] memory hashes = new bytes32[](proofFlags.length); + bytes32[] memory hashes = new bytes32[](proofFlagsLen); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; @@ -217,7 +218,7 @@ library MerkleProof { // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. - for (uint256 i = 0; i < proofFlags.length; i++) { + for (uint256 i = 0; i < proofFlagsLen; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) @@ -225,12 +226,12 @@ library MerkleProof { hashes[i] = Hashes.commutativeKeccak256(a, b); } - if (proofFlags.length > 0) { + if (proofFlagsLen > 0) { if (proofPos != proof.length) { revert MerkleProofInvalidMultiproof(); } unchecked { - return hashes[proofFlags.length - 1]; + return hashes[proofFlagsLen - 1]; } } else if (leavesLen > 0) { return leaves[0]; @@ -280,15 +281,16 @@ library MerkleProof { // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the Merkle tree. uint256 leavesLen = leaves.length; + uint256 proofFlagsLen = proofFlags.length; // Check proof validity. - if (leavesLen + proof.length != proofFlags.length + 1) { + if (leavesLen + proof.length != proofFlagsLen + 1) { revert MerkleProofInvalidMultiproof(); } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". - bytes32[] memory hashes = new bytes32[](proofFlags.length); + bytes32[] memory hashes = new bytes32[](proofFlagsLen); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; @@ -297,7 +299,7 @@ library MerkleProof { // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. - for (uint256 i = 0; i < proofFlags.length; i++) { + for (uint256 i = 0; i < proofFlagsLen; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) @@ -305,12 +307,12 @@ library MerkleProof { hashes[i] = hasher(a, b); } - if (proofFlags.length > 0) { + if (proofFlagsLen > 0) { if (proofPos != proof.length) { revert MerkleProofInvalidMultiproof(); } unchecked { - return hashes[proofFlags.length - 1]; + return hashes[proofFlagsLen - 1]; } } else if (leavesLen > 0) { return leaves[0]; @@ -331,9 +333,9 @@ library MerkleProof { bytes32[] calldata proof, bool[] calldata proofFlags, bytes32 root, - bytes32[] calldata leaves + bytes32[] memory leaves ) internal pure returns (bool) { - return processMultiProof(proof, proofFlags, leaves) == root; + return processMultiProofCalldata(proof, proofFlags, leaves) == root; } /** @@ -351,22 +353,23 @@ library MerkleProof { function processMultiProofCalldata( bytes32[] calldata proof, bool[] calldata proofFlags, - bytes32[] calldata leaves + bytes32[] memory leaves ) internal pure returns (bytes32 merkleRoot) { // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the Merkle tree. uint256 leavesLen = leaves.length; + uint256 proofFlagsLen = proofFlags.length; // Check proof validity. - if (leavesLen + proof.length != proofFlags.length + 1) { + if (leavesLen + proof.length != proofFlagsLen + 1) { revert MerkleProofInvalidMultiproof(); } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". - bytes32[] memory hashes = new bytes32[](proofFlags.length); + bytes32[] memory hashes = new bytes32[](proofFlagsLen); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; @@ -375,7 +378,7 @@ library MerkleProof { // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. - for (uint256 i = 0; i < proofFlags.length; i++) { + for (uint256 i = 0; i < proofFlagsLen; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) @@ -383,12 +386,12 @@ library MerkleProof { hashes[i] = Hashes.commutativeKeccak256(a, b); } - if (proofFlags.length > 0) { + if (proofFlagsLen > 0) { if (proofPos != proof.length) { revert MerkleProofInvalidMultiproof(); } unchecked { - return hashes[proofFlags.length - 1]; + return hashes[proofFlagsLen - 1]; } } else if (leavesLen > 0) { return leaves[0]; @@ -409,10 +412,10 @@ library MerkleProof { bytes32[] calldata proof, bool[] calldata proofFlags, bytes32 root, - bytes32[] calldata leaves, + bytes32[] memory leaves, function(bytes32, bytes32) view returns (bytes32) hasher ) internal view returns (bool) { - return processMultiProof(proof, proofFlags, leaves, hasher) == root; + return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root; } /** @@ -430,7 +433,7 @@ library MerkleProof { function processMultiProofCalldata( bytes32[] calldata proof, bool[] calldata proofFlags, - bytes32[] calldata leaves, + bytes32[] memory leaves, function(bytes32, bytes32) view returns (bytes32) hasher ) internal view returns (bytes32 merkleRoot) { // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by @@ -438,15 +441,16 @@ library MerkleProof { // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of // the Merkle tree. uint256 leavesLen = leaves.length; + uint256 proofFlagsLen = proofFlags.length; // Check proof validity. - if (leavesLen + proof.length != proofFlags.length + 1) { + if (leavesLen + proof.length != proofFlagsLen + 1) { revert MerkleProofInvalidMultiproof(); } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop". - bytes32[] memory hashes = new bytes32[](proofFlags.length); + bytes32[] memory hashes = new bytes32[](proofFlagsLen); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; @@ -455,7 +459,7 @@ library MerkleProof { // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // `proof` array. - for (uint256 i = 0; i < proofFlags.length; i++) { + for (uint256 i = 0; i < proofFlagsLen; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) @@ -463,12 +467,12 @@ library MerkleProof { hashes[i] = hasher(a, b); } - if (proofFlags.length > 0) { + if (proofFlagsLen > 0) { if (proofPos != proof.length) { revert MerkleProofInvalidMultiproof(); } unchecked { - return hashes[proofFlags.length - 1]; + return hashes[proofFlagsLen - 1]; } } else if (leavesLen > 0) { return leaves[0]; diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 768a981ee6b..45486bef395 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -56,7 +56,7 @@ function verify${suffix}(${(hash ? formatArgsMultiline : formatArgsSingleLine)( 'bytes32 leaf', hash && `function(bytes32, bytes32) view returns (bytes32) ${hash}`, )}) internal ${visibility} returns (bool) { - return processProof(proof, leaf${hash ? `, ${hash}` : ''}) == root; + return processProof${suffix}(proof, leaf${hash ? `, ${hash}` : ''}) == root; } /** @@ -93,10 +93,10 @@ function multiProofVerify${suffix}(${formatArgsMultiline( `bytes32[] ${location} proof`, `bool[] ${location} proofFlags`, 'bytes32 root', - `bytes32[] ${location} leaves`, + `bytes32[] memory leaves`, hash && `function(bytes32, bytes32) view returns (bytes32) ${hash}`, )}) internal ${visibility} returns (bool) { - return processMultiProof(proof, proofFlags, leaves${hash ? `, ${hash}` : ''}) == root; + return processMultiProof${suffix}(proof, proofFlags, leaves${hash ? `, ${hash}` : ''}) == root; } /** @@ -114,7 +114,7 @@ function multiProofVerify${suffix}(${formatArgsMultiline( function processMultiProof${suffix}(${formatArgsMultiline( `bytes32[] ${location} proof`, `bool[] ${location} proofFlags`, - `bytes32[] ${location} leaves`, + `bytes32[] memory leaves`, hash && `function(bytes32, bytes32) view returns (bytes32) ${hash}`, )}) internal ${visibility} returns (bytes32 merkleRoot) { // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by @@ -122,15 +122,16 @@ function processMultiProof${suffix}(${formatArgsMultiline( // \`hashes\` array. At the end of the process, the last hash in the \`hashes\` array should contain the root of // the Merkle tree. uint256 leavesLen = leaves.length; + uint256 proofFlagsLen = proofFlags.length; // Check proof validity. - if (leavesLen + proof.length != proofFlags.length + 1) { + if (leavesLen + proof.length != proofFlagsLen + 1) { revert MerkleProofInvalidMultiproof(); } // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using // \`xxx[xxxPos++]\`, which return the current value and increment the pointer, thus mimicking a queue's "pop". - bytes32[] memory hashes = new bytes32[](proofFlags.length); + bytes32[] memory hashes = new bytes32[](proofFlagsLen); uint256 leafPos = 0; uint256 hashPos = 0; uint256 proofPos = 0; @@ -139,7 +140,7 @@ function processMultiProof${suffix}(${formatArgsMultiline( // get the next hash. // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the // \`proof\` array. - for (uint256 i = 0; i < proofFlags.length; i++) { + for (uint256 i = 0; i < proofFlagsLen; i++) { bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]; bytes32 b = proofFlags[i] ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++]) @@ -147,12 +148,12 @@ function processMultiProof${suffix}(${formatArgsMultiline( hashes[i] = ${hash ?? DEFAULT_HASH}(a, b); } - if (proofFlags.length > 0) { + if (proofFlagsLen > 0) { if (proofPos != proof.length) { revert MerkleProofInvalidMultiproof(); } unchecked { - return hashes[proofFlags.length - 1]; + return hashes[proofFlagsLen - 1]; } } else if (leavesLen > 0) { return leaves[0]; From bcd4beb5e7fd8bd8edf160fbffb5d5b03804efdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 7 Aug 2024 09:34:02 -0600 Subject: [PATCH 14/81] Make explicit the validity of the empty set in MerkleProof.sol (#5142) --- contracts/utils/cryptography/MerkleProof.sol | 16 ++++++++++++++++ scripts/generate/templates/MerkleProof.js | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index ab268364fdf..f50752fedac 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -168,6 +168,10 @@ library MerkleProof { * This version handles multiproofs in memory with the default hashing function. * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, + * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function multiProofVerify( bytes32[] memory proof, @@ -247,6 +251,10 @@ library MerkleProof { * This version handles multiproofs in memory with a custom hashing function. * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, + * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function multiProofVerify( bytes32[] memory proof, @@ -328,6 +336,10 @@ library MerkleProof { * This version handles multiproofs in calldata with the default hashing function. * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, + * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function multiProofVerifyCalldata( bytes32[] calldata proof, @@ -407,6 +419,10 @@ library MerkleProof { * This version handles multiproofs in calldata with a custom hashing function. * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, + * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function multiProofVerifyCalldata( bytes32[] calldata proof, diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 45486bef395..7247d1626c2 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -88,6 +88,10 @@ const templateMultiProof = ({ suffix, location, visibility, hash }) => `\ * This version handles multiproofs in ${location} with ${hash ? 'a custom' : 'the default'} hashing function. * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. + * + * NOTE: The _empty set_ (i.e. the case where \`proof.length == 0 && leaves.length == 0\`) is considered a noop, + * and therefore a valid multiproof (i.e. it returns \`true\`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function multiProofVerify${suffix}(${formatArgsMultiline( `bytes32[] ${location} proof`, From c304b6710b4b5fcf2a319ad28c36c49df6caef14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 9 Aug 2024 15:03:20 -0600 Subject: [PATCH 15/81] Update MerkleProof note clarifying empty set definition (#5144) Co-authored-by: cairo Co-authored-by: sudo rm -rf --no-preserve-root / --- contracts/utils/cryptography/MerkleProof.sol | 36 +++++++++++++------- scripts/generate/templates/MerkleProof.js | 9 +++-- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index f50752fedac..6a0bc4d4e73 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -169,9 +169,8 @@ library MerkleProof { * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. * - * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, - * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not - * validating the leaves elsewhere. + * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. + * The `leaves` must be validated independently. See {processMultiProof}. */ function multiProofVerify( bytes32[] memory proof, @@ -193,6 +192,10 @@ library MerkleProof { * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, + * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function processMultiProof( bytes32[] memory proof, @@ -252,9 +255,8 @@ library MerkleProof { * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. * - * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, - * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not - * validating the leaves elsewhere. + * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. + * The `leaves` must be validated independently. See {processMultiProof}. */ function multiProofVerify( bytes32[] memory proof, @@ -277,6 +279,10 @@ library MerkleProof { * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, + * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function processMultiProof( bytes32[] memory proof, @@ -337,9 +343,8 @@ library MerkleProof { * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. * - * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, - * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not - * validating the leaves elsewhere. + * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. + * The `leaves` must be validated independently. See {processMultiProofCalldata}. */ function multiProofVerifyCalldata( bytes32[] calldata proof, @@ -361,6 +366,10 @@ library MerkleProof { * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, + * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function processMultiProofCalldata( bytes32[] calldata proof, @@ -420,9 +429,8 @@ library MerkleProof { * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. * - * NOTE: The _empty set_ (i.e. the case where `proof.length == 0 && leaves.length == 0`) is considered a noop, - * and therefore a valid multiproof (i.e. it returns `true`). Consider disallowing this case if you're not - * validating the leaves elsewhere. + * NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. + * The `leaves` must be validated independently. See {processMultiProofCalldata}. */ function multiProofVerifyCalldata( bytes32[] calldata proof, @@ -445,6 +453,10 @@ library MerkleProof { * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + * + * NOTE: The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, + * and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function processMultiProofCalldata( bytes32[] calldata proof, diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 7247d1626c2..80df713c7a3 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -89,9 +89,8 @@ const templateMultiProof = ({ suffix, location, visibility, hash }) => `\ * * CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details. * - * NOTE: The _empty set_ (i.e. the case where \`proof.length == 0 && leaves.length == 0\`) is considered a noop, - * and therefore a valid multiproof (i.e. it returns \`true\`). Consider disallowing this case if you're not - * validating the leaves elsewhere. + * NOTE: Consider the case where \`root == proof[0] && leaves.length == 0\` as it will return \`true\`. + * The \`leaves\` must be validated independently. See {processMultiProof${suffix}}. */ function multiProofVerify${suffix}(${formatArgsMultiline( `bytes32[] ${location} proof`, @@ -114,6 +113,10 @@ function multiProofVerify${suffix}(${formatArgsMultiline( * CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + * + * NOTE: The _empty set_ (i.e. the case where \`proof.length == 1 && leaves.length == 0\`) is considered a no-op, + * and therefore a valid multiproof (i.e. it returns \`proof[0]\`). Consider disallowing this case if you're not + * validating the leaves elsewhere. */ function processMultiProof${suffix}(${formatArgsMultiline( `bytes32[] ${location} proof`, From 4764ea50750d8bda9096e833706beba86918b163 Mon Sep 17 00:00:00 2001 From: danilo neves cruz Date: Mon, 12 Aug 2024 15:54:18 -0300 Subject: [PATCH 16/81] Fix `prepare` when using as git repository dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: cairo --- package.json | 2 +- scripts/prepare.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100755 scripts/prepare.sh diff --git a/package.json b/package.json index 868a4d3d0e2..bbad512c44f 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "coverage": "scripts/checks/coverage.sh", "docs": "npm run prepare-docs && oz-docs", "docs:watch": "oz-docs watch contracts docs/templates docs/config.js", - "prepare": "git config --local core.hooksPath .githooks", + "prepare": "scripts/prepare.sh", "prepare-docs": "scripts/prepare-docs.sh", "lint": "npm run lint:js && npm run lint:sol", "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", diff --git a/scripts/prepare.sh b/scripts/prepare.sh new file mode 100755 index 00000000000..a7d74227d26 --- /dev/null +++ b/scripts/prepare.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if git status &>/dev/null; then git config core.hooksPath .githooks; fi From 0b58a783b9b33b63eef2994af8958c0c6a72dc51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 07:51:07 -0600 Subject: [PATCH 17/81] Update codespell-project/actions-codespell action to v2.1 (#5151) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c280885632c..9d338bb642f 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -125,7 +125,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run CodeSpell - uses: codespell-project/actions-codespell@v2.0 + uses: codespell-project/actions-codespell@v2.1 with: check_hidden: true check_filenames: true From db464dd23bfda0a41970ec375609590a4d792629 Mon Sep 17 00:00:00 2001 From: cairo Date: Wed, 21 Aug 2024 13:35:52 -0700 Subject: [PATCH 18/81] Remove double declaration of `P` in P256 (#5159) --- contracts/utils/cryptography/P256.sol | 51 +++++++++++++-------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 83c9c975447..3275907f9f2 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -145,10 +145,9 @@ library P256 { */ function isValidPublicKey(bytes32 x, bytes32 y) internal pure returns (bool result) { assembly ("memory-safe") { - let p := P - let lhs := mulmod(y, y, p) // y^2 - let rhs := addmod(mulmod(addmod(mulmod(x, x, p), A, p), x, p), B, p) // ((x^2 + a) * x) + b = x^3 + ax + b - result := and(and(lt(x, p), lt(y, p)), eq(lhs, rhs)) // Should conform with the Weierstrass equation + let lhs := mulmod(y, y, P) // y^2 + let rhs := addmod(mulmod(addmod(mulmod(x, x, P), A, P), x, P), B, P) // ((x^2 + a) * x) + b = x^3 + ax + b + result := and(and(lt(x, P), lt(y, P)), eq(lhs, rhs)) // Should conform with the Weierstrass equation } } @@ -188,30 +187,29 @@ library P256 { uint256 z2 ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { assembly ("memory-safe") { - let p := P let z1 := mload(add(p1, 0x40)) - let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³ - let s2 := mulmod(y2, mulmod(mulmod(z1, z1, p), z1, p), p) // s2 = y2*z1³ - let r := addmod(s2, sub(p, s1), p) // r = s2-s1 - let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2² - let u2 := mulmod(x2, mulmod(z1, z1, p), p) // u2 = x2*z1² - let h := addmod(u2, sub(p, u1), p) // h = u2-u1 - let hh := mulmod(h, h, p) // h² + let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, P), z2, P), P) // s1 = y1*z2³ + let s2 := mulmod(y2, mulmod(mulmod(z1, z1, P), z1, P), P) // s2 = y2*z1³ + let r := addmod(s2, sub(P, s1), P) // r = s2-s1 + let u1 := mulmod(mload(p1), mulmod(z2, z2, P), P) // u1 = x1*z2² + let u2 := mulmod(x2, mulmod(z1, z1, P), P) // u2 = x2*z1² + let h := addmod(u2, sub(P, u1), P) // h = u2-u1 + let hh := mulmod(h, h, P) // h² // x' = r²-h³-2*u1*h² rx := addmod( - addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p), - sub(p, mulmod(2, mulmod(u1, hh, p), p)), - p + addmod(mulmod(r, r, P), sub(P, mulmod(h, hh, P)), P), + sub(P, mulmod(2, mulmod(u1, hh, P), P)), + P ) // y' = r*(u1*h²-x')-s1*h³ ry := addmod( - mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), - sub(p, mulmod(s1, mulmod(h, hh, p), p)), - p + mulmod(r, addmod(mulmod(u1, hh, P), sub(P, rx), P), P), + sub(P, mulmod(s1, mulmod(h, hh, P), P)), + P ) // z' = h*z1*z2 - rz := mulmod(h, mulmod(z1, z2, p), p) + rz := mulmod(h, mulmod(z1, z2, P), P) } } @@ -221,19 +219,18 @@ library P256 { */ function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) { assembly ("memory-safe") { - let p := P - let yy := mulmod(y, y, p) - let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² - let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ - let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s + let yy := mulmod(y, y, P) + let zz := mulmod(z, z, P) + let s := mulmod(4, mulmod(x, yy, P), P) // s = 4*x*y² + let m := addmod(mulmod(3, mulmod(x, x, P), P), mulmod(A, mulmod(zz, zz, P), P), P) // m = 3*x²+a*z⁴ + let t := addmod(mulmod(m, m, P), sub(P, mulmod(2, s, P)), P) // t = m²-2*s // x' = t rx := t // y' = m*(s-t)-8*y⁴ - ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + ry := addmod(mulmod(m, addmod(s, sub(P, t), P), P), sub(P, mulmod(8, mulmod(yy, yy, P), P)), P) // z' = 2*y*z - rz := mulmod(2, mulmod(y, z, p), p) + rz := mulmod(2, mulmod(y, z, P), P) } } From eb4e8632f781cdc86b4b47c7e80a5066499bd5d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:40:11 -0600 Subject: [PATCH 19/81] Bump axios from 1.6.8 to 1.7.4 (#5156) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7326aebeaa2..b679973dd3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2932,9 +2932,9 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", From 5fc38baea2f84b4a0c3cbd0642ab8b88fb3070de Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Fri, 23 Aug 2024 00:28:11 +0200 Subject: [PATCH 20/81] Fix documentation typos (#5118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/governance/README.adoc | 2 +- contracts/utils/README.adoc | 6 +++--- docs/modules/ROOT/pages/erc4626.adoc | 2 +- docs/modules/ROOT/pages/utilities.adoc | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index da678f4abb2..0f556b90d69 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -140,7 +140,7 @@ Timelocked operations are identified by a unique id (their hash) and follow a sp * By calling xref:api:governance.adoc#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-[`schedule`] (or xref:api:governance.adoc#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-[`scheduleBatch`]), a proposer moves the operation from the `Unset` to the `Pending` state. This starts a timer that must be longer than the minimum delay. The timer expires at a timestamp accessible through the xref:api:governance.adoc#TimelockController-getTimestamp-bytes32-[`getTimestamp`] method. * Once the timer expires, the operation automatically gets the `Ready` state. At this point, it can be executed. * By calling xref:api:governance.adoc#TimelockController-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-[`execute`] (or xref:api:governance.adoc#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-[`executeBatch`]), an executor triggers the operation's underlying transactions and moves it to the `Done` state. If the operation has a predecessor, it has to be in the `Done` state for this transition to succeed. -* xref:api:governance.adoc#TimelockController-TimelockController-cancel-bytes32-[`cancel`] allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is re-scheduled. +* xref:api:governance.adoc#TimelockController-TimelockController-cancel-bytes32-[`cancel`] allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is rescheduled. Operations status can be queried using the functions: diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index da9eae6a887..0ef3e5387c8 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -24,7 +24,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {EnumerableSet}: Like {EnumerableMap}, but for https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets]. Can be used to store privileged accounts, issued IDs, etc. * {DoubleEndedQueue}: An implementation of a https://en.wikipedia.org/wiki/Double-ended_queue[double ended queue] whose values can be removed added or remove from both sides. Useful for FIFO and LIFO structures. * {CircularBuffer}: A data structure to store the last N values pushed to it. - * {Checkpoints}: A data structure to store values mapped to an strictly increasing key. Can be used for storing and accessing values over time. + * {Checkpoints}: A data structure to store values mapped to a strictly increasing key. Can be used for storing and accessing values over time. * {Heap}: A library that implements a https://en.wikipedia.org/wiki/Binary_heap[binary heap] in storage. * {MerkleTree}: A library with https://wikipedia.org/wiki/Merkle_Tree[Merkle Tree] data structures and helper functions. * {Create2}: Wrapper around the https://blog.openzeppelin.com/getting-the-most-out-of-create2/[`CREATE2` EVM opcode] for safe use without having to deal with low-level assembly. @@ -35,8 +35,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. Also include primitives for reading from and writing to transient storage (only value types are currently supported). - * {Multicall}: Abstract contract with an utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. - * {Context}: An utility for abstracting the sender and calldata in the current execution context. + * {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. + * {Context}: A utility for abstracting the sender and calldata in the current execution context. * {Packing}: A library for packing and unpacking multiple values into bytes32 * {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes]. * {Comparators}: A library that contains comparator functions to use with with the {Heap} library. diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc index 03f3c216c70..977d8160632 100644 --- a/docs/modules/ROOT/pages/erc4626.adoc +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -41,7 +41,7 @@ image::erc4626-mint.png[Minting shares] Symmetrically, if we focus on limiting our losses to a maximum of 0.5%, we need to get at least 200 shares. With the green exchange rate that requires just 20 tokens, but with the orange rate that requires 200000 tokens. -We can clearly see that that the blue and green curves correspond to vaults that are safer than the yellow and orange curves. +We can clearly see that the blue and green curves correspond to vaults that are safer than the yellow and orange curves. The idea of an inflation attack is that an attacker can donate assets to the vault to move the rate curve to the right, and make the vault unsafe. diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index ecb950caf91..b8afec4eabd 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -8,7 +8,7 @@ Here are some of the more popular ones. === Checking Signatures On-Chain -At a high level, signatures are a set of cryptographic algorithms that allow for a _signer_ to prove himself owner of a _private key_ used to authorize an piece of information (generally a transaction or `UserOperation`). Natively, the EVM supports the Elliptic Curve Digital Signature Algorithm (https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm[ECDSA]) using the secp256k1 curve, however other signature algorithms such as P256 and RSA are supported. +At a high level, signatures are a set of cryptographic algorithms that allow for a _signer_ to prove himself owner of a _private key_ used to authorize a piece of information (generally a transaction or `UserOperation`). Natively, the EVM supports the Elliptic Curve Digital Signature Algorithm (https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm[ECDSA]) using the secp256k1 curve, however other signature algorithms such as P256 and RSA are supported. ==== Ethereum Signatures (secp256k1) @@ -34,7 +34,7 @@ WARNING: Getting signature verification right is not trivial: make sure you full P256, also known as secp256r1, is one of the most used signature schemes. P256 signatures are standardized by the National Institute of Standards and Technology (NIST) and it's widely available in consumer hardware and software. -These signatures are different to regular Ethereum Signatures (secp256k1) in that they use a different elliptic curve to perform operations but have similar security guarantees. +These signatures are different from regular Ethereum Signatures (secp256k1) in that they use a different elliptic curve to perform operations but have similar security guarantees. [source,solidity] ---- @@ -195,9 +195,9 @@ The `Enumerable*` structures are similar to mappings in that they store and remo === Building a Merkle Tree -Building an on-chain Merkle Tree allow developers to keep track of the history of roots in a decentralized manner. For these cases, the xref:api:utils.adoc#MerkleTree[`MerkleTree`] includes a predefined structure with functions to manipulate the tree (e.g. pushing values or resetting the tree). +Building an on-chain Merkle Tree allows developers to keep track of the history of roots in a decentralized manner. For these cases, the xref:api:utils.adoc#MerkleTree[`MerkleTree`] includes a predefined structure with functions to manipulate the tree (e.g. pushing values or resetting the tree). -The Merkle Tree does not keep track of the roots purposely, so that developers can choose their tracking mechanism. Setting up and using an Merkle Tree in Solidity is as simple as follows: +The Merkle Tree does not keep track of the roots purposely, so that developers can choose their tracking mechanism. Setting up and using a Merkle Tree in Solidity is as simple as follows: NOTE: Functions are exposed without access control for demonstration purposes @@ -218,7 +218,7 @@ function push(bytes32 leaf) public /* onlyOwner */ { The library also supports custom hashing functions, which can be passed as an extra parameter to the xref:api:utils.adoc#MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-[`push`] and xref:api:utils.adoc#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-[`setup`] functions. -Using custom hashing functions is a sensitive operation. After setup, it requires to keep using the same hashing function for every new valued pushed to the tree to avoid corrupting the tree. For this reason, it's a good practice to keep your hashing function static in your implementation contract as follows: +Using custom hashing functions is a sensitive operation. After setup, it requires to keep using the same hashing function for every new value pushed to the tree to avoid corrupting the tree. For this reason, it's a good practice to keep your hashing function static in your implementation contract as follows: [source,solidity] ---- @@ -272,7 +272,7 @@ function replace(Uint256Heap storage self, uint256 newValue) internal returns (u === Packing -The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allows for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one. +The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allow for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one. Commonly, developers pack values using structs that place values together so they fit better in storage. However, this approach requires to load such struct from either calldata or memory. Although sometimes necessary, it may be useful to pack values in a single slot and treat it as a packed value without involving calldata or memory. From 1edc2ae004974ebf053f4eba26b45469937b9381 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:51:58 -0700 Subject: [PATCH 21/81] Update dependency halmos to v0.1.14 and Python to 3.11 (#5147) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: cairo --- .github/workflows/formal-verification.yml | 2 +- fv-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/formal-verification.yml b/.github/workflows/formal-verification.yml index 517ec552c5d..e0475b195d8 100644 --- a/.github/workflows/formal-verification.yml +++ b/.github/workflows/formal-verification.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: {} env: - PIP_VERSION: '3.10' + PIP_VERSION: '3.11' JAVA_VERSION: '11' SOLC_VERSION: '0.8.20' diff --git a/fv-requirements.txt b/fv-requirements.txt index 920662b0277..6dda424ed2b 100644 --- a/fv-requirements.txt +++ b/fv-requirements.txt @@ -1,4 +1,4 @@ certora-cli==4.13.1 # File uses a custom name (fv-requirements.txt) so that it isn't picked by Netlify's build # whose latest Python version is 0.3.8, incompatible with most recent versions of Halmos -halmos==0.1.13 +halmos==0.1.14 From 48c67c7de0914df563e14f35b4bb6e9af7677bec Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 29 Aug 2024 19:58:35 +0200 Subject: [PATCH 22/81] Add missing docstrings (#5168) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/access/manager/AccessManager.sol | 8 ++++++++ contracts/proxy/ERC1967/ERC1967Utils.sol | 2 +- .../transparent/TransparentUpgradeableProxy.sol | 8 +++++++- contracts/utils/Base64.sol | 13 ++++++++----- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/contracts/access/manager/AccessManager.sol b/contracts/access/manager/AccessManager.sol index 76abea41c9a..88a9004912a 100644 --- a/contracts/access/manager/AccessManager.sol +++ b/contracts/access/manager/AccessManager.sol @@ -97,7 +97,15 @@ contract AccessManager is Context, Multicall, IAccessManager { uint32 nonce; } + /** + * @dev The identifier of the admin role. Required to perform most configuration operations including + * other roles' management and target restrictions. + */ uint64 public constant ADMIN_ROLE = type(uint64).min; // 0 + + /** + * @dev The identifier of the public role. Automatically granted to all addresses with no delay. + */ uint64 public constant PUBLIC_ROLE = type(uint64).max; // 2**64-1 mapping(address target => TargetConfig mode) private _targets; diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index ce7c474c9fb..cf555098af9 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -9,7 +9,7 @@ import {Address} from "../../utils/Address.sol"; import {StorageSlot} from "../../utils/StorageSlot.sol"; /** - * @dev This abstract contract provides getters and event emitting update functions for + * @dev This library provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[ERC-1967] slots. */ library ERC1967Utils { diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 2e3fbc538e6..6344eb9fa5f 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -15,7 +15,13 @@ import {ProxyAdmin} from "./ProxyAdmin.sol"; * include them in the ABI so this interface must be used to interact with it. */ interface ITransparentUpgradeableProxy is IERC1967 { - function upgradeToAndCall(address, bytes calldata) external payable; + /** + * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call + * encoded in `data`. + * + * See {UUPSUpgradeable-upgradeToAndCall} + */ + function upgradeToAndCall(address newImplementation, bytes calldata data) external payable; } /** diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index 4a0b313f9b9..630dc66be9c 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -40,14 +40,17 @@ library Base64 { // If padding is enabled, the final length should be `bytes` data length divided by 3 rounded up and then // multiplied by 4 so that it leaves room for padding the last chunk - // - `data.length + 2` -> Round up - // - `/ 3` -> Number of 3-bytes chunks + // - `data.length + 2` -> Prepare for division rounding up + // - `/ 3` -> Number of 3-bytes chunks (rounded up) // - `4 *` -> 4 characters for each chunk + // This is equivalent to: 4 * Math.ceil(data.length / 3) + // // If padding is disabled, the final length should be `bytes` data length multiplied by 4/3 rounded up as // opposed to when padding is required to fill the last chunk. - // - `4 *` -> 4 characters for each chunk - // - `data.length + 2` -> Round up - // - `/ 3` -> Number of 3-bytes chunks + // - `4 * data.length` -> 4 characters for each chunk + // - ` + 2` -> Prepare for division rounding up + // - `/ 3` -> Number of 3-bytes chunks (rounded up) + // This is equivalent to: Math.ceil((4 * data.length) / 3) uint256 resultLength = withPadding ? 4 * ((data.length + 2) / 3) : (4 * data.length + 2) / 3; string memory result = new string(resultLength); From 3547cdce21f0f907b93d1e1ebc2f0945c23bd166 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 29 Aug 2024 21:41:30 +0200 Subject: [PATCH 23/81] Fix typographical errors (#5171) --- contracts/access/manager/AccessManager.sol | 2 +- contracts/utils/StorageSlot.sol | 12 ++++++------ contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SignedMath.sol | 4 ++-- scripts/generate/templates/StorageSlot.js | 4 +++- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/contracts/access/manager/AccessManager.sol b/contracts/access/manager/AccessManager.sol index 88a9004912a..a6e2d7aed0e 100644 --- a/contracts/access/manager/AccessManager.sol +++ b/contracts/access/manager/AccessManager.sol @@ -695,7 +695,7 @@ contract AccessManager is Context, Multicall, IAccessManager { (bool adminRestricted, uint64 roleId, uint32 operationDelay) = _getAdminRestrictions(data); - // isTragetClosed apply to non-admin-restricted function + // isTargetClosed apply to non-admin-restricted function if (!adminRestricted && isTargetClosed(address(this))) { return (false, 0); } diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 85055ea0f29..e560717d2fa 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -89,7 +89,7 @@ library StorageSlot { } /** - * @dev Returns an `BooleanSlot` with member `value` located at `slot`. + * @dev Returns a `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly @@ -99,7 +99,7 @@ library StorageSlot { } /** - * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. + * @dev Returns a `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly @@ -109,7 +109,7 @@ library StorageSlot { } /** - * @dev Returns an `Uint256Slot` with member `value` located at `slot`. + * @dev Returns a `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly @@ -119,7 +119,7 @@ library StorageSlot { } /** - * @dev Returns an `Int256Slot` with member `value` located at `slot`. + * @dev Returns a `Int256Slot` with member `value` located at `slot`. */ function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) { /// @solidity memory-safe-assembly @@ -129,7 +129,7 @@ library StorageSlot { } /** - * @dev Returns an `StringSlot` with member `value` located at `slot`. + * @dev Returns a `StringSlot` with member `value` located at `slot`. */ function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly @@ -149,7 +149,7 @@ library StorageSlot { } /** - * @dev Returns an `BytesSlot` with member `value` located at `slot`. + * @dev Returns a `BytesSlot` with member `value` located at `slot`. */ function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index ff74f1358db..96af9ad247f 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -232,7 +232,7 @@ library Math { /** * @dev Calculate the modular multiplicative inverse of a number in Z/nZ. * - * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, expect 0. + * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0. * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible. * * If the input value is not inversible, 0 is returned. diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index c1109f9cb37..502f4ecb03f 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -58,10 +58,10 @@ library SignedMath { // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift, // taking advantage of the most significant (or "sign" bit) in two's complement representation. // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result, - // the mask will either be `bytes(0)` (if n is positive) or `~bytes32(0)` (if n is negative). + // the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative). int256 mask = n >> 255; - // A `bytes(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it. + // A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it. return uint256((n + mask) ^ mask); } } diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index aa0c325b72d..829e639c694 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -59,7 +59,9 @@ struct ${name}Slot { const get = ({ name }) => `\ /** - * @dev Returns an \`${name}Slot\` with member \`value\` located at \`slot\`. + * @dev Returns ${ + name.toLowerCase().startsWith('a') ? 'an' : 'a' + } \`${name}Slot\` with member \`value\` located at \`slot\`. */ function get${name}Slot(bytes32 slot) internal pure returns (${name}Slot storage r) { /// @solidity memory-safe-assembly From 37619479cd80f16e5b54307857f65b0b21162892 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 29 Aug 2024 21:45:16 +0200 Subject: [PATCH 24/81] Remove unused import in IAccessManaged (#5170) --- contracts/access/manager/IAccessManager.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/access/manager/IAccessManager.sol b/contracts/access/manager/IAccessManager.sol index 50637e90dbf..2811281a319 100644 --- a/contracts/access/manager/IAccessManager.sol +++ b/contracts/access/manager/IAccessManager.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.20; -import {IAccessManaged} from "./IAccessManaged.sol"; import {Time} from "../../utils/types/Time.sol"; interface IAccessManager { From 1e7ca3eea6b1438ad209e16104ad80f1a0f75163 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 29 Aug 2024 22:38:31 +0200 Subject: [PATCH 25/81] Remove redundant array access in Checkpoints (#5169) --- contracts/utils/structs/Checkpoints.sol | 6 +++--- scripts/generate/templates/Checkpoints.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 09477ee8e94..8d1575e162e 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -142,7 +142,7 @@ library Checkpoints { // Update or push new checkpoint if (lastKey == key) { - _unsafeAccess(self, pos - 1)._value = value; + last._value = value; } else { self.push(Checkpoint224({_key: key, _value: value})); } @@ -337,7 +337,7 @@ library Checkpoints { // Update or push new checkpoint if (lastKey == key) { - _unsafeAccess(self, pos - 1)._value = value; + last._value = value; } else { self.push(Checkpoint208({_key: key, _value: value})); } @@ -532,7 +532,7 @@ library Checkpoints { // Update or push new checkpoint if (lastKey == key) { - _unsafeAccess(self, pos - 1)._value = value; + last._value = value; } else { self.push(Checkpoint160({_key: key, _value: value})); } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index d4e70ee0217..1a4d1a7a5cd 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -147,7 +147,7 @@ function _insert(${opts.checkpointTypeName}[] storage self, ${opts.keyTypeName} // Update or push new checkpoint if (lastKey == key) { - _unsafeAccess(self, pos - 1).${opts.valueFieldName} = value; + last.${opts.valueFieldName} = value; } else { self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value})); } From d8bbd346762c7bf9f594777dfab788d214423ced Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 29 Aug 2024 23:07:17 +0200 Subject: [PATCH 26/81] Update declaration of memory safe assembly blocks (#5172) --- contracts/metatx/ERC2771Forwarder.sol | 6 +- contracts/proxy/Clones.sol | 9 +-- contracts/token/ERC1155/ERC1155.sol | 3 +- .../token/ERC1155/utils/ERC1155Utils.sol | 6 +- contracts/token/ERC20/utils/ERC1363Utils.sol | 6 +- contracts/token/ERC721/utils/ERC721Utils.sol | 3 +- contracts/utils/Address.sol | 3 +- contracts/utils/Arrays.sol | 21 +++---- contracts/utils/Base64.sol | 3 +- contracts/utils/Create2.sol | 6 +- contracts/utils/Panic.sol | 3 +- contracts/utils/ShortStrings.sol | 3 +- contracts/utils/SlotDerivation.sol | 27 +++------ contracts/utils/StorageSlot.sol | 57 +++++++------------ contracts/utils/Strings.sol | 6 +- contracts/utils/cryptography/ECDSA.sol | 3 +- contracts/utils/cryptography/Hashes.sol | 3 +- .../utils/cryptography/MessageHashUtils.sol | 6 +- contracts/utils/math/Math.sol | 6 +- contracts/utils/math/SafeCast.sol | 3 +- contracts/utils/structs/EnumerableMap.sol | 24 +++----- contracts/utils/structs/EnumerableSet.sol | 9 +-- contracts/utils/structs/Heap.sol | 6 +- scripts/generate/templates/Arrays.js | 9 +-- scripts/generate/templates/EnumerableMap.js | 3 +- scripts/generate/templates/EnumerableSet.js | 3 +- scripts/generate/templates/Heap.js | 3 +- scripts/generate/templates/SafeCast.js | 3 +- scripts/generate/templates/SlotDerivation.js | 12 ++-- scripts/generate/templates/StorageSlot.js | 12 ++-- test/proxy/Clones.t.sol | 3 +- test/utils/Create2.t.sol | 3 +- 32 files changed, 91 insertions(+), 182 deletions(-) diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 03c3bee70c6..b5e943d920c 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -309,8 +309,7 @@ contract ERC2771Forwarder is EIP712, Nonces { bool success; uint256 returnSize; uint256 returnValue; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { // Perform the staticcall and save the result in the scratch space. // | Location | Content | Content (Hex) | // |-----------|----------|--------------------------------------------------------------------| @@ -362,8 +361,7 @@ contract ERC2771Forwarder is EIP712, Nonces { // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since // neither revert or assert consume all gas since Solidity 0.8.20 // https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { invalid() } } diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index d243d67f34b..097b43b43b3 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -37,8 +37,7 @@ library Clones { if (address(this).balance < value) { revert Errors.InsufficientBalance(address(this).balance, value); } - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes // of the `implementation` address with the bytecode before the address. mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) @@ -77,8 +76,7 @@ library Clones { if (address(this).balance < value) { revert Errors.InsufficientBalance(address(this).balance, value); } - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes // of the `implementation` address with the bytecode before the address. mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) @@ -99,8 +97,7 @@ library Clones { bytes32 salt, address deployer ) internal pure returns (address predicted) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let ptr := mload(0x40) mstore(add(ptr, 0x38), deployer) mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 5e05e4791dd..b0e7e547ab2 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -381,8 +381,7 @@ abstract contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI, IER uint256 element1, uint256 element2 ) private pure returns (uint256[] memory array1, uint256[] memory array2) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { // Load the free memory pointer array1 := mload(0x40) // Set array length to 1 diff --git a/contracts/token/ERC1155/utils/ERC1155Utils.sol b/contracts/token/ERC1155/utils/ERC1155Utils.sol index 0ff2bf146a4..a07539bd018 100644 --- a/contracts/token/ERC1155/utils/ERC1155Utils.sol +++ b/contracts/token/ERC1155/utils/ERC1155Utils.sol @@ -38,8 +38,7 @@ library ERC1155Utils { // non-IERC1155Receiver implementer revert IERC1155Errors.ERC1155InvalidReceiver(to); } else { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(32, reason), mload(reason)) } } @@ -76,8 +75,7 @@ library ERC1155Utils { // non-IERC1155Receiver implementer revert IERC1155Errors.ERC1155InvalidReceiver(to); } else { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(32, reason), mload(reason)) } } diff --git a/contracts/token/ERC20/utils/ERC1363Utils.sol b/contracts/token/ERC20/utils/ERC1363Utils.sol index e6af49de5d3..f5c931361f7 100644 --- a/contracts/token/ERC20/utils/ERC1363Utils.sol +++ b/contracts/token/ERC20/utils/ERC1363Utils.sol @@ -51,8 +51,7 @@ library ERC1363Utils { if (reason.length == 0) { revert ERC1363InvalidReceiver(to); } else { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(32, reason), mload(reason)) } } @@ -86,8 +85,7 @@ library ERC1363Utils { if (reason.length == 0) { revert ERC1363InvalidSpender(spender); } else { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(32, reason), mload(reason)) } } diff --git a/contracts/token/ERC721/utils/ERC721Utils.sol b/contracts/token/ERC721/utils/ERC721Utils.sol index 43dd107b296..712ac16a70a 100644 --- a/contracts/token/ERC721/utils/ERC721Utils.sol +++ b/contracts/token/ERC721/utils/ERC721Utils.sol @@ -37,8 +37,7 @@ library ERC721Utils { // non-IERC721Receiver implementer revert IERC721Errors.ERC721InvalidReceiver(to); } else { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { revert(add(32, reason), mload(reason)) } } diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 53a3c442049..40f01a93dca 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -139,8 +139,7 @@ library Address { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index fe54bafee7c..14f4ce297ae 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -134,8 +134,7 @@ library Arrays { * @dev Pointer to the memory location of the first element of `array`. */ function _begin(uint256[] memory array) private pure returns (uint256 ptr) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { ptr := add(array, 0x20) } } @@ -377,8 +376,7 @@ library Arrays { */ function unsafeAccess(address[] storage arr, uint256 pos) internal pure returns (StorageSlot.AddressSlot storage) { bytes32 slot; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { slot := arr.slot } return slot.deriveArray().offset(pos).getAddressSlot(); @@ -391,8 +389,7 @@ library Arrays { */ function unsafeAccess(bytes32[] storage arr, uint256 pos) internal pure returns (StorageSlot.Bytes32Slot storage) { bytes32 slot; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { slot := arr.slot } return slot.deriveArray().offset(pos).getBytes32Slot(); @@ -405,8 +402,7 @@ library Arrays { */ function unsafeAccess(uint256[] storage arr, uint256 pos) internal pure returns (StorageSlot.Uint256Slot storage) { bytes32 slot; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { slot := arr.slot } return slot.deriveArray().offset(pos).getUint256Slot(); @@ -451,8 +447,7 @@ library Arrays { * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. */ function unsafeSetLength(address[] storage array, uint256 len) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { sstore(array.slot, len) } } @@ -463,8 +458,7 @@ library Arrays { * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. */ function unsafeSetLength(bytes32[] storage array, uint256 len) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { sstore(array.slot, len) } } @@ -475,8 +469,7 @@ library Arrays { * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. */ function unsafeSetLength(uint256[] storage array, uint256 len) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { sstore(array.slot, len) } } diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index 630dc66be9c..be0b7d687fb 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -55,8 +55,7 @@ library Base64 { string memory result = new string(resultLength); - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { // Prepare the lookup table (skip the first "length" byte) let tablePtr := add(table, 1) diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index a88ce25f643..121eb0c742a 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -41,8 +41,7 @@ library Create2 { if (bytecode.length == 0) { revert Create2EmptyBytecode(); } - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) // if no address was created, and returndata is not empty, bubble revert if and(iszero(addr), not(iszero(returndatasize()))) { @@ -69,8 +68,7 @@ library Create2 { * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}. */ function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let ptr := mload(0x40) // Get free memory pointer // | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... | diff --git a/contracts/utils/Panic.sol b/contracts/utils/Panic.sol index b6443035cf5..8769bd9a786 100644 --- a/contracts/utils/Panic.sol +++ b/contracts/utils/Panic.sol @@ -45,8 +45,7 @@ library Panic { /// @dev Reverts with a panic code. Recommended to use with /// the internal constants with predefined codes. function panic(uint256 code) internal pure { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, 0x4e487b71) mstore(0x20, code) revert(0x1c, 0x24) diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index fdfe774d635..78fc5cd963d 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -64,8 +64,7 @@ library ShortStrings { uint256 len = byteLength(sstr); // using `new string(len)` would work locally but is not memory safe. string memory str = new string(32); - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(str, len) mstore(add(str, 0x20), sstr) } diff --git a/contracts/utils/SlotDerivation.sol b/contracts/utils/SlotDerivation.sol index c75941b5886..62c28a55fa6 100644 --- a/contracts/utils/SlotDerivation.sol +++ b/contracts/utils/SlotDerivation.sol @@ -40,8 +40,7 @@ library SlotDerivation { * @dev Derive an ERC-7201 slot from a string (namespace). */ function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)) slot := and(keccak256(0x00, 0x20), not(0xff)) } @@ -60,8 +59,7 @@ library SlotDerivation { * @dev Derive the location of the first element in an array from the slot where the length is stored. */ function deriveArray(bytes32 slot) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, slot) result := keccak256(0x00, 0x20) } @@ -71,8 +69,7 @@ library SlotDerivation { * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, key) mstore(0x20, slot) result := keccak256(0x00, 0x40) @@ -83,8 +80,7 @@ library SlotDerivation { * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, key) mstore(0x20, slot) result := keccak256(0x00, 0x40) @@ -95,8 +91,7 @@ library SlotDerivation { * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, bytes32 key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, key) mstore(0x20, slot) result := keccak256(0x00, 0x40) @@ -107,8 +102,7 @@ library SlotDerivation { * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, uint256 key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, key) mstore(0x20, slot) result := keccak256(0x00, 0x40) @@ -119,8 +113,7 @@ library SlotDerivation { * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, int256 key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, key) mstore(0x20, slot) result := keccak256(0x00, 0x40) @@ -131,8 +124,7 @@ library SlotDerivation { * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, string memory key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let length := mload(key) let begin := add(key, 0x20) let end := add(begin, length) @@ -147,8 +139,7 @@ library SlotDerivation { * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, bytes memory key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let length := mload(key) let begin := add(key, 0x20) let end := add(begin, length) diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index e560717d2fa..2e4f736d8d1 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -82,8 +82,7 @@ library StorageSlot { * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -92,8 +91,7 @@ library StorageSlot { * @dev Returns a `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -102,8 +100,7 @@ library StorageSlot { * @dev Returns a `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -112,8 +109,7 @@ library StorageSlot { * @dev Returns a `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -122,8 +118,7 @@ library StorageSlot { * @dev Returns a `Int256Slot` with member `value` located at `slot`. */ function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -132,8 +127,7 @@ library StorageSlot { * @dev Returns a `StringSlot` with member `value` located at `slot`. */ function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -142,8 +136,7 @@ library StorageSlot { * @dev Returns an `StringSlot` representation of the string storage pointer `store`. */ function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := store.slot } } @@ -152,8 +145,7 @@ library StorageSlot { * @dev Returns a `BytesSlot` with member `value` located at `slot`. */ function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -162,8 +154,7 @@ library StorageSlot { * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. */ function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := store.slot } } @@ -232,8 +223,7 @@ library StorageSlot { * @dev Load the value held at location `slot` in transient storage. */ function tload(AddressSlotType slot) internal view returns (address value) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { value := tload(slot) } } @@ -242,8 +232,7 @@ library StorageSlot { * @dev Store `value` at location `slot` in transient storage. */ function tstore(AddressSlotType slot, address value) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { tstore(slot, value) } } @@ -252,8 +241,7 @@ library StorageSlot { * @dev Load the value held at location `slot` in transient storage. */ function tload(BooleanSlotType slot) internal view returns (bool value) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { value := tload(slot) } } @@ -262,8 +250,7 @@ library StorageSlot { * @dev Store `value` at location `slot` in transient storage. */ function tstore(BooleanSlotType slot, bool value) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { tstore(slot, value) } } @@ -272,8 +259,7 @@ library StorageSlot { * @dev Load the value held at location `slot` in transient storage. */ function tload(Bytes32SlotType slot) internal view returns (bytes32 value) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { value := tload(slot) } } @@ -282,8 +268,7 @@ library StorageSlot { * @dev Store `value` at location `slot` in transient storage. */ function tstore(Bytes32SlotType slot, bytes32 value) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { tstore(slot, value) } } @@ -292,8 +277,7 @@ library StorageSlot { * @dev Load the value held at location `slot` in transient storage. */ function tload(Uint256SlotType slot) internal view returns (uint256 value) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { value := tload(slot) } } @@ -302,8 +286,7 @@ library StorageSlot { * @dev Store `value` at location `slot` in transient storage. */ function tstore(Uint256SlotType slot, uint256 value) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { tstore(slot, value) } } @@ -312,8 +295,7 @@ library StorageSlot { * @dev Load the value held at location `slot` in transient storage. */ function tload(Int256SlotType slot) internal view returns (int256 value) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { value := tload(slot) } } @@ -322,8 +304,7 @@ library StorageSlot { * @dev Store `value` at location `slot` in transient storage. */ function tstore(Int256SlotType slot, int256 value) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { tstore(slot, value) } } diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 164d8acd07d..5448060b70e 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -26,14 +26,12 @@ library Strings { uint256 length = Math.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { ptr := add(buffer, add(32, length)) } while (true) { ptr--; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) } value /= 10; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 864c8ee8766..3736171bf9f 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -60,8 +60,7 @@ library ECDSA { uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) diff --git a/contracts/utils/cryptography/Hashes.sol b/contracts/utils/cryptography/Hashes.sol index 434a8494251..c78bc80fd79 100644 --- a/contracts/utils/cryptography/Hashes.sol +++ b/contracts/utils/cryptography/Hashes.sol @@ -19,8 +19,7 @@ library Hashes { * @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory. */ function _efficientKeccak256(bytes32 a, bytes32 b) private pure returns (bytes32 value) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, a) mstore(0x20, b) value := keccak256(0x00, 0x40) diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol index 45c2421ad96..35746ce72ce 100644 --- a/contracts/utils/cryptography/MessageHashUtils.sol +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -28,8 +28,7 @@ library MessageHashUtils { * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) @@ -74,8 +73,7 @@ library MessageHashUtils { * See {ECDSA-recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let ptr := mload(0x40) mstore(ptr, hex"19_01") mstore(add(ptr, 0x02), domainSeparator) diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 96af9ad247f..9e277f3f50d 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -336,8 +336,7 @@ library Math { */ function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) { if (m == 0) return (false, 0); - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let ptr := mload(0x40) // | Offset | Content | Content (Hex) | // |-----------|------------|--------------------------------------------------------------------| @@ -387,8 +386,7 @@ library Math { // Encode call args in result and move the free memory pointer result = abi.encodePacked(b.length, e.length, mLen, b, e, m); - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let dataPtr := add(result, 0x20) // Write result on top of args to avoid allocating extra memory. success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen) diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index d8de2e17c49..36832006ebf 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -1155,8 +1155,7 @@ library SafeCast { * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. */ function toUint(bool b) internal pure returns (uint256 u) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { u := iszero(iszero(b)) } } diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index 814916b69ae..e61182e1fbd 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -245,8 +245,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); uint256[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -339,8 +338,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); uint256[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -433,8 +431,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); uint256[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -527,8 +524,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); address[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -621,8 +617,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); address[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -715,8 +710,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); address[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -809,8 +803,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); bytes32[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -903,8 +896,7 @@ library EnumerableMap { bytes32[] memory store = keys(map._inner); bytes32[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index 4c7fc5e1d76..90fcfa9d77b 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -220,8 +220,7 @@ library EnumerableSet { bytes32[] memory store = _values(set._inner); bytes32[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -294,8 +293,7 @@ library EnumerableSet { bytes32[] memory store = _values(set._inner); address[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } @@ -368,8 +366,7 @@ library EnumerableSet { bytes32[] memory store = _values(set._inner); uint256[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index 64c3ecd2b1f..269c4723f34 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -212,8 +212,7 @@ library Heap { */ function clear(Uint256Heap storage self) internal { Uint256HeapNode[] storage data = self.data; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { sstore(data.slot, 0) } } @@ -479,8 +478,7 @@ library Heap { */ function clear(Uint208Heap storage self) internal { Uint208HeapNode[] storage data = self.data; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { sstore(data.slot, 0) } } diff --git a/scripts/generate/templates/Arrays.js b/scripts/generate/templates/Arrays.js index 9823e4e5d7b..3a1e62237ee 100644 --- a/scripts/generate/templates/Arrays.js +++ b/scripts/generate/templates/Arrays.js @@ -86,8 +86,7 @@ function _quickSort(uint256 begin, uint256 end, function(uint256, uint256) pure * @dev Pointer to the memory location of the first element of \`array\`. */ function _begin(uint256[] memory array) private pure returns (uint256 ptr) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { ptr := add(array, 0x20) } } @@ -323,8 +322,7 @@ function unsafeAccess(${type}[] storage arr, uint256 pos) internal pure returns type, )}Slot storage) { bytes32 slot; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { slot := arr.slot } return slot.deriveArray().offset(pos).get${capitalize(type)}Slot(); @@ -351,8 +349,7 @@ const unsafeSetLength = type => `\ * WARNING: this does not clear elements if length is reduced, of initialize elements if length is increased. */ function unsafeSetLength(${type}[] storage array, uint256 len) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { sstore(array.slot, len) } } diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index bcc8edd76e5..656d8463dec 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -250,8 +250,7 @@ function keys(${name} storage map) internal view returns (${keyType}[] memory) { bytes32[] memory store = keys(map._inner); ${keyType}[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index d1878775aaf..351466b1313 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -227,8 +227,7 @@ function values(${name} storage set) internal view returns (${type}[] memory) { bytes32[] memory store = _values(set._inner); ${type}[] memory result; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { result := store } diff --git a/scripts/generate/templates/Heap.js b/scripts/generate/templates/Heap.js index 5ef042a98b2..558ed1b9115 100644 --- a/scripts/generate/templates/Heap.js +++ b/scripts/generate/templates/Heap.js @@ -214,8 +214,7 @@ function length(${struct} storage self) internal view returns (${indexType}) { */ function clear(${struct} storage self) internal { ${struct}Node[] storage data = self.data; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { sstore(data.slot, 0) } } diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index 8a9ce9fc765..a3b32e3f00a 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -121,8 +121,7 @@ const boolToUint = `\ * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. */ function toUint(bool b) internal pure returns (uint256 u) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { u := iszero(iszero(b)) } } diff --git a/scripts/generate/templates/SlotDerivation.js b/scripts/generate/templates/SlotDerivation.js index d8ab35d6f75..5311fb3c8d1 100644 --- a/scripts/generate/templates/SlotDerivation.js +++ b/scripts/generate/templates/SlotDerivation.js @@ -43,8 +43,7 @@ const namespace = `\ * @dev Derive an ERC-7201 slot from a string (namespace). */ function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)) slot := and(keccak256(0x00, 0x20), not(0xff)) } @@ -65,8 +64,7 @@ function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result * @dev Derive the location of the first element in an array from the slot where the length is stored. */ function deriveArray(bytes32 slot) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, slot) result := keccak256(0x00, 0x20) } @@ -78,8 +76,7 @@ const mapping = ({ type }) => `\ * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, ${type} key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(0x00, key) mstore(0x20, slot) result := keccak256(0x00, 0x40) @@ -92,8 +89,7 @@ const mapping2 = ({ type }) => `\ * @dev Derive the location of a mapping element from the key. */ function deriveMapping(bytes32 slot, ${type} memory key) internal pure returns (bytes32 result) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { let length := mload(key) let begin := add(key, 0x20) let end := add(begin, length) diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index 829e639c694..7a00f5e225c 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -64,8 +64,7 @@ const get = ({ name }) => `\ } \`${name}Slot\` with member \`value\` located at \`slot\`. */ function get${name}Slot(bytes32 slot) internal pure returns (${name}Slot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := slot } } @@ -76,8 +75,7 @@ const getStorage = ({ type, name }) => `\ * @dev Returns an \`${name}Slot\` representation of the ${type} storage pointer \`store\`. */ function get${name}Slot(${type} storage store) internal pure returns (${name}Slot storage r) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { r.slot := store.slot } } @@ -102,8 +100,7 @@ const transient = ({ type, name }) => `\ * @dev Load the value held at location \`slot\` in transient storage. */ function tload(${name}SlotType slot) internal view returns (${type} value) { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { value := tload(slot) } } @@ -112,8 +109,7 @@ function tload(${name}SlotType slot) internal view returns (${type} value) { * @dev Store \`value\` at location \`slot\` in transient storage. */ function tstore(${name}SlotType slot, ${type} value) internal { - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { tstore(slot, value) } } diff --git a/test/proxy/Clones.t.sol b/test/proxy/Clones.t.sol index 4301e103c26..31b072b98de 100644 --- a/test/proxy/Clones.t.sol +++ b/test/proxy/Clones.t.sol @@ -13,8 +13,7 @@ contract ClonesTest is Test { function testSymbolicPredictDeterministicAddressSpillage(address implementation, bytes32 salt) public { address predicted = Clones.predictDeterministicAddress(implementation, salt); bytes32 spillage; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { spillage := and(predicted, 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) } assertEq(spillage, bytes32(0)); diff --git a/test/utils/Create2.t.sol b/test/utils/Create2.t.sol index e8e2269f68a..6cc037a3b3b 100644 --- a/test/utils/Create2.t.sol +++ b/test/utils/Create2.t.sol @@ -9,8 +9,7 @@ contract Create2Test is Test { function testSymbolicComputeAddressSpillage(bytes32 salt, bytes32 bytecodeHash, address deployer) public { address predicted = Create2.computeAddress(salt, bytecodeHash, deployer); bytes32 spillage; - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { spillage := and(predicted, 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) } assertEq(spillage, bytes32(0)); From a9161aa93f68ca9df9a369f2ddc0026781268bc9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:16:27 -0600 Subject: [PATCH 27/81] Update dependency glob to v11 (#5107) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 247 ++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 117 insertions(+), 132 deletions(-) diff --git a/package-lock.json b/package-lock.json index b679973dd3b..dc147499814 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "eslint": "^8.30.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", - "glob": "^10.3.5", + "glob": "^11.0.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", @@ -5226,22 +5226,24 @@ } }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5264,20 +5266,22 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5645,6 +5649,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/hardhat-gas-reporter/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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/hardhat-gas-reporter/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5706,6 +5720,27 @@ "@scure/bip39": "1.2.2" } }, + "node_modules/hardhat-gas-reporter/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/hardhat-gas-reporter/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5724,6 +5759,29 @@ "node": ">=8" } }, + "node_modules/hardhat-gas-reporter/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/hardhat-gas-reporter/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/hardhat-gas-reporter/node_modules/markdown-table": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", @@ -5737,6 +5795,39 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/hardhat-gas-reporter/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hardhat-gas-reporter/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/hardhat-gas-reporter/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6725,15 +6816,16 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -8022,30 +8114,22 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -8642,105 +8726,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz", - "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-4.0.0.tgz", - "integrity": "sha512-l/mOwLWs7BQIgOKrL46dIAbyCKvPV7YJPDspkuc88rHsZRlg3hptUGdU7Trv0VFP4d3xnSGBQrKu5ZvGB7UeIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^3.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.0.tgz", - "integrity": "sha512-S4phymWe5NHWbTV8sAlyNQfkmdhvaoHX43x4yLtJBjw2zJtEuzkihDjV5uKq+D/EoMkjbG6msw3ubbSd1pGkyg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^4.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", diff --git a/package.json b/package.json index bbad512c44f..f7c0f519737 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "eslint": "^8.30.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", - "glob": "^10.3.5", + "glob": "^11.0.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", From 83f954d8c46da85990f6a16b6a77b80904c1225c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 30 Aug 2024 05:44:05 -0600 Subject: [PATCH 28/81] Add memory side effects notes when using function pointers (#5174) --- contracts/utils/Arrays.sol | 6 ++++++ contracts/utils/cryptography/MerkleProof.sol | 3 +++ scripts/generate/templates/Arrays.js | 2 ++ scripts/generate/templates/MerkleProof.js | 3 +++ 4 files changed, 14 insertions(+) diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 14f4ce297ae..432c8602800 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -26,6 +26,8 @@ library Arrays { * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may * consume more gas than is available in a block, leading to potential DoS. + * + * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. */ function sort( uint256[] memory array, @@ -53,6 +55,8 @@ library Arrays { * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may * consume more gas than is available in a block, leading to potential DoS. + * + * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. */ function sort( address[] memory array, @@ -80,6 +84,8 @@ library Arrays { * array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may * consume more gas than is available in a block, leading to potential DoS. + * + * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. */ function sort( bytes32[] memory array, diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index 6a0bc4d4e73..a2a21adb2c9 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -20,6 +20,9 @@ import {Hashes} from "./Hashes.sol"; * OpenZeppelin's JavaScript library generates Merkle trees that are safe * against this attack out of the box. * + * IMPORTANT: Consider memory side-effects when using custom hashing functions + * that access memory in an unsafe way. + * * NOTE: This library supports proof verification for merkle trees built using * custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving * leaf inclusion in trees built using non-commutative hashing functions requires diff --git a/scripts/generate/templates/Arrays.js b/scripts/generate/templates/Arrays.js index 3a1e62237ee..0d3676a727d 100644 --- a/scripts/generate/templates/Arrays.js +++ b/scripts/generate/templates/Arrays.js @@ -26,6 +26,8 @@ const sort = type => `\ * array. Using it in view functions that are executed through \`eth_call\` is safe, but one should be very careful * when executing this as part of a transaction. If the array being sorted is too large, the sort operation may * consume more gas than is available in a block, leading to potential DoS. + * + * IMPORTANT: Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. */ function sort( ${type}[] memory array, diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 80df713c7a3..8f206c88056 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -26,6 +26,9 @@ import {Hashes} from "./Hashes.sol"; * OpenZeppelin's JavaScript library generates Merkle trees that are safe * against this attack out of the box. * + * IMPORTANT: Consider memory side-effects when using custom hashing functions + * that access memory in an unsafe way. + * * NOTE: This library supports proof verification for merkle trees built using * custom _commutative_ hashing functions (i.e. \`H(a, b) == H(b, a)\`). Proving * leaf inclusion in trees built using non-commutative hashing functions requires From 29a953954d308d0786403155db1eff9addcf6e2b Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 30 Aug 2024 21:31:00 +0200 Subject: [PATCH 29/81] Revert "Remove double declaration of `P` in P256 (#5159)" (#5180) --- contracts/utils/cryptography/P256.sol | 51 ++++++++++++++------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 3275907f9f2..83c9c975447 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -145,9 +145,10 @@ library P256 { */ function isValidPublicKey(bytes32 x, bytes32 y) internal pure returns (bool result) { assembly ("memory-safe") { - let lhs := mulmod(y, y, P) // y^2 - let rhs := addmod(mulmod(addmod(mulmod(x, x, P), A, P), x, P), B, P) // ((x^2 + a) * x) + b = x^3 + ax + b - result := and(and(lt(x, P), lt(y, P)), eq(lhs, rhs)) // Should conform with the Weierstrass equation + let p := P + let lhs := mulmod(y, y, p) // y^2 + let rhs := addmod(mulmod(addmod(mulmod(x, x, p), A, p), x, p), B, p) // ((x^2 + a) * x) + b = x^3 + ax + b + result := and(and(lt(x, p), lt(y, p)), eq(lhs, rhs)) // Should conform with the Weierstrass equation } } @@ -187,29 +188,30 @@ library P256 { uint256 z2 ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { assembly ("memory-safe") { + let p := P let z1 := mload(add(p1, 0x40)) - let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, P), z2, P), P) // s1 = y1*z2³ - let s2 := mulmod(y2, mulmod(mulmod(z1, z1, P), z1, P), P) // s2 = y2*z1³ - let r := addmod(s2, sub(P, s1), P) // r = s2-s1 - let u1 := mulmod(mload(p1), mulmod(z2, z2, P), P) // u1 = x1*z2² - let u2 := mulmod(x2, mulmod(z1, z1, P), P) // u2 = x2*z1² - let h := addmod(u2, sub(P, u1), P) // h = u2-u1 - let hh := mulmod(h, h, P) // h² + let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³ + let s2 := mulmod(y2, mulmod(mulmod(z1, z1, p), z1, p), p) // s2 = y2*z1³ + let r := addmod(s2, sub(p, s1), p) // r = s2-s1 + let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2² + let u2 := mulmod(x2, mulmod(z1, z1, p), p) // u2 = x2*z1² + let h := addmod(u2, sub(p, u1), p) // h = u2-u1 + let hh := mulmod(h, h, p) // h² // x' = r²-h³-2*u1*h² rx := addmod( - addmod(mulmod(r, r, P), sub(P, mulmod(h, hh, P)), P), - sub(P, mulmod(2, mulmod(u1, hh, P), P)), - P + addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p), + sub(p, mulmod(2, mulmod(u1, hh, p), p)), + p ) // y' = r*(u1*h²-x')-s1*h³ ry := addmod( - mulmod(r, addmod(mulmod(u1, hh, P), sub(P, rx), P), P), - sub(P, mulmod(s1, mulmod(h, hh, P), P)), - P + mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), + sub(p, mulmod(s1, mulmod(h, hh, p), p)), + p ) // z' = h*z1*z2 - rz := mulmod(h, mulmod(z1, z2, P), P) + rz := mulmod(h, mulmod(z1, z2, p), p) } } @@ -219,18 +221,19 @@ library P256 { */ function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) { assembly ("memory-safe") { - let yy := mulmod(y, y, P) - let zz := mulmod(z, z, P) - let s := mulmod(4, mulmod(x, yy, P), P) // s = 4*x*y² - let m := addmod(mulmod(3, mulmod(x, x, P), P), mulmod(A, mulmod(zz, zz, P), P), P) // m = 3*x²+a*z⁴ - let t := addmod(mulmod(m, m, P), sub(P, mulmod(2, s, P)), P) // t = m²-2*s + let p := P + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ + let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s // x' = t rx := t // y' = m*(s-t)-8*y⁴ - ry := addmod(mulmod(m, addmod(s, sub(P, t), P), P), sub(P, mulmod(8, mulmod(yy, yy, P), P)), P) + ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) // z' = 2*y*z - rz := mulmod(2, mulmod(y, z, P), P) + rz := mulmod(2, mulmod(y, z, p), p) } } From 373eabeca619c6d4281781d8568377c89b411863 Mon Sep 17 00:00:00 2001 From: cairo Date: Tue, 3 Sep 2024 14:29:26 +0200 Subject: [PATCH 30/81] Optimizations to P256 operations (#5181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hadrien Croubois Co-authored-by: Ernesto García --- contracts/utils/cryptography/P256.sol | 37 ++++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 83c9c975447..69717bdc51a 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -125,11 +125,12 @@ library P256 { return (0, 0); } + uint256 p = P; // cache P on the stack uint256 rx = uint256(r); - uint256 ry2 = addmod(mulmod(addmod(mulmod(rx, rx, P), A, P), rx, P), B, P); // weierstrass equation y² = x³ + a.x + b - uint256 ry = Math.modExp(ry2, P1DIV4, P); // This formula for sqrt work because P ≡ 3 (mod 4) - if (mulmod(ry, ry, P) != ry2) return (0, 0); // Sanity check - if (ry % 2 != v % 2) ry = P - ry; + uint256 ry2 = addmod(mulmod(addmod(mulmod(rx, rx, p), A, p), rx, p), B, p); // weierstrass equation y² = x³ + a.x + b + uint256 ry = Math.modExp(ry2, P1DIV4, p); // This formula for sqrt work because P ≡ 3 (mod 4) + if (mulmod(ry, ry, p) != ry2) return (0, 0); // Sanity check + if (ry % 2 != v % 2) ry = p - ry; JPoint[16] memory points = _preComputeJacobianPoints(rx, ry); uint256 w = Math.invModPrime(uint256(r), N); @@ -170,11 +171,13 @@ library P256 { */ function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (uint256 ax, uint256 ay) { if (jz == 0) return (0, 0); - uint256 zinv = Math.invModPrime(jz, P); - uint256 zzinv = mulmod(zinv, zinv, P); - uint256 zzzinv = mulmod(zzinv, zinv, P); - ax = mulmod(jx, zzinv, P); - ay = mulmod(jy, zzzinv, P); + uint256 p = P; // cache P on the stack + uint256 zinv = Math.invModPrime(jz, p); + assembly ("memory-safe") { + let zzinv := mulmod(zinv, zinv, p) + ax := mulmod(jx, zzinv, p) + ay := mulmod(jy, mulmod(zzinv, zinv, p), p) + } } /** @@ -190,12 +193,11 @@ library P256 { assembly ("memory-safe") { let p := P let z1 := mload(add(p1, 0x40)) + let zz1 := mulmod(z1, z1, p) // zz1 = z1² let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³ - let s2 := mulmod(y2, mulmod(mulmod(z1, z1, p), z1, p), p) // s2 = y2*z1³ - let r := addmod(s2, sub(p, s1), p) // r = s2-s1 + let r := addmod(mulmod(y2, mulmod(zz1, z1, p), p), sub(p, s1), p) // r = s2-s1 = y2*z1³-s1 let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2² - let u2 := mulmod(x2, mulmod(z1, z1, p), p) // u2 = x2*z1² - let h := addmod(u2, sub(p, u1), p) // h = u2-u1 + let h := addmod(mulmod(x2, zz1, p), sub(p, u1), p) // h = u2-u1 = x2*z1²-u1 let hh := mulmod(h, h, p) // h² // x' = r²-h³-2*u1*h² @@ -226,12 +228,11 @@ library P256 { let zz := mulmod(z, z, p) let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ - let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s - // x' = t - rx := t - // y' = m*(s-t)-8*y⁴ - ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) + // x' = t = m²-2*s + rx := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) + // y' = m*(s-t)-8*y⁴ = m*(s-x')-8*y⁴ + ry := addmod(mulmod(m, addmod(s, sub(p, rx), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) // z' = 2*y*z rz := mulmod(2, mulmod(y, z, p), p) } From 55fd53c6d2d516f0606a5830659e2ccb1fedb090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 3 Sep 2024 07:49:37 -0600 Subject: [PATCH 31/81] Add note about 100% royalty in IERC2981 (#5173) --- contracts/interfaces/IERC2981.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 9e7871df2ee..22b951dd84e 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -15,6 +15,9 @@ interface IERC2981 is IERC165 { /** * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of * exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + * + * NOTE: ERC-2981 allows setting the royalty to 100% of the price. In that case all the price would be sent to the + * royalty receiver and 0 tokens to the seller. Contracts dealing with royalty should consider empty transfers. */ function royaltyInfo( uint256 tokenId, From 9e970eb9faee9e1921cc41509ad0cc354f3e555b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 3 Sep 2024 09:04:18 -0600 Subject: [PATCH 32/81] Guidelines for named return values (#5178) --- GUIDELINES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/GUIDELINES.md b/GUIDELINES.md index 97fa7290cfe..a1418d6528a 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -131,6 +131,13 @@ In addition to the official Solidity Style Guide we have a number of other conve abstract contract AccessControl is ..., { ``` +* Return values are generally not named, unless they are not immediately clear or there are multiple return values. + + ```solidity + function expiration() public view returns (uint256) { // Good + function hasRole() public view returns (bool isMember, uint32 currentDelay) { // Good + ``` + * Unchecked arithmetic blocks should contain comments explaining why overflow is guaranteed not to happen. If the reason is immediately apparent from the line above the unchecked block, the comment may be omitted. * Custom errors should be declared following the [EIP-6093](https://eips.ethereum.org/EIPS/eip-6093) rationale whenever reasonable. Also, consider the following: From dfb3ec36b52ef4703a701c77e61a22b0a79c1359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 3 Sep 2024 11:37:09 -0600 Subject: [PATCH 33/81] Make padding skip clear in Base64URL encoding (#5176) --- contracts/utils/Base64.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index be0b7d687fb..ef9431b5578 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -23,6 +23,7 @@ library Base64 { /** * @dev Converts a `bytes` to its Bytes64Url `string` representation. + * Output is not padded with `=` as specified in https://www.rfc-editor.org/rfc/rfc4648[rfc4648]. */ function encodeURL(bytes memory data) internal pure returns (string memory) { return _encode(data, _TABLE_URL, false); From cb7faaf4db9d1ea443b507311487625220e5e215 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 4 Sep 2024 09:41:40 +0200 Subject: [PATCH 34/81] Add clone variant with per-instance immutable arguments (#5109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .changeset/four-chairs-help.md | 5 + contracts/proxy/Clones.sol | 141 +++++++++++++++++++++ test/proxy/Clones.t.sol | 36 +++++- test/proxy/Clones.test.js | 222 ++++++++++++++++++++++----------- 4 files changed, 331 insertions(+), 73 deletions(-) create mode 100644 .changeset/four-chairs-help.md diff --git a/.changeset/four-chairs-help.md b/.changeset/four-chairs-help.md new file mode 100644 index 00000000000..cbd0076075e --- /dev/null +++ b/.changeset/four-chairs-help.md @@ -0,0 +1,5 @@ +--- +"openzeppelin-solidity": minor +--- + +`Clones`: Add `cloneWithImmutableArgs` and `cloneDeterministicWithImmutableArgs` variants that create clones with per-instance immutable arguments. The immutable arguments can be retrieved using `fetchCloneArgs`. The corresponding `predictDeterministicWithImmutableArgs` function is also included. diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 097b43b43b3..454d7fb97d2 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.20; +import {Create2} from "../utils/Create2.sol"; import {Errors} from "../utils/Errors.sol"; /** @@ -17,6 +18,8 @@ import {Errors} from "../utils/Errors.sol"; * deterministic method. */ library Clones { + error CloneArgumentsTooLong(); + /** * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * @@ -118,4 +121,142 @@ library Clones { ) internal view returns (address predicted) { return predictDeterministicAddress(implementation, salt, address(this)); } + + /** + * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom + * immutable arguments. These are provided through `args` and cannot be changed after deployment. To + * access the arguments within the implementation, use {fetchCloneArgs}. + * + * This function uses the create opcode, which should never revert. + */ + function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) { + return cloneWithImmutableArgs(implementation, args, 0); + } + + /** + * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value` + * parameter to send native currency to the new contract. + * + * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) + * to always have enough balance for new deployments. Consider exposing this function under a payable method. + */ + function cloneWithImmutableArgs( + address implementation, + bytes memory args, + uint256 value + ) internal returns (address instance) { + if (address(this).balance < value) { + revert Errors.InsufficientBalance(address(this).balance, value); + } + bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); + assembly ("memory-safe") { + instance := create(value, add(bytecode, 0x20), mload(bytecode)) + } + if (instance == address(0)) { + revert Errors.FailedDeployment(); + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom + * immutable arguments. These are provided through `args` and cannot be changed after deployment. To + * access the arguments within the implementation, use {fetchCloneArgs}. + * + * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same + * `implementation` and `salt` multiple time will revert, since the clones cannot be deployed twice at the same + * address. + */ + function cloneDeterministicWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0); + } + + /** + * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs], + * but with a `value` parameter to send native currency to the new contract. + * + * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) + * to always have enough balance for new deployments. Consider exposing this function under a payable method. + */ + function cloneDeterministicWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt, + uint256 value + ) internal returns (address instance) { + bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); + return Create2.deploy(value, salt, bytecode); + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. + */ + function predictDeterministicAddressWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args); + return Create2.computeAddress(salt, keccak256(bytecode), deployer); + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}. + */ + function predictDeterministicAddressWithImmutableArgs( + address implementation, + bytes memory args, + bytes32 salt + ) internal view returns (address predicted) { + return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this)); + } + + /** + * @dev Get the immutable args attached to a clone. + * + * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this + * function will return an empty array. + * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or + * `cloneDeterministicWithImmutableArgs`, this function will return the args array used at + * creation. + * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This + * function should only be used to check addresses that are known to be clones. + */ + function fetchCloneArgs(address instance) internal view returns (bytes memory) { + bytes memory result = new bytes(instance.code.length - 0x2d); // revert if length is too short + assembly ("memory-safe") { + extcodecopy(instance, add(result, 0x20), 0x2d, mload(result)) + } + return result; + } + + /** + * @dev Helper that prepares the initcode of the proxy with immutable args. + * + * An assembly variant of this function requires copying the `args` array, which can be efficiently done using + * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using + * abi.encodePacked is more expensive but also more portable and easier to review. + * + * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes. + * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes. + */ + function _cloneCodeWithImmutableArgs( + address implementation, + bytes memory args + ) private pure returns (bytes memory) { + if (args.length > 0x5fd3) revert CloneArgumentsTooLong(); + return + abi.encodePacked( + hex"61", + uint16(args.length + 0x2d), + hex"3d81600a3d39f3363d3d373d3d3d363d73", + implementation, + hex"5af43d82803e903d91602b57fd5bf3", + args + ); + } } diff --git a/test/proxy/Clones.t.sol b/test/proxy/Clones.t.sol index 31b072b98de..e589ba90678 100644 --- a/test/proxy/Clones.t.sol +++ b/test/proxy/Clones.t.sol @@ -19,12 +19,28 @@ contract ClonesTest is Test { assertEq(spillage, bytes32(0)); } + function testSymbolicPredictDeterministicAddressWithImmutableArgsSpillage( + address implementation, + bytes32 salt, + bytes memory args + ) public { + vm.assume(args.length < 0xbfd3); + + address predicted = Clones.predictDeterministicAddressWithImmutableArgs(implementation, args, salt); + bytes32 spillage; + /// @solidity memory-safe-assembly + assembly { + spillage := and(predicted, 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) + } + assertEq(spillage, bytes32(0)); + } + function testCloneDirty() external { address cloneClean = Clones.clone(address(this)); address cloneDirty = Clones.clone(_dirty(address(this))); // both clones have the same code - assertEq(keccak256(cloneClean.code), keccak256(cloneDirty.code)); + assertEq(cloneClean.code, cloneDirty.code); // both clones behave as expected assertEq(ClonesTest(cloneClean).getNumber(), this.getNumber()); @@ -36,7 +52,7 @@ contract ClonesTest is Test { address cloneDirty = Clones.cloneDeterministic(_dirty(address(this)), ~salt); // both clones have the same code - assertEq(keccak256(cloneClean.code), keccak256(cloneDirty.code)); + assertEq(cloneClean.code, cloneDirty.code); // both clones behave as expected assertEq(ClonesTest(cloneClean).getNumber(), this.getNumber()); @@ -51,6 +67,22 @@ contract ClonesTest is Test { assertEq(predictClean, predictDirty); } + function testFetchCloneArgs(bytes memory args, bytes32 salt) external { + vm.assume(args.length < 0xbfd3); + + address instance1 = Clones.cloneWithImmutableArgs(address(this), args); + address instance2 = Clones.cloneDeterministicWithImmutableArgs(address(this), args, salt); + + // both clones have the same code + assertEq(instance1.code, instance2.code); + + // both clones behave as expected and args can be fetched + assertEq(ClonesTest(instance1).getNumber(), this.getNumber()); + assertEq(ClonesTest(instance2).getNumber(), this.getNumber()); + assertEq(Clones.fetchCloneArgs(instance1), args); + assertEq(Clones.fetchCloneArgs(instance2), args); + } + function _dirty(address input) private pure returns (address output) { assembly ("memory-safe") { output := or(input, shl(160, not(0))) diff --git a/test/proxy/Clones.test.js b/test/proxy/Clones.test.js index 70220fbf7a0..0706c6f834c 100644 --- a/test/proxy/Clones.test.js +++ b/test/proxy/Clones.test.js @@ -2,38 +2,77 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { generators } = require('../helpers/random'); + const shouldBehaveLikeClone = require('./Clones.behaviour'); +const cloneInitCode = (instance, args = undefined) => + args + ? ethers.concat([ + '0x61', + ethers.toBeHex(0x2d + ethers.getBytes(args).length, 2), + '0x3d81600a3d39f3363d3d373d3d3d363d73', + instance.target ?? instance.address ?? instance, + '0x5af43d82803e903d91602b57fd5bf3', + args, + ]) + : ethers.concat([ + '0x3d602d80600a3d3981f3363d3d373d3d3d363d73', + instance.target ?? instance.address ?? instance, + '0x5af43d82803e903d91602b57fd5bf3', + ]); + async function fixture() { const [deployer] = await ethers.getSigners(); const factory = await ethers.deployContract('$Clones'); const implementation = await ethers.deployContract('DummyImplementation'); - const newClone = async (opts = {}) => { - const clone = await factory.$clone.staticCall(implementation).then(address => implementation.attach(address)); - const tx = await (opts.deployValue - ? factory.$clone(implementation, ethers.Typed.uint256(opts.deployValue)) - : factory.$clone(implementation)); - if (opts.initData || opts.initValue) { - await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); - } - return Object.assign(clone, { deploymentTransaction: () => tx }); - }; - - const newCloneDeterministic = async (opts = {}) => { - const salt = opts.salt ?? ethers.randomBytes(32); - const clone = await factory.$cloneDeterministic - .staticCall(implementation, salt) - .then(address => implementation.attach(address)); - const tx = await (opts.deployValue - ? factory.$cloneDeterministic(implementation, salt, ethers.Typed.uint256(opts.deployValue)) - : factory.$cloneDeterministic(implementation, salt)); - if (opts.initData || opts.initValue) { - await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); - } - return Object.assign(clone, { deploymentTransaction: () => tx }); - }; + const newClone = + args => + async (opts = {}) => { + const clone = await (args + ? factory.$cloneWithImmutableArgs.staticCall(implementation, args) + : factory.$clone.staticCall(implementation) + ).then(address => implementation.attach(address)); + const tx = await (args + ? opts.deployValue + ? factory.$cloneWithImmutableArgs(implementation, args, ethers.Typed.uint256(opts.deployValue)) + : factory.$cloneWithImmutableArgs(implementation, args) + : opts.deployValue + ? factory.$clone(implementation, ethers.Typed.uint256(opts.deployValue)) + : factory.$clone(implementation)); + if (opts.initData || opts.initValue) { + await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); + } + return Object.assign(clone, { deploymentTransaction: () => tx }); + }; + + const newCloneDeterministic = + args => + async (opts = {}) => { + const salt = opts.salt ?? ethers.randomBytes(32); + const clone = await (args + ? factory.$cloneDeterministicWithImmutableArgs.staticCall(implementation, args, salt) + : factory.$cloneDeterministic.staticCall(implementation, salt) + ).then(address => implementation.attach(address)); + const tx = await (args + ? opts.deployValue + ? factory.$cloneDeterministicWithImmutableArgs( + implementation, + args, + salt, + ethers.Typed.uint256(opts.deployValue), + ) + : factory.$cloneDeterministicWithImmutableArgs(implementation, args, salt) + : opts.deployValue + ? factory.$cloneDeterministic(implementation, salt, ethers.Typed.uint256(opts.deployValue)) + : factory.$cloneDeterministic(implementation, salt)); + if (opts.initData || opts.initValue) { + await deployer.sendTransaction({ to: clone, value: opts.initValue ?? 0n, data: opts.initData ?? '0x' }); + } + return Object.assign(clone, { deploymentTransaction: () => tx }); + }; return { deployer, factory, implementation, newClone, newCloneDeterministic }; } @@ -43,53 +82,94 @@ describe('Clones', function () { Object.assign(this, await loadFixture(fixture)); }); - describe('clone', function () { - beforeEach(async function () { - this.createClone = this.newClone; - }); - - shouldBehaveLikeClone(); - }); - - describe('cloneDeterministic', function () { - beforeEach(async function () { - this.createClone = this.newCloneDeterministic; - }); - - shouldBehaveLikeClone(); - - it('revert if address already used', async function () { - const salt = ethers.randomBytes(32); - - // deploy once - await expect(this.factory.$cloneDeterministic(this.implementation, salt)).to.emit( - this.factory, - 'return$cloneDeterministic_address_bytes32', - ); - - // deploy twice - await expect(this.factory.$cloneDeterministic(this.implementation, salt)).to.be.revertedWithCustomError( - this.factory, - 'FailedDeployment', - ); - }); - - it('address prediction', async function () { - const salt = ethers.randomBytes(32); - - const creationCode = ethers.concat([ - '0x3d602d80600a3d3981f3363d3d373d3d3d363d73', - this.implementation.target, - '0x5af43d82803e903d91602b57fd5bf3', - ]); - - const predicted = await this.factory.$predictDeterministicAddress(this.implementation, salt); - const expected = ethers.getCreate2Address(this.factory.target, salt, ethers.keccak256(creationCode)); - expect(predicted).to.equal(expected); - - await expect(this.factory.$cloneDeterministic(this.implementation, salt)) - .to.emit(this.factory, 'return$cloneDeterministic_address_bytes32') - .withArgs(predicted); + for (const args of [undefined, '0x', '0x11223344']) { + describe(args ? `with immutable args: ${args}` : 'without immutable args', function () { + describe('clone', function () { + beforeEach(async function () { + this.createClone = this.newClone(args); + }); + + shouldBehaveLikeClone(); + + it('get immutable arguments', async function () { + const instance = await this.createClone(); + expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x'); + }); + }); + + describe('cloneDeterministic', function () { + beforeEach(async function () { + this.createClone = this.newCloneDeterministic(args); + }); + + shouldBehaveLikeClone(); + + it('get immutable arguments', async function () { + const instance = await this.createClone(); + expect(await this.factory.$fetchCloneArgs(instance)).to.equal(args ?? '0x'); + }); + + it('revert if address already used', async function () { + const salt = ethers.randomBytes(32); + + const deployClone = () => + args + ? this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt) + : this.factory.$cloneDeterministic(this.implementation, salt); + + // deploy once + await expect(deployClone()).to.not.be.reverted; + + // deploy twice + await expect(deployClone()).to.be.revertedWithCustomError(this.factory, 'FailedDeployment'); + }); + + it('address prediction', async function () { + const salt = ethers.randomBytes(32); + + const expected = ethers.getCreate2Address( + this.factory.target, + salt, + ethers.keccak256(cloneInitCode(this.implementation, args)), + ); + + if (args) { + const predicted = await this.factory.$predictDeterministicAddressWithImmutableArgs( + this.implementation, + args, + salt, + ); + expect(predicted).to.equal(expected); + + await expect(this.factory.$cloneDeterministicWithImmutableArgs(this.implementation, args, salt)) + .to.emit(this.factory, 'return$cloneDeterministicWithImmutableArgs_address_bytes_bytes32') + .withArgs(predicted); + } else { + const predicted = await this.factory.$predictDeterministicAddress(this.implementation, salt); + expect(predicted).to.equal(expected); + + await expect(this.factory.$cloneDeterministic(this.implementation, salt)) + .to.emit(this.factory, 'return$cloneDeterministic_address_bytes32') + .withArgs(predicted); + } + }); + }); }); + } + + it('EIP-170 limit on immutable args', async function () { + // EIP-170 limits the contract code size to 0x6000 + // This limits the length of immutable args to 0x5fd3 + const args = generators.hexBytes(0x5fd4); + const salt = ethers.randomBytes(32); + + await expect( + this.factory.$predictDeterministicAddressWithImmutableArgs(this.implementation, args, salt), + ).to.be.revertedWithCustomError(this.factory, 'CloneArgumentsTooLong'); + + await expect(this.factory.$cloneWithImmutableArgs(this.implementation, args)).to.be.revertedWithCustomError( + this.factory, + 'CloneArgumentsTooLong', + ); }); }); From 55d69573fc6c9cf2924efeb6265a4f4423a6c17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 5 Sep 2024 12:39:42 -0600 Subject: [PATCH 35/81] Clarify Governor events' lack of indexed parameters (#5175) Co-authored-by: Hadrien Croubois --- contracts/governance/IGovernor.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index a35aa18aba2..67abfdbbae4 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -8,6 +8,9 @@ import {IERC6372} from "../interfaces/IERC6372.sol"; /** * @dev Interface of the {Governor} core. + * + * NOTE: Event parameters lack the `indexed` keyword for compatibility with GovernorBravo events. + * Making event parameters `indexed` affects how events are decoded, potentially breaking existing indexers. */ interface IGovernor is IERC165, IERC6372 { enum ProposalState { From c01a0fa27fb2d1546958be5d2cbbdd3fb565e4fa Mon Sep 17 00:00:00 2001 From: cairo Date: Sun, 8 Sep 2024 12:32:56 +0200 Subject: [PATCH 36/81] Bump version of `ERC20TemporaryApproval` to transient minimum (#5191) --- .../token/ERC20/extensions/draft-ERC20TemporaryApproval.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index 74da55758cb..fa1e098a7cc 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {IERC20, ERC20} from "../ERC20.sol"; import {IERC7674} from "../../../interfaces/draft-IERC7674.sol"; From cae60c595b37b1e7ed7dd50ad0257387ec07c0cf Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 9 Sep 2024 21:49:34 +0200 Subject: [PATCH 37/81] Tag memory safe assembly blocks (#5193) --- contracts/governance/Governor.sol | 2 +- contracts/metatx/ERC2771Forwarder.sol | 2 +- contracts/utils/introspection/ERC165Checker.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index be2cf79d263..453f2029088 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -769,7 +769,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 // Extract what would be the `#proposer=0x` marker beginning the suffix bytes12 marker; - assembly { + assembly ("memory-safe") { // - Start of the string contents in memory = description + 32 // - First character of the marker = len - 52 // - Length of "#proposer=0x0000000000000000000000000000000000000000" = 52 diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index b5e943d920c..b66e7894b2c 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -286,7 +286,7 @@ contract ERC2771Forwarder is EIP712, Nonces { uint256 gasLeft; - assembly { + assembly ("memory-safe") { success := call(reqGas, to, value, add(data, 0x20), mload(data), 0, 0) gasLeft := gas() } diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index da729caf815..a0ac72c21bf 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -113,7 +113,7 @@ library ERC165Checker { bool success; uint256 returnSize; uint256 returnValue; - assembly { + assembly ("memory-safe") { success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) returnSize := returndatasize() returnValue := mload(0x00) From 6e224307b44bc4bd0cb60d408844e028cfa3e485 Mon Sep 17 00:00:00 2001 From: cairo Date: Fri, 13 Sep 2024 19:26:46 +0200 Subject: [PATCH 38/81] Fix typographical errors (#5194) --- contracts/interfaces/IERC1363Receiver.sol | 2 +- .../token/ERC1155/utils/ERC1155Utils.sol | 4 ++-- contracts/utils/cryptography/MerkleProof.sol | 8 +++---- contracts/utils/cryptography/RSA.sol | 6 ++--- contracts/utils/structs/CircularBuffer.sol | 2 +- contracts/utils/structs/Heap.sol | 8 +++---- scripts/generate/templates/Heap.js | 4 ++-- scripts/generate/templates/MerkleProof.js | 2 +- test/utils/structs/MerkleTree.test.js | 22 +++++++++---------- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/contracts/interfaces/IERC1363Receiver.sol b/contracts/interfaces/IERC1363Receiver.sol index ebcffe24e9c..279178849d6 100644 --- a/contracts/interfaces/IERC1363Receiver.sol +++ b/contracts/interfaces/IERC1363Receiver.sol @@ -18,7 +18,7 @@ interface IERC1363Receiver { * (i.e. 0x88a7ca5c, or its own function selector). * * @param operator The address which called `transferAndCall` or `transferFromAndCall` function. - * @param from The address which are tokens transferred from. + * @param from The address which the tokens are transferred from. * @param value The amount of tokens transferred. * @param data Additional data with no specified format. * @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` if transfer is allowed unless throwing. diff --git a/contracts/token/ERC1155/utils/ERC1155Utils.sol b/contracts/token/ERC1155/utils/ERC1155Utils.sol index a07539bd018..1439330e41c 100644 --- a/contracts/token/ERC1155/utils/ERC1155Utils.sol +++ b/contracts/token/ERC1155/utils/ERC1155Utils.sol @@ -15,7 +15,7 @@ library ERC1155Utils { * @dev Performs an acceptance check for the provided `operator` by calling {IERC1155-onERC1155Received} * on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`). * - * The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA). + * The acceptance call is not executed and treated as a no-op if the target address doesn't contain code (i.e. an EOA). * Otherwise, the recipient must implement {IERC1155Receiver-onERC1155Received} and return the acceptance magic value to accept * the transfer. */ @@ -50,7 +50,7 @@ library ERC1155Utils { * @dev Performs a batch acceptance check for the provided `operator` by calling {IERC1155-onERC1155BatchReceived} * on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`). * - * The acceptance call is not executed and treated as a no-op if the target address is doesn't contain code (i.e. an EOA). + * The acceptance call is not executed and treated as a no-op if the target address doesn't contain code (i.e. an EOA). * Otherwise, the recipient must implement {IERC1155Receiver-onERC1155Received} and return the acceptance magic value to accept * the transfer. */ diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index a2a21adb2c9..a8658013592 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -50,7 +50,7 @@ library MerkleProof { * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs - * of leafs & pre-images are assumed to be sorted. + * of leaves & pre-images are assumed to be sorted. * * This version handles proofs in memory with the default hashing function. */ @@ -83,7 +83,7 @@ library MerkleProof { * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs - * of leafs & pre-images are assumed to be sorted. + * of leaves & pre-images are assumed to be sorted. * * This version handles proofs in memory with a custom hashing function. */ @@ -115,7 +115,7 @@ library MerkleProof { * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs - * of leafs & pre-images are assumed to be sorted. + * of leaves & pre-images are assumed to be sorted. * * This version handles proofs in calldata with the default hashing function. */ @@ -148,7 +148,7 @@ library MerkleProof { * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs - * of leafs & pre-images are assumed to be sorted. + * of leaves & pre-images are assumed to be sorted. * * This version handles proofs in calldata with a custom hashing function. */ diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 105853aaec0..1b5860c3b2c 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -8,7 +8,7 @@ import {Math} from "../math/Math.sol"; * * This library supports PKCS#1 v1.5 padding to avoid malleability via chosen plaintext attacks in practical implementations. * The padding follows the EMSA-PKCS1-v1_5-ENCODE encoding definition as per section 9.2 of the RFC. This padding makes - * RSA semanticaly secure for signing messages. + * RSA semantically secure for signing messages. * * Inspired by https://github.com/adria0/SolRsaVerify[Adrià Massanet's work] */ @@ -26,7 +26,7 @@ library RSA { } /** - * @dev Verifies a PKCSv1.5 signature given a digest according the verification + * @dev Verifies a PKCSv1.5 signature given a digest according to the verification * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017]. * * IMPORTANT: Although this function allows for it, using n of length 1024 bits is considered unsafe. @@ -136,7 +136,7 @@ library RSA { /// @dev Reads a bytes32 from a bytes array without bounds checking. function _unsafeReadBytes32(bytes memory array, uint256 offset) private pure returns (bytes32 result) { - // Memory safetiness is guaranteed as long as the provided `array` is a Solidity-allocated bytes array + // Memory safeness is guaranteed as long as the provided `array` is a Solidity-allocated bytes array // and `offset` is within bounds. This is the case for all calls to this private function from {pkcs1}. assembly ("memory-safe") { result := mload(add(add(array, 0x20), offset)) diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index be9af9f8cbd..ae4e082595a 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -91,7 +91,7 @@ library CircularBuffer { } /** - * @dev Length of the buffer. This is the maximum number of elements kepts in the buffer. + * @dev Length of the buffer. This is the maximum number of elements kept in the buffer. */ function length(Bytes32CircularBuffer storage self) internal view returns (uint256) { return self._data.length; diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index 269c4723f34..24d97292fdf 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -43,7 +43,7 @@ library Heap { using SafeCast for *; /** - * @dev Binary heap that support values of type uint256. + * @dev Binary heap that supports values of type uint256. * * Each element of that structure uses 2 storage slots. */ @@ -235,7 +235,7 @@ library Heap { /** * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a - * comparator, and moving toward the leafs of the underlying tree. + * comparator, and moving toward the leaves of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These @@ -309,7 +309,7 @@ library Heap { } /** - * @dev Binary heap that support values of type uint208. + * @dev Binary heap that supports values of type uint208. * * Each element of that structure uses 1 storage slots. */ @@ -501,7 +501,7 @@ library Heap { /** * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a - * comparator, and moving toward the leafs of the underlying tree. + * comparator, and moving toward the leaves of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These diff --git a/scripts/generate/templates/Heap.js b/scripts/generate/templates/Heap.js index 558ed1b9115..aebc42e9ed6 100644 --- a/scripts/generate/templates/Heap.js +++ b/scripts/generate/templates/Heap.js @@ -45,7 +45,7 @@ import {Panic} from "../Panic.sol"; const generate = ({ struct, node, valueType, indexType, blockSize }) => `\ /** - * @dev Binary heap that support values of type ${valueType}. + * @dev Binary heap that supports values of type ${valueType}. * * Each element of that structure uses ${blockSize} storage slots. */ @@ -237,7 +237,7 @@ function _swap(${struct} storage self, ${indexType} i, ${indexType} j) private { /** * @dev Perform heap maintenance on \`self\`, starting at position \`pos\` (with the \`value\`), using \`comp\` as a - * comparator, and moving toward the leafs of the underlying tree. + * comparator, and moving toward the leaves of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`length\` * and \`value\` could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. These diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 8f206c88056..6711a87e28c 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -66,7 +66,7 @@ function verify${suffix}(${(hash ? formatArgsMultiline : formatArgsSingleLine)( * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up * from \`leaf\` using \`proof\`. A \`proof\` is valid if and only if the rebuilt * hash matches the root of the tree. When processing the proof, the pairs - * of leafs & pre-images are assumed to be sorted. + * of leaves & pre-images are assumed to be sorted. * * This version handles proofs in ${location} with ${hash ? 'a custom' : 'the default'} hashing function. */ diff --git a/test/utils/structs/MerkleTree.test.js b/test/utils/structs/MerkleTree.test.js index 2603ac2897d..bec39ceeadc 100644 --- a/test/utils/structs/MerkleTree.test.js +++ b/test/utils/structs/MerkleTree.test.js @@ -6,9 +6,9 @@ const { StandardMerkleTree } = require('@openzeppelin/merkle-tree'); const { generators } = require('../../helpers/random'); -const makeTree = (leafs = [ethers.ZeroHash]) => +const makeTree = (leaves = [ethers.ZeroHash]) => StandardMerkleTree.of( - leafs.map(leaf => [leaf]), + leaves.map(leaf => [leaf]), ['bytes32'], { sortLeaves: false }, ); @@ -39,15 +39,15 @@ describe('MerkleTree', function () { describe('push', function () { it('tree is correctly updated', async function () { - const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); + const leaves = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); // for each leaf slot - for (const i in leafs) { + for (const i in leaves) { // generate random leaf and hash it - const hashedLeaf = hashLeaf((leafs[i] = generators.bytes32())); + const hashedLeaf = hashLeaf((leaves[i] = generators.bytes32())); // update leaf list and rebuild tree. - const tree = makeTree(leafs); + const tree = makeTree(leaves); // push value to tree await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, i, tree.root); @@ -67,13 +67,13 @@ describe('MerkleTree', function () { it('reset', async function () { // empty tree - const zeroLeafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); - const zeroTree = makeTree(zeroLeafs); + const zeroLeaves = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); + const zeroTree = makeTree(zeroLeaves); // tree with one element - const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); - const hashedLeaf = hashLeaf((leafs[0] = generators.bytes32())); // fill first leaf and hash it - const tree = makeTree(leafs); + const leaves = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); + const hashedLeaf = hashLeaf((leaves[0] = generators.bytes32())); // fill first leaf and hash it + const tree = makeTree(leaves); // root should be that of a zero tree expect(await this.mock.root()).to.equal(zeroTree.root); From 37c35a1eaab77caa78abeb40008cab414329a182 Mon Sep 17 00:00:00 2001 From: cairo Date: Tue, 17 Sep 2024 22:39:45 +0200 Subject: [PATCH 39/81] Update to match Solidity pragma (#5198) --- contracts/utils/cryptography/Hashes.sol | 2 +- contracts/utils/structs/MerkleTree.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/Hashes.sol b/contracts/utils/cryptography/Hashes.sol index c78bc80fd79..85efd6d294e 100644 --- a/contracts/utils/cryptography/Hashes.sol +++ b/contracts/utils/cryptography/Hashes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; /** * @dev Library of standard hash functions. diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index 93f59ace585..c4933f79ef8 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import {Hashes} from "../cryptography/Hashes.sol"; import {Arrays} from "../Arrays.sol"; From 809ded806fe1d952027026b056ff6623ff2e2dfc Mon Sep 17 00:00:00 2001 From: cairo Date: Tue, 17 Sep 2024 22:42:06 +0200 Subject: [PATCH 40/81] Remove redundant modulo operation in P256 (#5200) --- contracts/utils/cryptography/P256.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 69717bdc51a..1b488c180be 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -130,7 +130,7 @@ library P256 { uint256 ry2 = addmod(mulmod(addmod(mulmod(rx, rx, p), A, p), rx, p), B, p); // weierstrass equation y² = x³ + a.x + b uint256 ry = Math.modExp(ry2, P1DIV4, p); // This formula for sqrt work because P ≡ 3 (mod 4) if (mulmod(ry, ry, p) != ry2) return (0, 0); // Sanity check - if (ry % 2 != v % 2) ry = p - ry; + if (ry % 2 != v) ry = p - ry; JPoint[16] memory points = _preComputeJacobianPoints(rx, ry); uint256 w = Math.invModPrime(uint256(r), N); From 3f901696f77997c060cc4c43fb77bfbd104713f3 Mon Sep 17 00:00:00 2001 From: cairo Date: Wed, 18 Sep 2024 18:21:17 +0200 Subject: [PATCH 41/81] Clean dirty addresses and booleans (#5195) Co-authored-by: Hadrien Croubois --- contracts/utils/SlotDerivation.sol | 4 ++-- scripts/generate/helpers/sanitize.js | 5 +++++ scripts/generate/templates/Packing.js | 9 +++++---- scripts/generate/templates/Slot.opts.js | 2 ++ scripts/generate/templates/SlotDerivation.js | 3 ++- .../generate/templates/SlotDerivation.t.js | 14 +++++++++++++ test/utils/SlotDerivation.t.sol | 20 +++++++++++++++++++ 7 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 scripts/generate/helpers/sanitize.js diff --git a/contracts/utils/SlotDerivation.sol b/contracts/utils/SlotDerivation.sol index 62c28a55fa6..c248edc01c1 100644 --- a/contracts/utils/SlotDerivation.sol +++ b/contracts/utils/SlotDerivation.sol @@ -70,7 +70,7 @@ library SlotDerivation { */ function deriveMapping(bytes32 slot, address key) internal pure returns (bytes32 result) { assembly ("memory-safe") { - mstore(0x00, key) + mstore(0x00, and(key, shr(96, not(0)))) mstore(0x20, slot) result := keccak256(0x00, 0x40) } @@ -81,7 +81,7 @@ library SlotDerivation { */ function deriveMapping(bytes32 slot, bool key) internal pure returns (bytes32 result) { assembly ("memory-safe") { - mstore(0x00, key) + mstore(0x00, iszero(iszero(key))) mstore(0x20, slot) result := keccak256(0x00, 0x40) } diff --git a/scripts/generate/helpers/sanitize.js b/scripts/generate/helpers/sanitize.js new file mode 100644 index 00000000000..e680ec1bf46 --- /dev/null +++ b/scripts/generate/helpers/sanitize.js @@ -0,0 +1,5 @@ +module.exports = { + address: expr => `and(${expr}, shr(96, not(0)))`, + bool: expr => `iszero(iszero(${expr}))`, + bytes: (expr, size) => `and(${expr}, shl(${256 - 8 * size}, not(0)))`, +}; diff --git a/scripts/generate/templates/Packing.js b/scripts/generate/templates/Packing.js index b9422ef4833..d841c2f816a 100644 --- a/scripts/generate/templates/Packing.js +++ b/scripts/generate/templates/Packing.js @@ -1,4 +1,5 @@ const format = require('../format-lines'); +const sanitize = require('../helpers/sanitize'); const { product } = require('../../helpers'); const { SIZES } = require('./Packing.opts'); @@ -44,8 +45,8 @@ function pack_${left}_${right}(bytes${left} left, bytes${right} right) internal left + right } result) { assembly ("memory-safe") { - left := and(left, shl(${256 - 8 * left}, not(0))) - right := and(right, shl(${256 - 8 * right}, not(0))) + left := ${sanitize.bytes('left', left)} + right := ${sanitize.bytes('right', right)} result := or(left, shr(${8 * left}, right)) } } @@ -55,7 +56,7 @@ const extract = (outer, inner) => `\ function extract_${outer}_${inner}(bytes${outer} self, uint8 offset) internal pure returns (bytes${inner} result) { if (offset > ${outer - inner}) revert OutOfRangeAccess(); assembly ("memory-safe") { - result := and(shl(mul(8, offset), self), shl(${256 - 8 * inner}, not(0))) + result := ${sanitize.bytes('shl(mul(8, offset), self)', inner)} } } `; @@ -64,7 +65,7 @@ const replace = (outer, inner) => `\ function replace_${outer}_${inner}(bytes${outer} self, bytes${inner} value, uint8 offset) internal pure returns (bytes${outer} result) { bytes${inner} oldValue = extract_${outer}_${inner}(self, offset); assembly ("memory-safe") { - value := and(value, shl(${256 - 8 * inner}, not(0))) + value := ${sanitize.bytes('value', inner)} result := xor(self, shr(mul(8, offset), xor(oldValue, value))) } } diff --git a/scripts/generate/templates/Slot.opts.js b/scripts/generate/templates/Slot.opts.js index aed1f988845..3eca2bcf08f 100644 --- a/scripts/generate/templates/Slot.opts.js +++ b/scripts/generate/templates/Slot.opts.js @@ -10,4 +10,6 @@ const TYPES = [ { type: 'bytes', isValueType: false }, ].map(type => Object.assign(type, { name: type.name ?? capitalize(type.type) })); +Object.assign(TYPES, Object.fromEntries(TYPES.map(entry => [entry.type, entry]))); + module.exports = { TYPES }; diff --git a/scripts/generate/templates/SlotDerivation.js b/scripts/generate/templates/SlotDerivation.js index 5311fb3c8d1..39d0d9e3508 100644 --- a/scripts/generate/templates/SlotDerivation.js +++ b/scripts/generate/templates/SlotDerivation.js @@ -1,4 +1,5 @@ const format = require('../format-lines'); +const sanitize = require('../helpers/sanitize'); const { TYPES } = require('./Slot.opts'); const header = `\ @@ -77,7 +78,7 @@ const mapping = ({ type }) => `\ */ function deriveMapping(bytes32 slot, ${type} key) internal pure returns (bytes32 result) { assembly ("memory-safe") { - mstore(0x00, key) + mstore(0x00, ${(sanitize[type] ?? (x => x))('key')}) mstore(0x20, slot) result := keccak256(0x00, 0x40) } diff --git a/scripts/generate/templates/SlotDerivation.t.js b/scripts/generate/templates/SlotDerivation.t.js index dc7b07ff45a..f03e1fc2598 100644 --- a/scripts/generate/templates/SlotDerivation.t.js +++ b/scripts/generate/templates/SlotDerivation.t.js @@ -61,6 +61,18 @@ function testSymbolicDeriveMapping${name}(${type} key) public { } `; +const mappingDirty = ({ type, name }) => `\ +function testSymbolicDeriveMapping${name}Dirty(bytes32 dirtyKey) public { + ${type} key; + assembly { + key := dirtyKey + } + + // run the "normal" test using a potentially dirty value + testSymbolicDeriveMapping${name}(key); +} +`; + const boundedMapping = ({ type, name }) => `\ mapping(${type} => bytes) private _${type}Mapping; @@ -107,6 +119,8 @@ module.exports = format( })), ), ).map(type => (type.isValueType ? mapping(type) : boundedMapping(type))), + mappingDirty(TYPES.bool), + mappingDirty(TYPES.address), ), ).trimEnd(), '}', diff --git a/test/utils/SlotDerivation.t.sol b/test/utils/SlotDerivation.t.sol index 300a58b5f14..4021f0f8796 100644 --- a/test/utils/SlotDerivation.t.sol +++ b/test/utils/SlotDerivation.t.sol @@ -225,4 +225,24 @@ contract SlotDerivationTest is Test, SymTest { assertEq(baseSlot.deriveMapping(key), derivedSlot); } + + function testSymbolicDeriveMappingBooleanDirty(bytes32 dirtyKey) public { + bool key; + assembly { + key := dirtyKey + } + + // run the "normal" test using a potentially dirty value + testSymbolicDeriveMappingBoolean(key); + } + + function testSymbolicDeriveMappingAddressDirty(bytes32 dirtyKey) public { + address key; + assembly { + key := dirtyKey + } + + // run the "normal" test using a potentially dirty value + testSymbolicDeriveMappingAddress(key); + } } From f20981528fa06e88ae97c9f599b9a313ad8186ce Mon Sep 17 00:00:00 2001 From: TechVoyagerX <164723159+techvoyagerX@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:18:31 +0800 Subject: [PATCH 42/81] refactor: enhance ERC6372 behavior test with detailed checks (#5164) --- test/governance/utils/ERC6372.behavior.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/governance/utils/ERC6372.behavior.js b/test/governance/utils/ERC6372.behavior.js index abcae43c7f3..32f27b59a7c 100644 --- a/test/governance/utils/ERC6372.behavior.js +++ b/test/governance/utils/ERC6372.behavior.js @@ -1,21 +1,24 @@ const { expect } = require('chai'); - const time = require('../../helpers/time'); function shouldBehaveLikeERC6372(mode = 'blocknumber') { - describe('should implement ERC-6372', function () { + describe(`ERC-6372 behavior in ${mode} mode`, function () { beforeEach(async function () { this.mock = this.mock ?? this.token ?? this.votes; }); - it('clock is correct', async function () { - expect(await this.mock.clock()).to.equal(await time.clock[mode]()); + it('should have a correct clock value', async function () { + const currentClock = await this.mock.clock(); + const expectedClock = await time.clock[mode](); + expect(currentClock).to.equal(expectedClock, `Clock mismatch in ${mode} mode`); }); - it('CLOCK_MODE is correct', async function () { - const params = new URLSearchParams(await this.mock.CLOCK_MODE()); - expect(params.get('mode')).to.equal(mode); - expect(params.get('from')).to.equal(mode == 'blocknumber' ? 'default' : null); + it('should have the correct CLOCK_MODE parameters', async function () { + const clockModeParams = new URLSearchParams(await this.mock.CLOCK_MODE()); + const expectedFromValue = mode === 'blocknumber' ? 'default' : null; + + expect(clockModeParams.get('mode')).to.equal(mode, `Expected mode to be ${mode}`); + expect(clockModeParams.get('from')).to.equal(expectedFromValue, `Expected 'from' to be ${expectedFromValue}`); }); }); } From 3cfebcb5c4fc4730bad4add1213ca52b85472dfa Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 19 Sep 2024 14:29:39 +0200 Subject: [PATCH 43/81] Refactor Heap.sol to remove `index` and `lookup` (#5190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/utils/structs/Heap.sol | 467 ++++-------------------- scripts/generate/run.js | 2 - scripts/generate/templates/Heap.js | 327 ----------------- scripts/generate/templates/Heap.opts.js | 13 - scripts/generate/templates/Heap.t.js | 89 ----- test/utils/structs/Heap.t.sol | 83 +---- test/utils/structs/Heap.test.js | 190 +++++----- 7 files changed, 159 insertions(+), 1012 deletions(-) delete mode 100644 scripts/generate/templates/Heap.js delete mode 100644 scripts/generate/templates/Heap.opts.js delete mode 100644 scripts/generate/templates/Heap.t.js diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index 24d97292fdf..c8e75f240a1 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -1,33 +1,25 @@ // SPDX-License-Identifier: MIT -// This file was procedurally generated from scripts/generate/templates/Heap.js. pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; import {SafeCast} from "../math/SafeCast.sol"; import {Comparators} from "../Comparators.sol"; +import {Arrays} from "../Arrays.sol"; import {Panic} from "../Panic.sol"; +import {StorageSlot} from "../StorageSlot.sol"; /** * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. * - * Heaps are represented as an array of Node objects. This array stores two overlapping structures: - * * A tree structure where the first element (index 0) is the root, and where the node at index i is the child of the - * node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node stores the index (in the array) - * where the corresponding value is stored. - * * A list of payloads values where each index contains a value and a lookup index. The type of the value depends on - * the variant being used. The lookup is the index of the node (in the tree) that points to this value. - * - * Some invariants: - * ``` - * i == heap.data[heap.data[i].index].lookup // for all indices i - * i == heap.data[heap.data[i].lookup].index // for all indices i - * ``` + * Heaps are represented as an tree of values where the first element (index 0) is the root, and where the node at + * index i is the child of the node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node + * stores an element of the heap. * * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at - * `heap.data[heap.data[0].index].value` + * `heap.tree[0].value` * * The structure is designed to perform the following operations with the corresponding complexities: * @@ -37,8 +29,13 @@ import {Panic} from "../Panic.sol"; * * replace (replace the highest priority value with a new value): O(log(n)) * * length (get the number of elements): O(1) * * clear (remove all elements): O(1) + * + * IMPORTANT: This library allows for the use of custom comparator functions. Given that manipulating + * memory can lead to unexpected behavior. Consider verifying that the comparator does not manipulate + * the Heap's state directly and that it follows the Solidity memory safety rules. */ library Heap { + using Arrays for *; using Math for *; using SafeCast for *; @@ -48,24 +45,15 @@ library Heap { * Each element of that structure uses 2 storage slots. */ struct Uint256Heap { - Uint256HeapNode[] data; - } - - /** - * @dev Internal node type for Uint256Heap. Stores a value of type uint256. - */ - struct Uint256HeapNode { - uint256 value; - uint64 index; // position -> value - uint64 lookup; // value -> position + uint256[] tree; } /** * @dev Lookup the root element of the heap. */ function peek(Uint256Heap storage self) internal view returns (uint256) { - // self.data[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. - return _unsafeNodeAccess(self, self.data[0].index).value; + // self.tree[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. + return self.tree[0]; } /** @@ -89,44 +77,19 @@ library Heap { function(uint256, uint256) view returns (bool) comp ) internal returns (uint256) { unchecked { - uint64 size = length(self); + uint256 size = length(self); if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - uint64 last = size - 1; - - // get root location (in the data array) and value - Uint256HeapNode storage rootNode = _unsafeNodeAccess(self, 0); - uint64 rootIdx = rootNode.index; - Uint256HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); - Uint256HeapNode storage lastNode = _unsafeNodeAccess(self, last); - uint256 rootDataValue = rootData.value; - - // if root is not the last element of the data array (that will get popped), reorder the data array. - if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get popped) - uint64 lastDataIdx = lastNode.lookup; - uint256 lastDataValue = lastNode.value; - // copy these values to the location of the root (that is safe, and that we no longer use) - rootData.value = lastDataValue; - rootData.lookup = lastDataIdx; - // update the tree node that used to point to that last element (value now located where the root was) - _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; - } - - // get last leaf location (in the data array) and value - uint64 lastIdx = lastNode.index; - uint256 lastValue = _unsafeNodeAccess(self, lastIdx).value; - - // move the last leaf to the root, pop last leaf ... - rootNode.index = lastIdx; - _unsafeNodeAccess(self, lastIdx).lookup = 0; - self.data.pop(); + // cache + uint256 rootValue = self.tree.unsafeAccess(0).value; + uint256 lastValue = self.tree.unsafeAccess(size - 1).value; - // ... and heapify - _siftDown(self, last, 0, lastValue, comp); + // swap last leaf with root, shrink tree and re-heapify + self.tree.pop(); + self.tree.unsafeAccess(0).value = lastValue; + _siftDown(self, size - 1, 0, lastValue, comp); - // return root value - return rootDataValue; + return rootValue; } } @@ -151,10 +114,10 @@ library Heap { uint256 value, function(uint256, uint256) view returns (bool) comp ) internal { - uint64 size = length(self); - if (size == type(uint64).max) Panic.panic(Panic.RESOURCE_ERROR); + uint256 size = length(self); - self.data.push(Uint256HeapNode({index: size, lookup: size, value: value})); + // push new item and re-heapify + self.tree.push(value); _siftUp(self, size, value, comp); } @@ -181,396 +144,108 @@ library Heap { uint256 newValue, function(uint256, uint256) view returns (bool) comp ) internal returns (uint256) { - uint64 size = length(self); + uint256 size = length(self); if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - // position of the node that holds the data for the root - uint64 rootIdx = _unsafeNodeAccess(self, 0).index; - // storage pointer to the node that holds the data for the root - Uint256HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); + // cache + uint256 oldValue = self.tree.unsafeAccess(0).value; - // cache old value and replace it - uint256 oldValue = rootData.value; - rootData.value = newValue; - - // re-heapify + // replace and re-heapify + self.tree.unsafeAccess(0).value = newValue; _siftDown(self, size, 0, newValue, comp); - // return old root value return oldValue; } /** * @dev Returns the number of elements in the heap. */ - function length(Uint256Heap storage self) internal view returns (uint64) { - return self.data.length.toUint64(); + function length(Uint256Heap storage self) internal view returns (uint256) { + return self.tree.length; } /** * @dev Removes all elements in the heap. */ function clear(Uint256Heap storage self) internal { - Uint256HeapNode[] storage data = self.data; - assembly ("memory-safe") { - sstore(data.slot, 0) - } + self.tree.unsafeSetLength(0); } /** * @dev Swap node `i` and `j` in the tree. */ - function _swap(Uint256Heap storage self, uint64 i, uint64 j) private { - Uint256HeapNode storage ni = _unsafeNodeAccess(self, i); - Uint256HeapNode storage nj = _unsafeNodeAccess(self, j); - uint64 ii = ni.index; - uint64 jj = nj.index; - // update pointers to the data (swap the value) - ni.index = jj; - nj.index = ii; - // update lookup pointers for consistency - _unsafeNodeAccess(self, ii).lookup = j; - _unsafeNodeAccess(self, jj).lookup = i; + function _swap(Uint256Heap storage self, uint256 i, uint256 j) private { + StorageSlot.Uint256Slot storage ni = self.tree.unsafeAccess(i); + StorageSlot.Uint256Slot storage nj = self.tree.unsafeAccess(j); + (ni.value, nj.value) = (nj.value, ni.value); } /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * @dev Perform heap maintenance on `self`, starting at `index` (with the `value`), using `comp` as a * comparator, and moving toward the leaves of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` - * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These + * and `value` could be extracted from `self` and `index`, but that would require redundant storage read. These * parameters are not verified. It is the caller role to make sure the parameters are correct. */ function _siftDown( Uint256Heap storage self, - uint64 size, - uint64 pos, + uint256 size, + uint256 index, uint256 value, function(uint256, uint256) view returns (bool) comp ) private { - uint256 left = 2 * pos + 1; // this could overflow uint64 - uint256 right = 2 * pos + 2; // this could overflow uint64 - - if (right < size) { - // the check guarantees that `left` and `right` are both valid uint64 - uint64 lIndex = uint64(left); - uint64 rIndex = uint64(right); - uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - uint256 rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; + // Check if there is a risk of overflow when computing the indices of the child nodes. If that is the case, + // there cannot be child nodes in the tree, so sifting is done. + if (index >= type(uint256).max / 2) return; + + // Compute the indices of the potential child nodes + uint256 lIndex = 2 * index + 1; + uint256 rIndex = 2 * index + 2; + + // Three cases: + // 1. Both children exist: sifting may continue on one of the branch (selection required) + // 2. Only left child exist: sifting may contineu on the left branch (no selection required) + // 3. Neither child exist: sifting is done + if (rIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; + uint256 rValue = self.tree.unsafeAccess(rIndex).value; if (comp(lValue, value) || comp(rValue, value)) { - uint64 index = uint64(comp(lValue, rValue).ternary(lIndex, rIndex)); - _swap(self, pos, index); - _siftDown(self, size, index, value, comp); + uint256 cIndex = comp(lValue, rValue).ternary(lIndex, rIndex); + _swap(self, index, cIndex); + _siftDown(self, size, cIndex, value, comp); } - } else if (left < size) { - // the check guarantees that `left` is a valid uint64 - uint64 lIndex = uint64(left); - uint256 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; + } else if (lIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; if (comp(lValue, value)) { - _swap(self, pos, lIndex); + _swap(self, index, lIndex); _siftDown(self, size, lIndex, value, comp); } } } /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a + * @dev Perform heap maintenance on `self`, starting at `index` (with the `value`), using `comp` as a * comparator, and moving toward the root of the underlying tree. * * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` - * could be extracted from `self` and `pos`, but that would require redundant storage read. These parameters are not + * could be extracted from `self` and `index`, but that would require redundant storage read. These parameters are not * verified. It is the caller role to make sure the parameters are correct. */ function _siftUp( Uint256Heap storage self, - uint64 pos, + uint256 index, uint256 value, function(uint256, uint256) view returns (bool) comp ) private { unchecked { - while (pos > 0) { - uint64 parent = (pos - 1) / 2; - uint256 parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; - if (comp(parentValue, value)) break; - _swap(self, pos, parent); - pos = parent; - } - } - } - - function _unsafeNodeAccess( - Uint256Heap storage self, - uint64 pos - ) private pure returns (Uint256HeapNode storage result) { - assembly ("memory-safe") { - mstore(0x00, self.slot) - result.slot := add(keccak256(0x00, 0x20), mul(pos, 2)) - } - } - - /** - * @dev Binary heap that supports values of type uint208. - * - * Each element of that structure uses 1 storage slots. - */ - struct Uint208Heap { - Uint208HeapNode[] data; - } - - /** - * @dev Internal node type for Uint208Heap. Stores a value of type uint208. - */ - struct Uint208HeapNode { - uint208 value; - uint24 index; // position -> value - uint24 lookup; // value -> position - } - - /** - * @dev Lookup the root element of the heap. - */ - function peek(Uint208Heap storage self) internal view returns (uint208) { - // self.data[0] will `ARRAY_ACCESS_OUT_OF_BOUNDS` panic if heap is empty. - return _unsafeNodeAccess(self, self.data[0].index).value; - } - - /** - * @dev Remove (and return) the root element for the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function pop(Uint208Heap storage self) internal returns (uint208) { - return pop(self, Comparators.lt); - } - - /** - * @dev Remove (and return) the root element for the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function pop( - Uint208Heap storage self, - function(uint256, uint256) view returns (bool) comp - ) internal returns (uint208) { - unchecked { - uint24 size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - uint24 last = size - 1; - - // get root location (in the data array) and value - Uint208HeapNode storage rootNode = _unsafeNodeAccess(self, 0); - uint24 rootIdx = rootNode.index; - Uint208HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); - Uint208HeapNode storage lastNode = _unsafeNodeAccess(self, last); - uint208 rootDataValue = rootData.value; - - // if root is not the last element of the data array (that will get popped), reorder the data array. - if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get popped) - uint24 lastDataIdx = lastNode.lookup; - uint208 lastDataValue = lastNode.value; - // copy these values to the location of the root (that is safe, and that we no longer use) - rootData.value = lastDataValue; - rootData.lookup = lastDataIdx; - // update the tree node that used to point to that last element (value now located where the root was) - _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; - } - - // get last leaf location (in the data array) and value - uint24 lastIdx = lastNode.index; - uint208 lastValue = _unsafeNodeAccess(self, lastIdx).value; - - // move the last leaf to the root, pop last leaf ... - rootNode.index = lastIdx; - _unsafeNodeAccess(self, lastIdx).lookup = 0; - self.data.pop(); - - // ... and heapify - _siftDown(self, last, 0, lastValue, comp); - - // return root value - return rootDataValue; - } - } - - /** - * @dev Insert a new element in the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function insert(Uint208Heap storage self, uint208 value) internal { - insert(self, value, Comparators.lt); - } - - /** - * @dev Insert a new element in the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function insert( - Uint208Heap storage self, - uint208 value, - function(uint256, uint256) view returns (bool) comp - ) internal { - uint24 size = length(self); - if (size == type(uint24).max) Panic.panic(Panic.RESOURCE_ERROR); - - self.data.push(Uint208HeapNode({index: size, lookup: size, value: value})); - _siftUp(self, size, value, comp); - } - - /** - * @dev Return the root element for the heap, and replace it with a new value, using the default comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function replace(Uint208Heap storage self, uint208 newValue) internal returns (uint208) { - return replace(self, newValue, Comparators.lt); - } - - /** - * @dev Return the root element for the heap, and replace it with a new value, using the provided comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ - function replace( - Uint208Heap storage self, - uint208 newValue, - function(uint256, uint256) view returns (bool) comp - ) internal returns (uint208) { - uint24 size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - // position of the node that holds the data for the root - uint24 rootIdx = _unsafeNodeAccess(self, 0).index; - // storage pointer to the node that holds the data for the root - Uint208HeapNode storage rootData = _unsafeNodeAccess(self, rootIdx); - - // cache old value and replace it - uint208 oldValue = rootData.value; - rootData.value = newValue; - - // re-heapify - _siftDown(self, size, 0, newValue, comp); - - // return old root value - return oldValue; - } - - /** - * @dev Returns the number of elements in the heap. - */ - function length(Uint208Heap storage self) internal view returns (uint24) { - return self.data.length.toUint24(); - } - - /** - * @dev Removes all elements in the heap. - */ - function clear(Uint208Heap storage self) internal { - Uint208HeapNode[] storage data = self.data; - assembly ("memory-safe") { - sstore(data.slot, 0) - } - } - - /** - * @dev Swap node `i` and `j` in the tree. - */ - function _swap(Uint208Heap storage self, uint24 i, uint24 j) private { - Uint208HeapNode storage ni = _unsafeNodeAccess(self, i); - Uint208HeapNode storage nj = _unsafeNodeAccess(self, j); - uint24 ii = ni.index; - uint24 jj = nj.index; - // update pointers to the data (swap the value) - ni.index = jj; - nj.index = ii; - // update lookup pointers for consistency - _unsafeNodeAccess(self, ii).lookup = j; - _unsafeNodeAccess(self, jj).lookup = i; - } - - /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a - * comparator, and moving toward the leaves of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` - * and `value` could be extracted from `self` and `pos`, but that would require redundant storage read. These - * parameters are not verified. It is the caller role to make sure the parameters are correct. - */ - function _siftDown( - Uint208Heap storage self, - uint24 size, - uint24 pos, - uint208 value, - function(uint256, uint256) view returns (bool) comp - ) private { - uint256 left = 2 * pos + 1; // this could overflow uint24 - uint256 right = 2 * pos + 2; // this could overflow uint24 - - if (right < size) { - // the check guarantees that `left` and `right` are both valid uint24 - uint24 lIndex = uint24(left); - uint24 rIndex = uint24(right); - uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - uint208 rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; - if (comp(lValue, value) || comp(rValue, value)) { - uint24 index = uint24(comp(lValue, rValue).ternary(lIndex, rIndex)); - _swap(self, pos, index); - _siftDown(self, size, index, value, comp); - } - } else if (left < size) { - // the check guarantees that `left` is a valid uint24 - uint24 lIndex = uint24(left); - uint208 lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - if (comp(lValue, value)) { - _swap(self, pos, lIndex); - _siftDown(self, size, lIndex, value, comp); - } - } - } - - /** - * @dev Perform heap maintenance on `self`, starting at position `pos` (with the `value`), using `comp` as a - * comparator, and moving toward the root of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. `value` - * could be extracted from `self` and `pos`, but that would require redundant storage read. These parameters are not - * verified. It is the caller role to make sure the parameters are correct. - */ - function _siftUp( - Uint208Heap storage self, - uint24 pos, - uint208 value, - function(uint256, uint256) view returns (bool) comp - ) private { - unchecked { - while (pos > 0) { - uint24 parent = (pos - 1) / 2; - uint208 parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; + while (index > 0) { + uint256 parentIndex = (index - 1) / 2; + uint256 parentValue = self.tree.unsafeAccess(parentIndex).value; if (comp(parentValue, value)) break; - _swap(self, pos, parent); - pos = parent; + _swap(self, index, parentIndex); + index = parentIndex; } } } - - function _unsafeNodeAccess( - Uint208Heap storage self, - uint24 pos - ) private pure returns (Uint208HeapNode storage result) { - assembly ("memory-safe") { - mstore(0x00, self.slot) - result.slot := add(keccak256(0x00, 0x20), pos) - } - } } diff --git a/scripts/generate/run.js b/scripts/generate/run.js index d49105c5a85..c28d1175b12 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -37,7 +37,6 @@ for (const [file, template] of Object.entries({ 'utils/structs/Checkpoints.sol': './templates/Checkpoints.js', 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', - 'utils/structs/Heap.sol': './templates/Heap.js', 'utils/SlotDerivation.sol': './templates/SlotDerivation.js', 'utils/StorageSlot.sol': './templates/StorageSlot.js', 'utils/Arrays.sol': './templates/Arrays.js', @@ -50,7 +49,6 @@ for (const [file, template] of Object.entries({ // Tests for (const [file, template] of Object.entries({ 'utils/structs/Checkpoints.t.sol': './templates/Checkpoints.t.js', - 'utils/structs/Heap.t.sol': './templates/Heap.t.js', 'utils/Packing.t.sol': './templates/Packing.t.js', 'utils/SlotDerivation.t.sol': './templates/SlotDerivation.t.js', })) { diff --git a/scripts/generate/templates/Heap.js b/scripts/generate/templates/Heap.js deleted file mode 100644 index aebc42e9ed6..00000000000 --- a/scripts/generate/templates/Heap.js +++ /dev/null @@ -1,327 +0,0 @@ -const format = require('../format-lines'); -const { TYPES } = require('./Heap.opts'); -const { capitalize } = require('../../helpers'); - -/* eslint-disable max-len */ -const header = `\ -pragma solidity ^0.8.20; - -import {Math} from "../math/Math.sol"; -import {SafeCast} from "../math/SafeCast.sol"; -import {Comparators} from "../Comparators.sol"; -import {Panic} from "../Panic.sol"; - -/** - * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as - * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. - * - * Heaps are represented as an array of Node objects. This array stores two overlapping structures: - * * A tree structure where the first element (index 0) is the root, and where the node at index i is the child of the - * node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node stores the index (in the array) - * where the corresponding value is stored. - * * A list of payloads values where each index contains a value and a lookup index. The type of the value depends on - * the variant being used. The lookup is the index of the node (in the tree) that points to this value. - * - * Some invariants: - * \`\`\` - * i == heap.data[heap.data[i].index].lookup // for all indices i - * i == heap.data[heap.data[i].lookup].index // for all indices i - * \`\`\` - * - * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the - * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at - * \`heap.data[heap.data[0].index].value\` - * - * The structure is designed to perform the following operations with the corresponding complexities: - * - * * peek (get the highest priority value): O(1) - * * insert (insert a value): O(log(n)) - * * pop (remove the highest priority value): O(log(n)) - * * replace (replace the highest priority value with a new value): O(log(n)) - * * length (get the number of elements): O(1) - * * clear (remove all elements): O(1) - */ -`; - -const generate = ({ struct, node, valueType, indexType, blockSize }) => `\ -/** - * @dev Binary heap that supports values of type ${valueType}. - * - * Each element of that structure uses ${blockSize} storage slots. - */ -struct ${struct} { - ${node}[] data; -} - -/** - * @dev Internal node type for ${struct}. Stores a value of type ${valueType}. - */ -struct ${node} { - ${valueType} value; - ${indexType} index; // position -> value - ${indexType} lookup; // value -> position -} - -/** - * @dev Lookup the root element of the heap. - */ -function peek(${struct} storage self) internal view returns (${valueType}) { - // self.data[0] will \`ARRAY_ACCESS_OUT_OF_BOUNDS\` panic if heap is empty. - return _unsafeNodeAccess(self, self.data[0].index).value; -} - -/** - * @dev Remove (and return) the root element for the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function pop(${struct} storage self) internal returns (${valueType}) { - return pop(self, Comparators.lt); -} - -/** - * @dev Remove (and return) the root element for the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function pop( - ${struct} storage self, - function(uint256, uint256) view returns (bool) comp -) internal returns (${valueType}) { - unchecked { - ${indexType} size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - ${indexType} last = size - 1; - - // get root location (in the data array) and value - ${node} storage rootNode = _unsafeNodeAccess(self, 0); - ${indexType} rootIdx = rootNode.index; - ${node} storage rootData = _unsafeNodeAccess(self, rootIdx); - ${node} storage lastNode = _unsafeNodeAccess(self, last); - ${valueType} rootDataValue = rootData.value; - - // if root is not the last element of the data array (that will get popped), reorder the data array. - if (rootIdx != last) { - // get details about the value stored in the last element of the array (that will get popped) - ${indexType} lastDataIdx = lastNode.lookup; - ${valueType} lastDataValue = lastNode.value; - // copy these values to the location of the root (that is safe, and that we no longer use) - rootData.value = lastDataValue; - rootData.lookup = lastDataIdx; - // update the tree node that used to point to that last element (value now located where the root was) - _unsafeNodeAccess(self, lastDataIdx).index = rootIdx; - } - - // get last leaf location (in the data array) and value - ${indexType} lastIdx = lastNode.index; - ${valueType} lastValue = _unsafeNodeAccess(self, lastIdx).value; - - // move the last leaf to the root, pop last leaf ... - rootNode.index = lastIdx; - _unsafeNodeAccess(self, lastIdx).lookup = 0; - self.data.pop(); - - // ... and heapify - _siftDown(self, last, 0, lastValue, comp); - - // return root value - return rootDataValue; - } -} - -/** - * @dev Insert a new element in the heap using the default comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function insert(${struct} storage self, ${valueType} value) internal { - insert(self, value, Comparators.lt); -} - -/** - * @dev Insert a new element in the heap using the provided comparator. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function insert( - ${struct} storage self, - ${valueType} value, - function(uint256, uint256) view returns (bool) comp -) internal { - ${indexType} size = length(self); - if (size == type(${indexType}).max) Panic.panic(Panic.RESOURCE_ERROR); - - self.data.push(${struct}Node({index: size, lookup: size, value: value})); - _siftUp(self, size, value, comp); -} - -/** - * @dev Return the root element for the heap, and replace it with a new value, using the default comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function replace(${struct} storage self, ${valueType} newValue) internal returns (${valueType}) { - return replace(self, newValue, Comparators.lt); -} - -/** - * @dev Return the root element for the heap, and replace it with a new value, using the provided comparator. - * This is equivalent to using {pop} and {insert}, but requires only one rebalancing operation. - * - * NOTE: All inserting and removal from a heap should always be done using the same comparator. Mixing comparator - * during the lifecycle of a heap will result in undefined behavior. - */ -function replace( - ${struct} storage self, - ${valueType} newValue, - function(uint256, uint256) view returns (bool) comp -) internal returns (${valueType}) { - ${indexType} size = length(self); - if (size == 0) Panic.panic(Panic.EMPTY_ARRAY_POP); - - // position of the node that holds the data for the root - ${indexType} rootIdx = _unsafeNodeAccess(self, 0).index; - // storage pointer to the node that holds the data for the root - ${node} storage rootData = _unsafeNodeAccess(self, rootIdx); - - // cache old value and replace it - ${valueType} oldValue = rootData.value; - rootData.value = newValue; - - // re-heapify - _siftDown(self, size, 0, newValue, comp); - - // return old root value - return oldValue; -} - -/** - * @dev Returns the number of elements in the heap. - */ -function length(${struct} storage self) internal view returns (${indexType}) { - return self.data.length.to${capitalize(indexType)}(); -} - -/** - * @dev Removes all elements in the heap. - */ -function clear(${struct} storage self) internal { - ${struct}Node[] storage data = self.data; - assembly ("memory-safe") { - sstore(data.slot, 0) - } -} - -/** - * @dev Swap node \`i\` and \`j\` in the tree. - */ -function _swap(${struct} storage self, ${indexType} i, ${indexType} j) private { - ${node} storage ni = _unsafeNodeAccess(self, i); - ${node} storage nj = _unsafeNodeAccess(self, j); - ${indexType} ii = ni.index; - ${indexType} jj = nj.index; - // update pointers to the data (swap the value) - ni.index = jj; - nj.index = ii; - // update lookup pointers for consistency - _unsafeNodeAccess(self, ii).lookup = j; - _unsafeNodeAccess(self, jj).lookup = i; -} - -/** - * @dev Perform heap maintenance on \`self\`, starting at position \`pos\` (with the \`value\`), using \`comp\` as a - * comparator, and moving toward the leaves of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`length\` - * and \`value\` could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. These - * parameters are not verified. It is the caller role to make sure the parameters are correct. - */ -function _siftDown( - ${struct} storage self, - ${indexType} size, - ${indexType} pos, - ${valueType} value, - function(uint256, uint256) view returns (bool) comp -) private { - uint256 left = 2 * pos + 1; // this could overflow ${indexType} - uint256 right = 2 * pos + 2; // this could overflow ${indexType} - - if (right < size) { - // the check guarantees that \`left\` and \`right\` are both valid ${indexType} - ${indexType} lIndex = ${indexType}(left); - ${indexType} rIndex = ${indexType}(right); - ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - ${valueType} rValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, rIndex).index).value; - if (comp(lValue, value) || comp(rValue, value)) { - ${indexType} index = ${indexType}(comp(lValue, rValue).ternary(lIndex, rIndex)); - _swap(self, pos, index); - _siftDown(self, size, index, value, comp); - } - } else if (left < size) { - // the check guarantees that \`left\` is a valid ${indexType} - ${indexType} lIndex = ${indexType}(left); - ${valueType} lValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, lIndex).index).value; - if (comp(lValue, value)) { - _swap(self, pos, lIndex); - _siftDown(self, size, lIndex, value, comp); - } - } -} - -/** - * @dev Perform heap maintenance on \`self\`, starting at position \`pos\` (with the \`value\`), using \`comp\` as a - * comparator, and moving toward the root of the underlying tree. - * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. \`value\` - * could be extracted from \`self\` and \`pos\`, but that would require redundant storage read. These parameters are not - * verified. It is the caller role to make sure the parameters are correct. - */ -function _siftUp( - ${struct} storage self, - ${indexType} pos, - ${valueType} value, - function(uint256, uint256) view returns (bool) comp -) private { - unchecked { - while (pos > 0) { - ${indexType} parent = (pos - 1) / 2; - ${valueType} parentValue = _unsafeNodeAccess(self, _unsafeNodeAccess(self, parent).index).value; - if (comp(parentValue, value)) break; - _swap(self, pos, parent); - pos = parent; - } - } -} - -function _unsafeNodeAccess( - ${struct} storage self, - ${indexType} pos -) private pure returns (${node} storage result) { - assembly ("memory-safe") { - mstore(0x00, self.slot) - result.slot := add(keccak256(0x00, 0x20), ${blockSize == 1 ? 'pos' : `mul(pos, ${blockSize})`}) - } -} -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library Heap {', - format( - [].concat( - 'using Math for *;', - 'using SafeCast for *;', - '', - TYPES.map(type => generate(type)), - ), - ).trimEnd(), - '}', -); diff --git a/scripts/generate/templates/Heap.opts.js b/scripts/generate/templates/Heap.opts.js deleted file mode 100644 index 8b8be0afdfa..00000000000 --- a/scripts/generate/templates/Heap.opts.js +++ /dev/null @@ -1,13 +0,0 @@ -const makeType = (valueSize, indexSize) => ({ - struct: `Uint${valueSize}Heap`, - node: `Uint${valueSize}HeapNode`, - valueSize, - valueType: `uint${valueSize}`, - indexSize, - indexType: `uint${indexSize}`, - blockSize: Math.ceil((valueSize + 2 * indexSize) / 256), -}); - -module.exports = { - TYPES: [makeType(256, 64), makeType(208, 24)], -}; diff --git a/scripts/generate/templates/Heap.t.js b/scripts/generate/templates/Heap.t.js deleted file mode 100644 index 04b3152ba3a..00000000000 --- a/scripts/generate/templates/Heap.t.js +++ /dev/null @@ -1,89 +0,0 @@ -const format = require('../format-lines'); -const { TYPES } = require('./Heap.opts'); - -/* eslint-disable max-len */ -const header = `\ -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {Heap} from "@openzeppelin/contracts/utils/structs/Heap.sol"; -import {Comparators} from "@openzeppelin/contracts/utils/Comparators.sol"; -`; - -const generate = ({ struct, valueType }) => `\ -contract ${struct}Test is Test { - using Heap for Heap.${struct}; - - Heap.${struct} internal heap; - - function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { - for (uint32 i = 0; i < heap.length(); ++i) { - // lookups - assertEq(i, heap.data[heap.data[i].index].lookup); - assertEq(i, heap.data[heap.data[i].lookup].index); - - // ordering: each node has a value bigger then its parent - if (i > 0) - assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); - } - } - - function testFuzz(${valueType}[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i]); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.lt); - - min = Math.min(min, input[i]); - assertEq(heap.peek(), min); - } - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - ${valueType} top = heap.peek(); - ${valueType} pop = heap.pop(); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.lt); - - assertEq(pop, top); - assertGe(pop, max); - max = pop; - } - } - - function testFuzzGt(${valueType}[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i], Comparators.gt); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.gt); - - max = Math.max(max, input[i]); - assertEq(heap.peek(), max); - } - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - ${valueType} top = heap.peek(); - ${valueType} pop = heap.pop(Comparators.gt); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.gt); - - assertEq(pop, top); - assertLe(pop, min); - min = pop; - } - } -} -`; - -// GENERATE -module.exports = format(header, ...TYPES.map(type => generate(type))); diff --git a/test/utils/structs/Heap.t.sol b/test/utils/structs/Heap.t.sol index b9d0b98787c..434f37f669a 100644 --- a/test/utils/structs/Heap.t.sol +++ b/test/utils/structs/Heap.t.sol @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT -// This file was procedurally generated from scripts/generate/templates/Heap.t.js. pragma solidity ^0.8.20; @@ -14,14 +13,8 @@ contract Uint256HeapTest is Test { Heap.Uint256Heap internal heap; function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { - for (uint32 i = 0; i < heap.length(); ++i) { - // lookups - assertEq(i, heap.data[heap.data[i].index].lookup); - assertEq(i, heap.data[heap.data[i].lookup].index); - - // ordering: each node has a value bigger then its parent - if (i > 0) - assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); + for (uint32 i = 1; i < heap.length(); ++i) { + assertFalse(comp(heap.tree[i], heap.tree[(i - 1) / 2])); } } @@ -79,75 +72,3 @@ contract Uint256HeapTest is Test { } } } - -contract Uint208HeapTest is Test { - using Heap for Heap.Uint208Heap; - - Heap.Uint208Heap internal heap; - - function _validateHeap(function(uint256, uint256) view returns (bool) comp) internal { - for (uint32 i = 0; i < heap.length(); ++i) { - // lookups - assertEq(i, heap.data[heap.data[i].index].lookup); - assertEq(i, heap.data[heap.data[i].lookup].index); - - // ordering: each node has a value bigger then its parent - if (i > 0) - assertFalse(comp(heap.data[heap.data[i].index].value, heap.data[heap.data[(i - 1) / 2].index].value)); - } - } - - function testFuzz(uint208[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i]); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.lt); - - min = Math.min(min, input[i]); - assertEq(heap.peek(), min); - } - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - uint208 top = heap.peek(); - uint208 pop = heap.pop(); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.lt); - - assertEq(pop, top); - assertGe(pop, max); - max = pop; - } - } - - function testFuzzGt(uint208[] calldata input) public { - vm.assume(input.length < 0x20); - assertEq(heap.length(), 0); - - uint256 max = 0; - for (uint256 i = 0; i < input.length; ++i) { - heap.insert(input[i], Comparators.gt); - assertEq(heap.length(), i + 1); - _validateHeap(Comparators.gt); - - max = Math.max(max, input[i]); - assertEq(heap.peek(), max); - } - - uint256 min = type(uint256).max; - for (uint256 i = 0; i < input.length; ++i) { - uint208 top = heap.peek(); - uint208 pop = heap.pop(Comparators.gt); - assertEq(heap.length(), input.length - i - 1); - _validateHeap(Comparators.gt); - - assertEq(pop, top); - assertLe(pop, min); - min = pop; - } - } -} diff --git a/test/utils/structs/Heap.test.js b/test/utils/structs/Heap.test.js index 7e95a0e7adb..6d751205c7a 100644 --- a/test/utils/structs/Heap.test.js +++ b/test/utils/structs/Heap.test.js @@ -3,8 +3,6 @@ const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); -const { TYPES } = require('../../../scripts/generate/templates/Heap.opts'); - async function fixture() { const mock = await ethers.deployContract('$Heap'); return { mock }; @@ -15,117 +13,101 @@ describe('Heap', function () { Object.assign(this, await loadFixture(fixture)); }); - for (const { struct, valueType } of TYPES) { - describe(struct, function () { - const popEvent = `return$pop_Heap_${struct}`; - const replaceEvent = `return$replace_Heap_${struct}_${valueType}`; - - beforeEach(async function () { - this.helper = { - clear: (...args) => this.mock[`$clear_Heap_${struct}`](0, ...args), - insert: (...args) => this.mock[`$insert(uint256,${valueType})`](0, ...args), - replace: (...args) => this.mock[`$replace(uint256,${valueType})`](0, ...args), - length: (...args) => this.mock[`$length_Heap_${struct}`](0, ...args), - pop: (...args) => this.mock[`$pop_Heap_${struct}`](0, ...args), - peek: (...args) => this.mock[`$peek_Heap_${struct}`](0, ...args), - }; - }); - - it('starts empty', async function () { - expect(await this.helper.length()).to.equal(0n); - }); - - it('peek, pop and replace from empty', async function () { - await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - await expect(this.helper.replace(0n)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - }); - - it('clear', async function () { - await this.helper.insert(42n); - - expect(await this.helper.length()).to.equal(1n); - expect(await this.helper.peek()).to.equal(42n); + describe('Uint256Heap', function () { + it('starts empty', async function () { + expect(await this.mock.$length(0)).to.equal(0n); + }); - await this.helper.clear(); + it('peek, pop and replace from empty', async function () { + await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + await expect(this.mock.$replace(0, 0n)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + }); - expect(await this.helper.length()).to.equal(0n); - await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - }); + it('clear', async function () { + await this.mock.$insert(0, 42n); - it('support duplicated items', async function () { - expect(await this.helper.length()).to.equal(0n); + expect(await this.mock.$length(0)).to.equal(1n); + expect(await this.mock.$peek(0)).to.equal(42n); - // insert 5 times - await this.helper.insert(42n); - await this.helper.insert(42n); - await this.helper.insert(42n); - await this.helper.insert(42n); - await this.helper.insert(42n); + await this.mock.$clear(0); - // pop 5 times - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(42n); + expect(await this.mock.$length(0)).to.equal(0n); + await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + }); - // popping a 6th time panics - await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - }); + it('support duplicated items', async function () { + expect(await this.mock.$length(0)).to.equal(0n); + + // insert 5 times + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + await this.mock.$insert(0, 42n); + + // pop 5 times + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(42n); + + // popping a 6th time panics + await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + }); - it('insert, pop and replace', async function () { - const heap = []; - for (const { op, value } of [ - { op: 'insert', value: 712 }, // [712] - { op: 'insert', value: 20 }, // [20, 712] - { op: 'insert', value: 4337 }, // [20, 712, 4437] - { op: 'pop' }, // 20, [712, 4437] - { op: 'insert', value: 1559 }, // [712, 1559, 4437] - { op: 'insert', value: 165 }, // [165, 712, 1559, 4437] - { op: 'insert', value: 155 }, // [155, 165, 712, 1559, 4437] - { op: 'insert', value: 7702 }, // [155, 165, 712, 1559, 4437, 7702] - { op: 'pop' }, // 155, [165, 712, 1559, 4437, 7702] - { op: 'replace', value: 721 }, // 165, [712, 721, 1559, 4437, 7702] - { op: 'pop' }, // 712, [721, 1559, 4437, 7702] - { op: 'pop' }, // 721, [1559, 4437, 7702] - { op: 'pop' }, // 1559, [4437, 7702] - { op: 'pop' }, // 4437, [7702] - { op: 'pop' }, // 7702, [] - { op: 'pop' }, // panic - { op: 'replace', value: '1363' }, // panic - ]) { - switch (op) { - case 'insert': - await this.helper.insert(value); + it('insert, pop and replace', async function () { + const heap = []; + for (const { op, value } of [ + { op: 'insert', value: 712 }, // [712] + { op: 'insert', value: 20 }, // [20, 712] + { op: 'insert', value: 4337 }, // [20, 712, 4437] + { op: 'pop' }, // 20, [712, 4437] + { op: 'insert', value: 1559 }, // [712, 1559, 4437] + { op: 'insert', value: 165 }, // [165, 712, 1559, 4437] + { op: 'insert', value: 155 }, // [155, 165, 712, 1559, 4437] + { op: 'insert', value: 7702 }, // [155, 165, 712, 1559, 4437, 7702] + { op: 'pop' }, // 155, [165, 712, 1559, 4437, 7702] + { op: 'replace', value: 721 }, // 165, [712, 721, 1559, 4437, 7702] + { op: 'pop' }, // 712, [721, 1559, 4437, 7702] + { op: 'pop' }, // 721, [1559, 4437, 7702] + { op: 'pop' }, // 1559, [4437, 7702] + { op: 'pop' }, // 4437, [7702] + { op: 'pop' }, // 7702, [] + { op: 'pop' }, // panic + { op: 'replace', value: '1363' }, // panic + ]) { + switch (op) { + case 'insert': + await this.mock.$insert(0, value); + heap.push(value); + heap.sort((a, b) => a - b); + break; + case 'pop': + if (heap.length == 0) { + await expect(this.mock.$pop(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + } else { + await expect(this.mock.$pop(0)).to.emit(this.mock, 'return$pop').withArgs(heap.shift()); + } + break; + case 'replace': + if (heap.length == 0) { + await expect(this.mock.$replace(0, value)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); + } else { + await expect(this.mock.$replace(0, value)).to.emit(this.mock, 'return$replace').withArgs(heap.shift()); heap.push(value); heap.sort((a, b) => a - b); - break; - case 'pop': - if (heap.length == 0) { - await expect(this.helper.pop()).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - } else { - await expect(this.helper.pop()).to.emit(this.mock, popEvent).withArgs(heap.shift()); - } - break; - case 'replace': - if (heap.length == 0) { - await expect(this.helper.replace(value)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - } else { - await expect(this.helper.replace(value)).to.emit(this.mock, replaceEvent).withArgs(heap.shift()); - heap.push(value); - heap.sort((a, b) => a - b); - } - break; - } - expect(await this.helper.length()).to.equal(heap.length); - if (heap.length == 0) { - await expect(this.helper.peek()).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - } else { - expect(await this.helper.peek()).to.equal(heap[0]); - } + } + break; + } + expect(await this.mock.$length(0)).to.equal(heap.length); + if (heap.length == 0) { + await expect(this.mock.$peek(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); + } else { + expect(await this.mock.$peek(0)).to.equal(heap[0]); } - }); + } }); - } + }); }); From 204ffee5315f7abe72a3b09f41537bc319a10944 Mon Sep 17 00:00:00 2001 From: cairo Date: Thu, 19 Sep 2024 15:16:19 +0200 Subject: [PATCH 44/81] Clarify reference commits and licenses (#5205) --- contracts/utils/cryptography/P256.sol | 6 +++--- contracts/utils/cryptography/RSA.sol | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 1b488c180be..9aa7be3c007 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -11,9 +11,9 @@ import {Errors} from "../Errors.sol"; * and cryptographic standards. Some notable examples include Apple's Secure Enclave and Android's Keystore * as well as authentication protocols like FIDO2. * - * Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/main/src/Secp256r1.sol[implementation of itsobvioustech]. - * Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/master/contracts/Secp256r1.sol[maxrobot] and - * https://github.com/tdrerup/elliptic-curve-solidity/blob/master/contracts/curves/EllipticCurve.sol[tdrerup] implementations. + * Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/d3d423f28a4d8dfcb203c7fa0c47f42592a7378e/src/Secp256r1.sol[implementation of itsobvioustech] (GNU General Public License v3.0). + * Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/c4bb1b6e8ae89534d8db3a6b3a6b52219100520f/contracts/Secp256r1.sol[maxrobot] and + * https://github.com/tdrerup/elliptic-curve-solidity/blob/59a9c25957d4d190eff53b6610731d81a077a15e/contracts/curves/EllipticCurve.sol[tdrerup] implementations. */ library P256 { struct JPoint { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 1b5860c3b2c..9c2205d8f79 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -10,7 +10,7 @@ import {Math} from "../math/Math.sol"; * The padding follows the EMSA-PKCS1-v1_5-ENCODE encoding definition as per section 9.2 of the RFC. This padding makes * RSA semantically secure for signing messages. * - * Inspired by https://github.com/adria0/SolRsaVerify[Adrià Massanet's work] + * Inspired by https://github.com/adria0/SolRsaVerify/blob/79c6182cabb9102ea69d4a2e996816091d5f1cd1[Adrià Massanet's work] (GNU General Public License v3.0). */ library RSA { /** From f3825ab335662879d47a42236895c6cd2485e381 Mon Sep 17 00:00:00 2001 From: cairo Date: Thu, 19 Sep 2024 15:16:52 +0200 Subject: [PATCH 45/81] Clarify mapping named parameters (#5204) --- contracts/governance/extensions/GovernorCountingFractional.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index 5a553fbdb44..06e7897dc48 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -43,7 +43,7 @@ abstract contract GovernorCountingFractional is Governor { /** * @dev Mapping from proposal ID to vote tallies for that proposal. */ - mapping(uint256 => ProposalVote) private _proposalVotes; + mapping(uint256 proposalId => ProposalVote) private _proposalVotes; /** * @dev A fractional vote params uses more votes than are available for that user. From 8a309ab5ecab69455812e01829c6425059cc5e99 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Thu, 19 Sep 2024 15:54:54 +0200 Subject: [PATCH 46/81] Update documentation of helper interface (#5179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- .../proxy/transparent/TransparentUpgradeableProxy.sol | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 6344eb9fa5f..5304f4311bc 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -15,12 +15,7 @@ import {ProxyAdmin} from "./ProxyAdmin.sol"; * include them in the ABI so this interface must be used to interact with it. */ interface ITransparentUpgradeableProxy is IERC1967 { - /** - * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call - * encoded in `data`. - * - * See {UUPSUpgradeable-upgradeToAndCall} - */ + /// @dev See {UUPSUpgradeable-upgradeToAndCall} function upgradeToAndCall(address newImplementation, bytes calldata data) external payable; } From b1f6bbe69f70d106f4de55845d8abe51ef9b91dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 19 Sep 2024 09:08:52 -0600 Subject: [PATCH 47/81] Add note about memory manipulation in MerkleTree (#5213) --- contracts/utils/structs/MerkleTree.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index c4933f79ef8..c3768ed8c74 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -70,7 +70,10 @@ library MerkleTree { * should be pushed to it using the custom push function, which should be the same one as used during the setup. * * IMPORTANT: Providing a custom hashing function is a security-sensitive operation since it may - * compromise the soundness of the tree. Consider using functions from {Hashes}. + * compromise the soundness of the tree. + * + * NOTE: Consider verifying that the hashing function does not manipulate the memory state directly and that it + * follows the Solidity memory safety rules. Otherwise, it may lead to unexpected behavior. */ function setup( Bytes32PushTree storage self, From 530179a71f435e85ae9df9b9f12b5637cf229e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 19 Sep 2024 11:20:04 -0600 Subject: [PATCH 48/81] Disallow empty CircularBuffer setup (#5214) --- contracts/utils/structs/CircularBuffer.sol | 6 ++++++ test/utils/structs/CircularBuffer.test.js | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index ae4e082595a..1d0adf8196b 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -36,6 +36,11 @@ import {Panic} from "../Panic.sol"; * ``` */ library CircularBuffer { + /** + * @dev Error emitted when trying to setup a buffer with a size of 0. + */ + error InvalidBufferSize(); + /** * @dev Counts the number of items that have been pushed to the buffer. The residuo modulo _data.length indicates * where the next value should be stored. @@ -61,6 +66,7 @@ library CircularBuffer { * Consider a large buffer size may render the function unusable. */ function setup(Bytes32CircularBuffer storage self, uint256 size) internal { + if (size == 0) revert InvalidBufferSize(); clear(self); Arrays.unsafeSetLength(self._data, size); } diff --git a/test/utils/structs/CircularBuffer.test.js b/test/utils/structs/CircularBuffer.test.js index c3d61aaa823..e79ba6923b4 100644 --- a/test/utils/structs/CircularBuffer.test.js +++ b/test/utils/structs/CircularBuffer.test.js @@ -18,6 +18,10 @@ describe('CircularBuffer', function () { Object.assign(this, await loadFixture(fixture)); }); + it('reverts on invalid setup', async function () { + await expect(this.mock.$setup(0, 0)).to.be.revertedWithCustomError(this.mock, 'InvalidBufferSize'); + }); + it('starts empty', async function () { expect(await this.mock.$count(0)).to.equal(0n); expect(await this.mock.$length(0)).to.equal(LENGTH); From e866815c7d14bc34598bf9f6110c0f0398749d2f Mon Sep 17 00:00:00 2001 From: skyge <1506186404li@gmail.com> Date: Mon, 23 Sep 2024 23:16:54 +0800 Subject: [PATCH 49/81] Fix typo in ERC4626 docs (#5222) Co-authored-by: Hadrien Croubois --- docs/modules/ROOT/pages/erc4626.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc index 977d8160632..79388c0a2e7 100644 --- a/docs/modules/ROOT/pages/erc4626.adoc +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -29,7 +29,7 @@ image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale] === The attack -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor or the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. +When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. From 2f0bc58946db746c0d17a2b9d9a8e13f5a8edd7f Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 23 Sep 2024 17:17:10 +0200 Subject: [PATCH 50/81] Update and clarify documentation comments (#5206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hadrien Croubois Co-authored-by: Ernesto García --- contracts/finance/VestingWalletCliff.sol | 3 +- .../extensions/GovernorCountingFractional.sol | 2 +- contracts/interfaces/IERC1363Spender.sol | 2 +- contracts/utils/cryptography/P256.sol | 10 +- contracts/utils/cryptography/RSA.sol | 23 ++-- contracts/utils/structs/CircularBuffer.sol | 5 +- docs/modules/ROOT/pages/utilities.adoc | 2 +- test/utils/cryptography/RSA.test.js | 101 +++++++++--------- 8 files changed, 77 insertions(+), 71 deletions(-) diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index d98be640bc7..034dd9cf6ea 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -17,8 +17,7 @@ abstract contract VestingWalletCliff is VestingWallet { error InvalidCliffDuration(uint64 cliffSeconds, uint64 durationSeconds); /** - * @dev Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp, the - * vesting duration and the duration of the cliff of the vesting wallet. + * @dev Set the start timestamp of the vesting wallet cliff. */ constructor(uint64 cliffSeconds) { if (cliffSeconds > duration()) { diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index 06e7897dc48..3a4fb664a53 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -145,7 +145,7 @@ abstract contract GovernorCountingFractional is Governor { uint256 againstVotes = 0; uint256 forVotes = 0; uint256 abstainVotes = 0; - uint256 usedWeight; + uint256 usedWeight = 0; // For clarity of event indexing, fractional voting must be clearly advertised in the "support" field. // diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index b4bf3f42c3d..4e9aba7d7f7 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; /** - * @title ERC1363Spender + * @title IERC1363Spender * @dev Interface for any contract that wants to support `approveAndCall` * from ERC-1363 token contracts. */ diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 9aa7be3c007..60c5fec3d8b 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -142,7 +142,7 @@ library P256 { /** * @dev Checks if (x, y) are valid coordinates of a point on the curve. - * In particular this function checks that x <= P and y <= P. + * In particular this function checks that x < P and y < P. */ function isValidPublicKey(bytes32 x, bytes32 y) internal pure returns (bool result) { assembly ("memory-safe") { @@ -239,7 +239,7 @@ library P256 { } /** - * @dev Compute P·u1 + Q·u2 using the precomputed points for P and Q (see {_preComputeJacobianPoints}). + * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). * * Uses Strauss Shamir trick for EC multiplication * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method @@ -292,17 +292,17 @@ library P256 { points[0x04] = JPoint(GX, GY, 1); // 0,1 (g) points[0x02] = _jDoublePoint(points[0x01]); // 2,0 (2p) points[0x08] = _jDoublePoint(points[0x04]); // 0,2 (2g) - points[0x03] = _jAddPoint(points[0x01], points[0x02]); // 3,0 (3p) + points[0x03] = _jAddPoint(points[0x01], points[0x02]); // 3,0 (p+2p = 3p) points[0x05] = _jAddPoint(points[0x01], points[0x04]); // 1,1 (p+g) points[0x06] = _jAddPoint(points[0x02], points[0x04]); // 2,1 (2p+g) points[0x07] = _jAddPoint(points[0x03], points[0x04]); // 3,1 (3p+g) points[0x09] = _jAddPoint(points[0x01], points[0x08]); // 1,2 (p+2g) points[0x0a] = _jAddPoint(points[0x02], points[0x08]); // 2,2 (2p+2g) points[0x0b] = _jAddPoint(points[0x03], points[0x08]); // 3,2 (3p+2g) - points[0x0c] = _jAddPoint(points[0x04], points[0x08]); // 0,3 (g+2g) + points[0x0c] = _jAddPoint(points[0x04], points[0x08]); // 0,3 (g+2g = 3g) points[0x0d] = _jAddPoint(points[0x01], points[0x0c]); // 1,3 (p+3g) points[0x0e] = _jAddPoint(points[0x02], points[0x0c]); // 2,3 (2p+3g) - points[0x0f] = _jAddPoint(points[0x03], points[0x0C]); // 3,3 (3p+3g) + points[0x0f] = _jAddPoint(points[0x03], points[0x0c]); // 3,3 (3p+3g) } function _jAddPoint(JPoint memory p1, JPoint memory p2) private pure returns (JPoint memory) { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 9c2205d8f79..6d355c60e2f 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -14,7 +14,7 @@ import {Math} from "../math/Math.sol"; */ library RSA { /** - * @dev Same as {pkcs1} but using SHA256 to calculate the digest of `data`. + * @dev Same as {pkcs1Sha256} but using SHA256 to calculate the digest of `data`. */ function pkcs1Sha256( bytes memory data, @@ -22,15 +22,16 @@ library RSA { bytes memory e, bytes memory n ) internal view returns (bool) { - return pkcs1(sha256(data), s, e, n); + return pkcs1Sha256(sha256(data), s, e, n); } /** * @dev Verifies a PKCSv1.5 signature given a digest according to the verification - * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017]. + * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with support + * for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported). * - * IMPORTANT: Although this function allows for it, using n of length 1024 bits is considered unsafe. - * Consider using at least 2048 bits. + * IMPORTANT: For security reason, this function requires the signature and modulus to have a length of at least 2048 bits. + * If you use a smaller key, consider replacing it with a larger, more secure, one. * * WARNING: PKCS#1 v1.5 allows for replayability given the message may contain arbitrary optional parameters in the * DigestInfo. Consider using an onchain nonce or unique identifier to include in the message to prevent replay attacks. @@ -40,12 +41,12 @@ library RSA { * @param e is the exponent of the public key * @param n is the modulus of the public key */ - function pkcs1(bytes32 digest, bytes memory s, bytes memory e, bytes memory n) internal view returns (bool) { + function pkcs1Sha256(bytes32 digest, bytes memory s, bytes memory e, bytes memory n) internal view returns (bool) { unchecked { // cache and check length uint256 length = n.length; if ( - length < 0x40 || // PKCS#1 padding is slightly less than 0x40 bytes at the bare minimum + length < 0x100 || // Enforce 2048 bits minimum length != s.length // signature must have the same length as the finite field ) { return false; @@ -94,13 +95,13 @@ library RSA { // it should be at 32 (digest) + 2 bytes from the end. To those 34 bytes, we add the // OID (9 bytes) and its length (2 bytes) to get the position of the DigestInfo sequence, // which is expected to have a length of 0x31 when the NULL param is present or 0x2f if not. - if (bytes1(_unsafeReadBytes32(buffer, length - 50)) == 0x31) { + if (bytes1(_unsafeReadBytes32(buffer, length - 0x32)) == 0x31) { offset = 0x34; // 00 (1 byte) | SEQUENCE length (0x31) = 3031 (2 bytes) | SEQUENCE length (0x0d) = 300d (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes) // SHA256 OID = 608648016503040201 (9 bytes) | NULL = 0500 (2 bytes) (explicit) | OCTET_STRING length (0x20) = 0420 (2 bytes) params = 0x003031300d060960864801650304020105000420000000000000000000000000; mask = 0xffffffffffffffffffffffffffffffffffffffff000000000000000000000000; // (20 bytes) - } else if (bytes1(_unsafeReadBytes32(buffer, length - 48)) == 0x2F) { + } else if (bytes1(_unsafeReadBytes32(buffer, length - 0x30)) == 0x2F) { offset = 0x32; // 00 (1 byte) | SEQUENCE length (0x2f) = 302f (2 bytes) | SEQUENCE length (0x0b) = 300b (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes) // SHA256 OID = 608648016503040201 (9 bytes) | NULL = | OCTET_STRING length (0x20) = 0420 (2 bytes) @@ -111,7 +112,7 @@ library RSA { return false; } - // Length is at least 0x40 and offset is at most 0x34, so this is safe. There is always some padding. + // Length is at least 0x100 and offset is at most 0x34, so this is safe. There is always some padding. uint256 paddingEnd = length - offset; // The padding has variable (arbitrary) length, so we check it byte per byte in a loop. @@ -137,7 +138,7 @@ library RSA { /// @dev Reads a bytes32 from a bytes array without bounds checking. function _unsafeReadBytes32(bytes memory array, uint256 offset) private pure returns (bytes32 result) { // Memory safeness is guaranteed as long as the provided `array` is a Solidity-allocated bytes array - // and `offset` is within bounds. This is the case for all calls to this private function from {pkcs1}. + // and `offset` is within bounds. This is the case for all calls to this private function from {pkcs1Sha256}. assembly ("memory-safe") { result := mload(add(add(array, 0x20), offset)) } diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index 1d0adf8196b..faaad2f028b 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -49,8 +49,9 @@ library CircularBuffer { * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and * lead to unexpected behavior. * - * The last item is at data[(index - 1) % data.length] and the last item is at data[index % data.length]. This - * range can wrap around. + * In a full buffer: + * - The most recently pushed item (last) is at data[(index - 1) % data.length] + * - The oldest item (first) is at data[index % data.length] */ struct Bytes32CircularBuffer { uint256 _count; diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index b8afec4eabd..7b2edf11287 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -91,7 +91,7 @@ function _verify( bytes memory e, bytes memory n ) internal pure returns (bool) { - return data.pkcs1(signature, e, n); + return data.pkcs1Sha256(signature, e, n); } ---- diff --git a/test/utils/cryptography/RSA.test.js b/test/utils/cryptography/RSA.test.js index d717811f1dc..bdf33911fe0 100644 --- a/test/utils/cryptography/RSA.test.js +++ b/test/utils/cryptography/RSA.test.js @@ -1,6 +1,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { bytes, bytes32 } = ethers.Typed; const parse = require('./RSA.helper'); @@ -24,7 +25,7 @@ describe('RSA', function () { // const { sha224, sha256 } = require('@noble/hashes/sha256'); // const { sha384, sha512 } = require('@noble/hashes/sha512'); - if (test.SHAAlg === 'SHA256') { + if (test.SHAAlg === 'SHA256' && length >= 0x100) { const result = test.Result === 'P'; it(`signature length ${length} ${test.extra} ${result ? 'works' : 'fails'}`, async function () { @@ -33,65 +34,69 @@ describe('RSA', function () { const exp = '0x' + test.e; const mod = '0x' + test.n; - expect(await this.mock.$pkcs1(ethers.sha256(data), sig, exp, mod)).to.equal(result); - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.equal(result); + expect(await this.mock.$pkcs1Sha256(bytes32(ethers.sha256(data)), sig, exp, mod)).to.equal(result); + expect(await this.mock.$pkcs1Sha256(bytes(data), sig, exp, mod)).to.equal(result); }); } } }); describe('others tests', function () { - it('openssl', async function () { - const data = ethers.toUtf8Bytes('hello world'); - const sig = - '0x079bed733b48d69bdb03076cb17d9809072a5a765460bc72072d687dba492afe951d75b814f561f253ee5cc0f3d703b6eab5b5df635b03a5437c0a5c179309812f5b5c97650361c645bc99f806054de21eb187bc0a704ed38d3d4c2871a117c19b6da7e9a3d808481c46b22652d15b899ad3792da5419e50ee38759560002388'; - const exp = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001'; - const mod = - '0xdf3edde009b96bc5b03b48bd73fe70a3ad20eaf624d0dc1ba121a45cc739893741b7cf82acf1c91573ec8266538997c6699760148de57e54983191eca0176f518e547b85fe0bb7d9e150df19eee734cf5338219c7f8f7b13b39f5384179f62c135e544cb70be7505751f34568e06981095aeec4f3a887639718a3e11d48c240d'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.true; - }); + // > openssl genrsa -out private.pem 2048 + // > openssl rsa -in private.pem -outform der -pubout -out public.pem + // > openssl asn1parse -in public.pem -inform DER -strparse 19 + // > echo -n 'hello world!' | openssl dgst -sha256 -sign private.pem | xxd -p | tr -d \\n + const openssl = { + descr: 'openssl', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0x2ff4349940bf0db9bce422e316ac47e3d24b0a869acb05c9c46f74e17491177698b150f2a5996a6bf7d7c73e05af91ad78632115a7d95b823c462596486e56e8473b75a270ca4760cd83f244d5d3af81d2c7d188879abbc2992b22d51e22ffb725f0828c852ee44f81def383e0f92ebfa3c6d97ca5e52a4254f9a886680e3fb394c2a8a955849313dce2cb416f8a67974effd9a17d229146ce10a98684fb3d46a1e53ddaf831cdd2beed895532533c554ae087b2738a5c4cf0802e8062b2a599fd76d67b92eabffa8a92b24e08fbc866217502a4a3d9f6157e491bede3c1048fa8f2d804f66128e8a883018b0ec33a59e1086bf71ae5dc193d9815ca82892dbc', + exp: '0x010001', + mod: '0xDC1CE5F7B202464CD320B4F9E44FEE0A358BE7022AB155A5BDEE45B1AED3C5A19645D898E294CBA96EAD6929FD8FB4B23E9ADB4D3143A736232C32A8617A77B89F7D8399B9BE37F8349D111067F71D2F20237B9F1A7C1CF44819F9FA5AA030F563DCFB1CC59FFAA86BA2ABEE28D949FED0DF34071B7558950079E28CD9BBA4CAC2F0F86D7BBFB13363C792B5A70C9B279F0B43A264A7CB1A7C7C41FC6EC1D1C1125A6BECE3207AE582F74CE896B9AC18DB00C8985B70145217B831CC313FC06581E186BF70A2EEE2C3C065B5C91A89B2C099B4924CDBF5707D161BD83AC8D9FCA309AC75D63EACF21027C2C9C9F05994331CBDFDD24F9BC6C8B58D8F1824540B', + result: true, + }; // According to RFC4055, pg.5 and RFC8017, pg. 64, for SHA-1, and the SHA-2 family, // the algorithm parameter has to be NULL and both explicit NULL parameter and implicit // NULL parameter (ie, absent NULL parameter) are considered to be legal and equivalent. - it('rfc8017 implicit null parameter', async function () { - const data = ethers.toUtf8Bytes('hello world!'); - const sig = - '0xa0073057133ff3758e7e111b4d7441f1d8cbe4b2dd5ee4316a14264290dee5ed7f175716639bd9bb43a14e4f9fcb9e84dedd35e2205caac04828b2c053f68176d971ea88534dd2eeec903043c3469fc69c206b2a8694fd262488441ed8852280c3d4994e9d42bd1d575c7024095f1a20665925c2175e089c0d731471f6cc145404edf5559fd2276e45e448086f71c78d0cc6628fad394a34e51e8c10bc39bfe09ed2f5f742cc68bee899d0a41e4c75b7b80afd1c321d89ccd9fe8197c44624d91cc935dfa48de3c201099b5b417be748aef29248527e8bbb173cab76b48478d4177b338fe1f1244e64d7d23f07add560d5ad50b68d6649a49d7bc3db686daaa7'; - const exp = '0x03'; - const mod = - '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.true; - }); + const rfc4055 = { + descr: 'rfc8017 implicit null parameter', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0xa0073057133ff3758e7e111b4d7441f1d8cbe4b2dd5ee4316a14264290dee5ed7f175716639bd9bb43a14e4f9fcb9e84dedd35e2205caac04828b2c053f68176d971ea88534dd2eeec903043c3469fc69c206b2a8694fd262488441ed8852280c3d4994e9d42bd1d575c7024095f1a20665925c2175e089c0d731471f6cc145404edf5559fd2276e45e448086f71c78d0cc6628fad394a34e51e8c10bc39bfe09ed2f5f742cc68bee899d0a41e4c75b7b80afd1c321d89ccd9fe8197c44624d91cc935dfa48de3c201099b5b417be748aef29248527e8bbb173cab76b48478d4177b338fe1f1244e64d7d23f07add560d5ad50b68d6649a49d7bc3db686daaa7', + exp: '0x03', + mod: '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7', + result: true, + }; - it('returns false for a very short n', async function () { - const data = ethers.toUtf8Bytes('hello world!'); - const sig = '0x0102'; - const exp = '0x03'; - const mod = '0x0405'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.false; - }); + const shortN = { + descr: 'returns false for a very short n', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0x0102', + exp: '0x03', + mod: '0x0405', + result: false, + }; - it('returns false for a signature with different length to n', async function () { - const data = ethers.toUtf8Bytes('hello world!'); - const sig = '0x00112233'; - const exp = '0x03'; - const mod = - '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.false; - }); + const differentLength = { + descr: 'returns false for a signature with different length to n', + data: ethers.toUtf8Bytes('hello world!'), + sig: '0x00112233', + exp: '0x03', + mod: '0xe932ac92252f585b3a80a4dd76a897c8b7652952fe788f6ec8dd640587a1ee5647670a8ad4c2be0f9fa6e49c605adf77b5174230af7bd50e5d6d6d6d28ccf0a886a514cc72e51d209cc772a52ef419f6a953f3135929588ebe9b351fca61ced78f346fe00dbb6306e5c2a4c6dfc3779af85ab417371cf34d8387b9b30ae46d7a5ff5a655b8d8455f1b94ae736989d60a6f2fd5cadbffbd504c5a756a2e6bb5cecc13bca7503f6df8b52ace5c410997e98809db4dc30d943de4e812a47553dce54844a78e36401d13f77dc650619fed88d8b3926e3d8e319c80c744779ac5d6abe252896950917476ece5e8fc27d5f053d6018d91b502c4787558a002b9283da7', + result: false, + }; - it('returns false if s >= n', async function () { - // this is the openssl example where sig has been replaced by sig + mod - const data = ethers.toUtf8Bytes('hello world'); - const sig = - '0xe6dacb53450242618b3e502a257c08acb44b456c7931988da84f0cda8182b435d6d5453ac1e72b07c7dadf2747609b7d544d15f3f14081f9dbad9c48b7aa78d2bdafd81d630f19a0270d7911f4ec82b171e9a95889ffc9e740dc9fac89407a82d152ecb514967d4d9165e67ce0d7f39a3082657cdfca148a5fc2b3a7348c4795'; - const exp = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001'; - const mod = - '0xdf3edde009b96bc5b03b48bd73fe70a3ad20eaf624d0dc1ba121a45cc739893741b7cf82acf1c91573ec8266538997c6699760148de57e54983191eca0176f518e547b85fe0bb7d9e150df19eee734cf5338219c7f8f7b13b39f5384179f62c135e544cb70be7505751f34568e06981095aeec4f3a887639718a3e11d48c240d'; - expect(await this.mock.$pkcs1Sha256(data, sig, exp, mod)).to.be.false; - }); + // this is the openssl example where sig has been replaced by sig + mod + const sTooLarge = { + ...openssl, + descr: 'returns false if s >= n', + sig: ethers.toBeHex(ethers.toBigInt(openssl.sig) + ethers.toBigInt(openssl.mod)), + result: false, + }; + + for (const { descr, data, sig, exp, mod, result } of [openssl, rfc4055, shortN, differentLength, sTooLarge]) { + it(descr, async function () { + expect(await this.mock.$pkcs1Sha256(bytes(data), sig, exp, mod)).to.equal(result); + }); + } }); }); From cc67e0eb83a0aef1e0f3e44491344f93cf67ff2c Mon Sep 17 00:00:00 2001 From: PurrProof <149718167+PurrProof@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:34:12 +0200 Subject: [PATCH 51/81] Add comment and tests for zero address behavior in Ownable2Step.transferOwnership() (#5226) Co-authored-by: Hadrien Croubois --- contracts/access/Ownable2Step.sol | 2 ++ test/access/Ownable2Step.test.js | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index 46765c4daa8..8050ff2b030 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -37,6 +37,8 @@ abstract contract Ownable2Step is Ownable { /** * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. * Can only be called by the current owner. + * + * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer. */ function transferOwnership(address newOwner) public virtual override onlyOwner { _pendingOwner = newOwner; diff --git a/test/access/Ownable2Step.test.js b/test/access/Ownable2Step.test.js index 4c49e217015..5620a249132 100644 --- a/test/access/Ownable2Step.test.js +++ b/test/access/Ownable2Step.test.js @@ -81,5 +81,22 @@ describe('Ownable2Step', function () { expect(await this.ownable2Step.owner()).to.equal(this.accountA); }); + + it('allows the owner to cancel an initiated ownership transfer by setting newOwner to zero address', async function () { + // initiate ownership transfer to accountA + await this.ownable2Step.connect(this.owner).transferOwnership(this.accountA); + expect(await this.ownable2Step.pendingOwner()).to.equal(this.accountA); + + // cancel the ownership transfer by setting newOwner to zero address + await expect(this.ownable2Step.connect(this.owner).transferOwnership(ethers.ZeroAddress)) + .to.emit(this.ownable2Step, 'OwnershipTransferStarted') + .withArgs(this.owner, ethers.ZeroAddress); + expect(await this.ownable2Step.pendingOwner()).to.equal(ethers.ZeroAddress); + + // verify that accountA cannot accept ownership anymore + await expect(this.ownable2Step.connect(this.accountA).acceptOwnership()) + .to.be.revertedWithCustomError(this.ownable2Step, 'OwnableUnauthorizedAccount') + .withArgs(this.accountA); + }); }); }); From f6db28630c05d995017d91e4d21009ed637a6299 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Wed, 25 Sep 2024 17:53:54 -0400 Subject: [PATCH 52/81] Add P256 and RSA documentation sections (#5227) --- contracts/utils/README.adoc | 4 ++++ contracts/utils/cryptography/P256.sol | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 0ef3e5387c8..4b40a967efd 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -58,6 +58,10 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable {{ECDSA}} +{{P256}} + +{{RSA}} + {{EIP712}} {{MessageHashUtils}} diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 60c5fec3d8b..cd612af6a1c 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -242,10 +242,10 @@ library P256 { * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). * * Uses Strauss Shamir trick for EC multiplication - * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method - * we optimise on this a bit to do with 2 bits at a time rather than a single bit - * the individual points for a single pass are precomputed - * overall this reduces the number of additions while keeping the same number of doublings + * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method. + * We optimise on this a bit to do with 2 bits at a time rather than a single bit. + * The individual points for a single pass are precomputed. + * Overall this reduces the number of additions while keeping the same number of doublings. */ function _jMultShamir(JPoint[16] memory points, uint256 u1, uint256 u2) private view returns (uint256, uint256) { uint256 x = 0; From 4c481d6584d9a2f32cd347ea07aceed02ee875fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 25 Sep 2024 16:18:40 -0600 Subject: [PATCH 53/81] Implement 5.1 Full Audit Naming Suggestions (#5215) Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- contracts/utils/structs/Heap.sol | 62 +++++++++++++------------- contracts/utils/structs/MerkleTree.sol | 20 ++++----- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index c8e75f240a1..17a778f3aa7 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -13,13 +13,13 @@ import {StorageSlot} from "../StorageSlot.sol"; * @dev Library for managing https://en.wikipedia.org/wiki/Binary_heap[binary heap] that can be used as * https://en.wikipedia.org/wiki/Priority_queue[priority queue]. * - * Heaps are represented as an tree of values where the first element (index 0) is the root, and where the node at - * index i is the child of the node at index (i-1)/2 and the father of nodes at index 2*i+1 and 2*i+2. Each node + * Heaps are represented as a tree of values where the first element (index 0) is the root, and where the node at + * index i is the child of the node at index (i-1)/2 and the parent of nodes at index 2*i+1 and 2*i+2. Each node * stores an element of the heap. * * The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the * highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at - * `heap.tree[0].value` + * `heap.tree[0]` * * The structure is designed to perform the following operations with the corresponding complexities: * @@ -42,7 +42,7 @@ library Heap { /** * @dev Binary heap that supports values of type uint256. * - * Each element of that structure uses 2 storage slots. + * Each element of that structure uses one storage slot. */ struct Uint256Heap { uint256[] tree; @@ -184,7 +184,7 @@ library Heap { * @dev Perform heap maintenance on `self`, starting at `index` (with the `value`), using `comp` as a * comparator, and moving toward the leaves of the underlying tree. * - * NOTE: This is a private function that is called in a trusted context with already cached parameters. `length` + * NOTE: This is a private function that is called in a trusted context with already cached parameters. `size` * and `value` could be extracted from `self` and `index`, but that would require redundant storage read. These * parameters are not verified. It is the caller role to make sure the parameters are correct. */ @@ -195,31 +195,33 @@ library Heap { uint256 value, function(uint256, uint256) view returns (bool) comp ) private { - // Check if there is a risk of overflow when computing the indices of the child nodes. If that is the case, - // there cannot be child nodes in the tree, so sifting is done. - if (index >= type(uint256).max / 2) return; - - // Compute the indices of the potential child nodes - uint256 lIndex = 2 * index + 1; - uint256 rIndex = 2 * index + 2; - - // Three cases: - // 1. Both children exist: sifting may continue on one of the branch (selection required) - // 2. Only left child exist: sifting may contineu on the left branch (no selection required) - // 3. Neither child exist: sifting is done - if (rIndex < size) { - uint256 lValue = self.tree.unsafeAccess(lIndex).value; - uint256 rValue = self.tree.unsafeAccess(rIndex).value; - if (comp(lValue, value) || comp(rValue, value)) { - uint256 cIndex = comp(lValue, rValue).ternary(lIndex, rIndex); - _swap(self, index, cIndex); - _siftDown(self, size, cIndex, value, comp); - } - } else if (lIndex < size) { - uint256 lValue = self.tree.unsafeAccess(lIndex).value; - if (comp(lValue, value)) { - _swap(self, index, lIndex); - _siftDown(self, size, lIndex, value, comp); + unchecked { + // Check if there is a risk of overflow when computing the indices of the child nodes. If that is the case, + // there cannot be child nodes in the tree, so sifting is done. + if (index >= type(uint256).max / 2) return; + + // Compute the indices of the potential child nodes + uint256 lIndex = 2 * index + 1; + uint256 rIndex = 2 * index + 2; + + // Three cases: + // 1. Both children exist: sifting may continue on one of the branch (selection required) + // 2. Only left child exist: sifting may continue on the left branch (no selection required) + // 3. Neither child exist: sifting is done + if (rIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; + uint256 rValue = self.tree.unsafeAccess(rIndex).value; + if (comp(lValue, value) || comp(rValue, value)) { + uint256 cIndex = comp(lValue, rValue).ternary(lIndex, rIndex); + _swap(self, index, cIndex); + _siftDown(self, size, cIndex, value, comp); + } + } else if (lIndex < size) { + uint256 lValue = self.tree.unsafeAccess(lIndex).value; + if (comp(lValue, value)) { + _swap(self, index, lIndex); + _siftDown(self, size, lIndex, value, comp); + } } } } diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index c3768ed8c74..6a78c4b3c97 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -49,7 +49,7 @@ library MerkleTree { /** * @dev Initialize a {Bytes32PushTree} using {Hashes-commutativeKeccak256} to hash internal nodes. - * The capacity of the tree (i.e. number of leaves) is set to `2**levels`. + * The capacity of the tree (i.e. number of leaves) is set to `2**treeDepth`. * * Calling this function on MerkleTree that was already setup and used will reset it to a blank state. * @@ -59,8 +59,8 @@ library MerkleTree { * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing * empty leaves. It should be a value that is not expected to be part of the tree. */ - function setup(Bytes32PushTree storage self, uint8 levels, bytes32 zero) internal returns (bytes32 initialRoot) { - return setup(self, levels, zero, Hashes.commutativeKeccak256); + function setup(Bytes32PushTree storage self, uint8 treeDepth, bytes32 zero) internal returns (bytes32 initialRoot) { + return setup(self, treeDepth, zero, Hashes.commutativeKeccak256); } /** @@ -77,17 +77,17 @@ library MerkleTree { */ function setup( Bytes32PushTree storage self, - uint8 levels, + uint8 treeDepth, bytes32 zero, function(bytes32, bytes32) view returns (bytes32) fnHash ) internal returns (bytes32 initialRoot) { // Store depth in the dynamic array - Arrays.unsafeSetLength(self._sides, levels); - Arrays.unsafeSetLength(self._zeros, levels); + Arrays.unsafeSetLength(self._sides, treeDepth); + Arrays.unsafeSetLength(self._zeros, treeDepth); // Build each root of zero-filled subtrees bytes32 currentZero = zero; - for (uint32 i = 0; i < levels; ++i) { + for (uint32 i = 0; i < treeDepth; ++i) { Arrays.unsafeAccess(self._zeros, i).value = currentZero; currentZero = fnHash(currentZero, currentZero); } @@ -129,20 +129,20 @@ library MerkleTree { function(bytes32, bytes32) view returns (bytes32) fnHash ) internal returns (uint256 index, bytes32 newRoot) { // Cache read - uint256 levels = self._zeros.length; + uint256 treeDepth = depth(self); // Get leaf index index = self._nextLeafIndex++; // Check if tree is full. - if (index >= 1 << levels) { + if (index >= 1 << treeDepth) { Panic.panic(Panic.RESOURCE_ERROR); } // Rebuild branch from leaf to root uint256 currentIndex = index; bytes32 currentLevelHash = leaf; - for (uint32 i = 0; i < levels; i++) { + for (uint32 i = 0; i < treeDepth; i++) { // Reaching the parent node, is currentLevelHash the left child? bool isLeft = currentIndex % 2 == 0; From 414cb9e6fd8c1636c481da404caa9395cf1b91d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 25 Sep 2024 16:23:31 -0600 Subject: [PATCH 54/81] Consistently name multiple returned values (#5177) --- contracts/governance/Governor.sol | 2 +- .../governance/extensions/GovernorStorage.sol | 22 +++- contracts/governance/utils/Votes.sol | 2 +- contracts/metatx/ERC2771Forwarder.sol | 2 +- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- contracts/token/common/ERC2981.sol | 5 +- contracts/utils/cryptography/ECDSA.sol | 13 ++- contracts/utils/cryptography/P256.sol | 12 +- contracts/utils/structs/Checkpoints.sol | 36 +++++- contracts/utils/structs/EnumerableMap.sol | 110 +++++++++--------- contracts/utils/types/Time.sol | 9 +- scripts/generate/templates/Checkpoints.js | 12 +- scripts/generate/templates/EnumerableMap.js | 26 ++--- 13 files changed, 156 insertions(+), 97 deletions(-) diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 453f2029088..02adffcb39b 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -802,7 +802,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in * `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16` */ - function _tryHexToUint(bytes1 char) private pure returns (bool, uint8) { + function _tryHexToUint(bytes1 char) private pure returns (bool isHex, uint8 value) { uint8 c = uint8(char); unchecked { // Case 0-9 diff --git a/contracts/governance/extensions/GovernorStorage.sol b/contracts/governance/extensions/GovernorStorage.sol index 2547b553bc8..40f7b23d5ac 100644 --- a/contracts/governance/extensions/GovernorStorage.sol +++ b/contracts/governance/extensions/GovernorStorage.sol @@ -88,7 +88,12 @@ abstract contract GovernorStorage is Governor { */ function proposalDetails( uint256 proposalId - ) public view virtual returns (address[] memory, uint256[] memory, bytes[] memory, bytes32) { + ) + public + view + virtual + returns (address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) + { // here, using memory is more efficient than storage ProposalDetails memory details = _proposalDetails[proposalId]; if (details.descriptionHash == 0) { @@ -102,14 +107,19 @@ abstract contract GovernorStorage is Governor { */ function proposalDetailsAt( uint256 index - ) public view virtual returns (uint256, address[] memory, uint256[] memory, bytes[] memory, bytes32) { - uint256 proposalId = _proposalIds[index]; - ( + ) + public + view + virtual + returns ( + uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) = proposalDetails(proposalId); - return (proposalId, targets, values, calldatas, descriptionHash); + ) + { + proposalId = _proposalIds[index]; + (targets, values, calldatas, descriptionHash) = proposalDetails(proposalId); } } diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 2b4def2e0cb..9c3262b9789 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -232,7 +232,7 @@ abstract contract Votes is Context, EIP712, Nonces, IERC5805 { Checkpoints.Trace208 storage store, function(uint208, uint208) view returns (uint208) op, uint208 delta - ) private returns (uint208, uint208) { + ) private returns (uint208 oldValue, uint208 newValue) { return store.push(clock(), op(store.latest(), delta)); } diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index b66e7894b2c..0b0d185d87e 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -218,7 +218,7 @@ contract ERC2771Forwarder is EIP712, Nonces { */ function _recoverForwardRequestSigner( ForwardRequestData calldata request - ) internal view virtual returns (bool, address) { + ) internal view virtual returns (bool isValid, address signer) { (address recovered, ECDSA.RecoverError err, ) = _hashTypedDataV4( keccak256( abi.encode( diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index c71b14ad48c..73778b530ca 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -83,7 +83,7 @@ abstract contract ERC4626 is ERC20, IERC4626 { /** * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. */ - function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) { + function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool ok, uint8 assetDecimals) { (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( abi.encodeCall(IERC20Metadata.decimals, ()) ); diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 70e8f469144..7f56b275ee3 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -58,7 +58,10 @@ abstract contract ERC2981 is IERC2981, ERC165 { /** * @inheritdoc IERC2981 */ - function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual returns (address, uint256) { + function royaltyInfo( + uint256 tokenId, + uint256 salePrice + ) public view virtual returns (address receiver, uint256 amount) { RoyaltyInfo storage _royaltyInfo = _tokenRoyaltyInfo[tokenId]; address royaltyReceiver = _royaltyInfo.receiver; uint96 royaltyFraction = _royaltyInfo.royaltyFraction; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index 3736171bf9f..dc6534a8aa0 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -53,7 +53,10 @@ library ECDSA { * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] */ - function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { + function tryRecover( + bytes32 hash, + bytes memory signature + ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { if (signature.length == 65) { bytes32 r; bytes32 s; @@ -96,7 +99,11 @@ library ECDSA { * * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures] */ - function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { + function tryRecover( + bytes32 hash, + bytes32 r, + bytes32 vs + ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { unchecked { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); // We do not check for an overflow here since the shift operation results in 0 or 1. @@ -123,7 +130,7 @@ library ECDSA { uint8 v, bytes32 r, bytes32 s - ) internal pure returns (address, RecoverError, bytes32) { + ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) { // 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 // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index cd612af6a1c..eebedfd34f7 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -120,7 +120,7 @@ library P256 { * IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability. * To flip the `s` value, compute `s = N - s` and `v = 1 - v` if (`v = 0 | 1`). */ - function recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal view returns (bytes32, bytes32) { + function recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal view returns (bytes32 x, bytes32 y) { if (!_isProperSignature(r, s) || v > 1) { return (0, 0); } @@ -136,8 +136,8 @@ library P256 { uint256 w = Math.invModPrime(uint256(r), N); uint256 u1 = mulmod(N - (uint256(h) % N), w, N); uint256 u2 = mulmod(uint256(s), w, N); - (uint256 x, uint256 y) = _jMultShamir(points, u1, u2); - return (bytes32(x), bytes32(y)); + (uint256 xU, uint256 yU) = _jMultShamir(points, u1, u2); + return (bytes32(xU), bytes32(yU)); } /** @@ -247,7 +247,11 @@ library P256 { * The individual points for a single pass are precomputed. * Overall this reduces the number of additions while keeping the same number of doublings. */ - function _jMultShamir(JPoint[16] memory points, uint256 u1, uint256 u2) private view returns (uint256, uint256) { + function _jMultShamir( + JPoint[16] memory points, + uint256 u1, + uint256 u2 + ) private view returns (uint256 rx, uint256 ry) { uint256 x = 0; uint256 y = 0; uint256 z = 0; diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 8d1575e162e..39e18a1fe82 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -36,7 +36,11 @@ library Checkpoints { * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the * library. */ - function push(Trace224 storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { + function push( + Trace224 storage self, + uint32 key, + uint224 value + ) internal returns (uint224 oldValue, uint224 newValue) { return _insert(self._checkpoints, key, value); } @@ -127,7 +131,11 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ - function _insert(Checkpoint224[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { + function _insert( + Checkpoint224[] storage self, + uint32 key, + uint224 value + ) private returns (uint224 oldValue, uint224 newValue) { uint256 pos = self.length; if (pos > 0) { @@ -231,7 +239,11 @@ library Checkpoints { * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint48).max` key set will disable the * library. */ - function push(Trace208 storage self, uint48 key, uint208 value) internal returns (uint208, uint208) { + function push( + Trace208 storage self, + uint48 key, + uint208 value + ) internal returns (uint208 oldValue, uint208 newValue) { return _insert(self._checkpoints, key, value); } @@ -322,7 +334,11 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ - function _insert(Checkpoint208[] storage self, uint48 key, uint208 value) private returns (uint208, uint208) { + function _insert( + Checkpoint208[] storage self, + uint48 key, + uint208 value + ) private returns (uint208 oldValue, uint208 newValue) { uint256 pos = self.length; if (pos > 0) { @@ -426,7 +442,11 @@ library Checkpoints { * IMPORTANT: Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the * library. */ - function push(Trace160 storage self, uint96 key, uint160 value) internal returns (uint160, uint160) { + function push( + Trace160 storage self, + uint96 key, + uint160 value + ) internal returns (uint160 oldValue, uint160 newValue) { return _insert(self._checkpoints, key, value); } @@ -517,7 +537,11 @@ library Checkpoints { * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ - function _insert(Checkpoint160[] storage self, uint96 key, uint160 value) private returns (uint160, uint160) { + function _insert( + Checkpoint160[] storage self, + uint96 key, + uint160 value + ) private returns (uint160 oldValue, uint160 newValue) { uint256 pos = self.length; if (pos > 0) { diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index e61182e1fbd..d86e57f69d5 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -114,21 +114,21 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { - bytes32 key = map._keys.at(index); - return (key, map._values[key]); + function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32 key, bytes32 value) { + bytes32 atKey = map._keys.at(index); + return (atKey, map._values[atKey]); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { - bytes32 value = map._values[key]; - if (value == bytes32(0)) { + function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool exists, bytes32 value) { + bytes32 val = map._values[key]; + if (val == bytes32(0)) { return (contains(map, key), bytes32(0)); } else { - return (true, value); + return (true, val); } } @@ -208,18 +208,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (uint256(key), uint256(value)); + function at(UintToUintMap storage map, uint256 index) internal view returns (uint256 key, uint256 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (uint256(atKey), uint256(val)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); - return (success, uint256(value)); + function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, uint256(val)); } /** @@ -301,18 +301,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (uint256(key), address(uint160(uint256(value)))); + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256 key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (uint256(atKey), address(uint160(uint256(val)))); } /** * @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)))); + function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(val)))); } /** @@ -394,18 +394,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(UintToBytes32Map storage map, uint256 index) internal view returns (uint256, bytes32) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (uint256(key), value); + function at(UintToBytes32Map storage map, uint256 index) internal view returns (uint256 key, bytes32 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (uint256(atKey), val); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(UintToBytes32Map storage map, uint256 key) internal view returns (bool, bytes32) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); - return (success, value); + function tryGet(UintToBytes32Map storage map, uint256 key) internal view returns (bool exists, bytes32 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(key)); + return (success, val); } /** @@ -487,18 +487,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (address(uint160(uint256(key))), uint256(value)); + function at(AddressToUintMap storage map, uint256 index) internal view returns (address key, uint256 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (address(uint160(uint256(atKey))), uint256(val)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); - return (success, uint256(value)); + function tryGet(AddressToUintMap storage map, address key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, uint256(val)); } /** @@ -580,18 +580,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(AddressToAddressMap storage map, uint256 index) internal view returns (address, address) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (address(uint160(uint256(key))), address(uint160(uint256(value)))); + function at(AddressToAddressMap storage map, uint256 index) internal view returns (address key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (address(uint160(uint256(atKey))), address(uint160(uint256(val)))); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(AddressToAddressMap storage map, address key) internal view returns (bool, address) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); - return (success, address(uint160(uint256(value)))); + function tryGet(AddressToAddressMap storage map, address key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, address(uint160(uint256(val)))); } /** @@ -673,18 +673,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address, bytes32) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (address(uint160(uint256(key))), value); + function at(AddressToBytes32Map storage map, uint256 index) internal view returns (address key, bytes32 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (address(uint160(uint256(atKey))), val); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool, bytes32) { - (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); - return (success, value); + function tryGet(AddressToBytes32Map storage map, address key) internal view returns (bool exists, bytes32 value) { + (bool success, bytes32 val) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, val); } /** @@ -766,18 +766,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (key, uint256(value)); + function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32 key, uint256 value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (atKey, uint256(val)); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) { - (bool success, bytes32 value) = tryGet(map._inner, key); - return (success, uint256(value)); + function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool exists, uint256 value) { + (bool success, bytes32 val) = tryGet(map._inner, key); + return (success, uint256(val)); } /** @@ -859,18 +859,18 @@ library EnumerableMap { * * - `index` must be strictly less than {length}. */ - function at(Bytes32ToAddressMap storage map, uint256 index) internal view returns (bytes32, address) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (key, address(uint160(uint256(value)))); + function at(Bytes32ToAddressMap storage map, uint256 index) internal view returns (bytes32 key, address value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (atKey, address(uint160(uint256(val)))); } /** * @dev Tries to returns the value associated with `key`. O(1). * Does not revert if `key` is not in the map. */ - function tryGet(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool, address) { - (bool success, bytes32 value) = tryGet(map._inner, key); - return (success, address(uint160(uint256(value)))); + function tryGet(Bytes32ToAddressMap storage map, bytes32 key) internal view returns (bool exists, address value) { + (bool success, bytes32 val) = tryGet(map._inner, key); + return (success, address(uint160(uint256(val)))); } /** diff --git a/contracts/utils/types/Time.sol b/contracts/utils/types/Time.sol index 9faef31f054..1f729515425 100644 --- a/contracts/utils/types/Time.sol +++ b/contracts/utils/types/Time.sol @@ -71,8 +71,11 @@ library Time { * @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled * change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered. */ - function _getFullAt(Delay self, uint48 timepoint) private pure returns (uint32, uint32, uint48) { - (uint32 valueBefore, uint32 valueAfter, uint48 effect) = self.unpack(); + function _getFullAt( + Delay self, + uint48 timepoint + ) private pure returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) { + (valueBefore, valueAfter, effect) = self.unpack(); return effect <= timepoint ? (valueAfter, 0, 0) : (valueBefore, valueAfter, effect); } @@ -80,7 +83,7 @@ library Time { * @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the * effect timepoint is 0, then the pending value should not be considered. */ - function getFull(Delay self) internal view returns (uint32, uint32, uint48) { + function getFull(Delay self) internal view returns (uint32 valueBefore, uint32 valueAfter, uint48 effect) { return _getFullAt(self, timestamp()); } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index 1a4d1a7a5cd..d418b1177d1 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -41,7 +41,11 @@ struct ${opts.checkpointTypeName} { * IMPORTANT: Never accept \`key\` as a user input, since an arbitrary \`type(${opts.keyTypeName}).max\` key set will disable the * library. */ -function push(${opts.historyTypeName} storage self, ${opts.keyTypeName} key, ${opts.valueTypeName} value) internal returns (${opts.valueTypeName}, ${opts.valueTypeName}) { +function push( + ${opts.historyTypeName} storage self, + ${opts.keyTypeName} key, + ${opts.valueTypeName} value +) internal returns (${opts.valueTypeName} oldValue, ${opts.valueTypeName} newValue) { return _insert(self.${opts.checkpointFieldName}, key, value); } @@ -132,7 +136,11 @@ function at(${opts.historyTypeName} storage self, uint32 pos) internal view retu * @dev Pushes a (\`key\`, \`value\`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ -function _insert(${opts.checkpointTypeName}[] storage self, ${opts.keyTypeName} key, ${opts.valueTypeName} value) private returns (${opts.valueTypeName}, ${opts.valueTypeName}) { +function _insert( + ${opts.checkpointTypeName}[] storage self, + ${opts.keyTypeName} key, + ${opts.valueTypeName} value +) private returns (${opts.valueTypeName} oldValue, ${opts.valueTypeName} newValue) { uint256 pos = self.length; if (pos > 0) { diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index 656d8463dec..fc896f8fb9a 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -117,21 +117,21 @@ function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) * * - \`index\` must be strictly less than {length}. */ -function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { - bytes32 key = map._keys.at(index); - return (key, map._values[key]); +function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32 key, bytes32 value) { + bytes32 atKey = map._keys.at(index); + return (atKey, map._values[atKey]); } /** * @dev Tries to returns the value associated with \`key\`. O(1). * Does not revert if \`key\` is not in the map. */ -function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { - bytes32 value = map._values[key]; - if (value == bytes32(0)) { +function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool exists, bytes32 value) { + bytes32 val = map._values[key]; + if (val == bytes32(0)) { return (contains(map, key), bytes32(0)); } else { - return (true, value); + return (true, val); } } @@ -213,18 +213,18 @@ function length(${name} storage map) internal view returns (uint256) { * * - \`index\` must be strictly less than {length}. */ -function at(${name} storage map, uint256 index) internal view returns (${keyType}, ${valueType}) { - (bytes32 key, bytes32 value) = at(map._inner, index); - return (${fromBytes32(keyType, 'key')}, ${fromBytes32(valueType, 'value')}); +function at(${name} storage map, uint256 index) internal view returns (${keyType} key, ${valueType} value) { + (bytes32 atKey, bytes32 val) = at(map._inner, index); + return (${fromBytes32(keyType, 'atKey')}, ${fromBytes32(valueType, 'val')}); } /** * @dev Tries to returns the value associated with \`key\`. O(1). * Does not revert if \`key\` is not in the map. */ -function tryGet(${name} storage map, ${keyType} key) internal view returns (bool, ${valueType}) { - (bool success, bytes32 value) = tryGet(map._inner, ${toBytes32(keyType, 'key')}); - return (success, ${fromBytes32(valueType, 'value')}); +function tryGet(${name} storage map, ${keyType} key) internal view returns (bool exists, ${valueType} value) { + (bool success, bytes32 val) = tryGet(map._inner, ${toBytes32(keyType, 'key')}); + return (success, ${fromBytes32(valueType, 'val')}); } /** From 057d35a9eb363b4468d9cef69423879e3fa34e82 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:28:02 -0600 Subject: [PATCH 55/81] Update dependency halmos to v0.2.0 (#5225) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- fv-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fv-requirements.txt b/fv-requirements.txt index 6dda424ed2b..608b4de24a3 100644 --- a/fv-requirements.txt +++ b/fv-requirements.txt @@ -1,4 +1,4 @@ certora-cli==4.13.1 # File uses a custom name (fv-requirements.txt) so that it isn't picked by Netlify's build # whose latest Python version is 0.3.8, incompatible with most recent versions of Halmos -halmos==0.1.14 +halmos==0.2.0 From ae753b7eac6eaba0e8eebab11ecf2ca5a0057d90 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 27 Sep 2024 07:48:15 +0200 Subject: [PATCH 56/81] Improve VestingWallet's constructor and RSA.pkcs1Sha256 documentation (#5229) --- contracts/finance/VestingWallet.sol | 4 ++-- contracts/finance/VestingWalletCliff.sol | 3 ++- contracts/utils/cryptography/RSA.sol | 15 ++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index f472b66068b..153b8fc63de 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -37,8 +37,8 @@ contract VestingWallet is Context, Ownable { uint64 private immutable _duration; /** - * @dev Sets the sender as the initial owner, the beneficiary as the pending owner, the start timestamp and the - * vesting duration of the vesting wallet. + * @dev Sets the beneficiary (owner), the start timestamp and the vesting duration (in seconds) of the vesting + * wallet. */ constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds) payable Ownable(beneficiary) { _start = startTimestamp; diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index 034dd9cf6ea..7582ca56876 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -17,7 +17,8 @@ abstract contract VestingWalletCliff is VestingWallet { error InvalidCliffDuration(uint64 cliffSeconds, uint64 durationSeconds); /** - * @dev Set the start timestamp of the vesting wallet cliff. + * @dev Set the duration of the cliff, in seconds. The cliff starts vesting schedule (see {VestingWallet}'s + * constructor) and ends `cliffSeconds` later. */ constructor(uint64 cliffSeconds) { if (cliffSeconds > duration()) { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 6d355c60e2f..689124b5959 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -27,14 +27,15 @@ library RSA { /** * @dev Verifies a PKCSv1.5 signature given a digest according to the verification - * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with support - * for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported). + * method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with + * support for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported). * - * IMPORTANT: For security reason, this function requires the signature and modulus to have a length of at least 2048 bits. - * If you use a smaller key, consider replacing it with a larger, more secure, one. + * IMPORTANT: For security reason, this function requires the signature and modulus to have a length of at least + * 2048 bits. If you use a smaller key, consider replacing it with a larger, more secure, one. * - * WARNING: PKCS#1 v1.5 allows for replayability given the message may contain arbitrary optional parameters in the - * DigestInfo. Consider using an onchain nonce or unique identifier to include in the message to prevent replay attacks. + * WARNING: This verification algorithm doesn't prevent replayability. If called multiple times with the same + * digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce or + * unique identifier in the message to prevent replay attacks. * * @param digest the digest to verify * @param s is a buffer containing the signature @@ -79,7 +80,7 @@ library RSA { // - PS is padding filled with 0xFF // - DigestInfo ::= SEQUENCE { // digestAlgorithm AlgorithmIdentifier, - // [optional algorithm parameters] + // [optional algorithm parameters] -- not currently supported // digest OCTET STRING // } From cceac54953ccda8a9a028d0b9bf4378605fdf67e Mon Sep 17 00:00:00 2001 From: cairo Date: Fri, 27 Sep 2024 08:47:15 -0700 Subject: [PATCH 57/81] Add introduction tag for v5.1 contracts (#5228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/finance/VestingWalletCliff.sol | 2 ++ .../governance/extensions/GovernorCountingFractional.sol | 2 ++ contracts/token/ERC1155/utils/ERC1155Utils.sol | 2 ++ contracts/token/ERC20/extensions/ERC1363.sol | 2 ++ .../token/ERC20/extensions/draft-ERC20TemporaryApproval.sol | 2 ++ contracts/token/ERC721/utils/ERC721Utils.sol | 2 ++ contracts/utils/Comparators.sol | 5 +++++ contracts/utils/Errors.sol | 2 ++ contracts/utils/Packing.sol | 2 ++ contracts/utils/Panic.sol | 2 ++ contracts/utils/ReentrancyGuardTransient.sol | 2 ++ contracts/utils/SlotDerivation.sol | 2 ++ contracts/utils/cryptography/Hashes.sol | 2 ++ contracts/utils/cryptography/P256.sol | 2 ++ contracts/utils/cryptography/RSA.sol | 2 ++ contracts/utils/structs/CircularBuffer.sol | 2 ++ contracts/utils/structs/Heap.sol | 2 ++ scripts/generate/templates/Packing.js | 2 ++ scripts/generate/templates/SlotDerivation.js | 2 ++ 19 files changed, 41 insertions(+) diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index 7582ca56876..83d82510427 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -7,6 +7,8 @@ import {VestingWallet} from "./VestingWallet.sol"; /** * @dev Extension of {VestingWallet} that adds a cliff to the vesting schedule. + * + * _Available since v5.1._ */ abstract contract VestingWalletCliff is VestingWallet { using SafeCast for *; diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index 3a4fb664a53..a3f40201fd1 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -27,6 +27,8 @@ import {Math} from "../../utils/math/Math.sol"; * * Voting privately from a shielded pool using zero knowledge proofs. * * Based on ScopeLift's GovernorCountingFractional[https://github.com/ScopeLift/flexible-voting/blob/e5de2efd1368387b840931f19f3c184c85842761/src/GovernorCountingFractional.sol] + * + * _Available since v5.1._ */ abstract contract GovernorCountingFractional is Governor { using Math for *; diff --git a/contracts/token/ERC1155/utils/ERC1155Utils.sol b/contracts/token/ERC1155/utils/ERC1155Utils.sol index 1439330e41c..62accf6bc6e 100644 --- a/contracts/token/ERC1155/utils/ERC1155Utils.sol +++ b/contracts/token/ERC1155/utils/ERC1155Utils.sol @@ -9,6 +9,8 @@ import {IERC1155Errors} from "../../../interfaces/draft-IERC6093.sol"; * @dev Library that provide common ERC-1155 utility functions. * * See https://eips.ethereum.org/EIPS/eip-1155[ERC-1155]. + * + * _Available since v5.1._ */ library ERC1155Utils { /** diff --git a/contracts/token/ERC20/extensions/ERC1363.sol b/contracts/token/ERC20/extensions/ERC1363.sol index 253e443d6b1..4ae78f32764 100644 --- a/contracts/token/ERC20/extensions/ERC1363.sol +++ b/contracts/token/ERC20/extensions/ERC1363.sol @@ -12,6 +12,8 @@ import {ERC1363Utils} from "../utils/ERC1363Utils.sol"; * @dev Extension of {ERC20} tokens that adds support for code execution after transfers and approvals * on recipient contracts. Calls after transfers are enabled through the {ERC1363-transferAndCall} and * {ERC1363-transferFromAndCall} methods while calls after approvals can be made with {ERC1363-approveAndCall} + * + * _Available since v5.1._ */ abstract contract ERC1363 is ERC20, ERC165, IERC1363 { /** diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index fa1e098a7cc..515b080ea16 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -12,6 +12,8 @@ import {StorageSlot} from "../../../utils/StorageSlot.sol"; * @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674. * * WARNING: This is a draft contract. The corresponding ERC is still subject to changes. + * + * _Available since v5.1._ */ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 { using SlotDerivation for bytes32; diff --git a/contracts/token/ERC721/utils/ERC721Utils.sol b/contracts/token/ERC721/utils/ERC721Utils.sol index 712ac16a70a..0e18f5cc0a8 100644 --- a/contracts/token/ERC721/utils/ERC721Utils.sol +++ b/contracts/token/ERC721/utils/ERC721Utils.sol @@ -9,6 +9,8 @@ import {IERC721Errors} from "../../../interfaces/draft-IERC6093.sol"; * @dev Library that provide common ERC-721 utility functions. * * See https://eips.ethereum.org/EIPS/eip-721[ERC-721]. + * + * _Available since v5.1._ */ library ERC721Utils { /** diff --git a/contracts/utils/Comparators.sol b/contracts/utils/Comparators.sol index 3a63aa0e8ee..c10734712bc 100644 --- a/contracts/utils/Comparators.sol +++ b/contracts/utils/Comparators.sol @@ -2,6 +2,11 @@ pragma solidity ^0.8.20; +/** + * @dev Provides a set of functions to compare values. + * + * _Available since v5.1._ + */ library Comparators { function lt(uint256 a, uint256 b) internal pure returns (bool) { return a < b; diff --git a/contracts/utils/Errors.sol b/contracts/utils/Errors.sol index 633d8c20286..715db953620 100644 --- a/contracts/utils/Errors.sol +++ b/contracts/utils/Errors.sol @@ -7,6 +7,8 @@ pragma solidity ^0.8.20; * * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. * It is recommended to avoid relying on the error API for critical functionality. + * + * _Available since v5.1._ */ library Errors { /** diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 8a8e3fee86f..4da311cf3c0 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -28,6 +28,8 @@ pragma solidity ^0.8.20; * } * } * ``` + * + * _Available since v5.1._ */ // solhint-disable func-name-mixedcase library Packing { diff --git a/contracts/utils/Panic.sol b/contracts/utils/Panic.sol index 8769bd9a786..0e3c38c3e36 100644 --- a/contracts/utils/Panic.sol +++ b/contracts/utils/Panic.sol @@ -18,6 +18,8 @@ pragma solidity ^0.8.20; * ``` * * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. + * + * _Available since v5.1._ */ // slither-disable-next-line unused-state library Panic { diff --git a/contracts/utils/ReentrancyGuardTransient.sol b/contracts/utils/ReentrancyGuardTransient.sol index 0389b86205d..54df0a71ac8 100644 --- a/contracts/utils/ReentrancyGuardTransient.sol +++ b/contracts/utils/ReentrancyGuardTransient.sol @@ -8,6 +8,8 @@ import {StorageSlot} from "./StorageSlot.sol"; * @dev Variant of {ReentrancyGuard} that uses transient storage. * * NOTE: This variant only works on networks where EIP-1153 is available. + * + * _Available since v5.1._ */ abstract contract ReentrancyGuardTransient { using StorageSlot for *; diff --git a/contracts/utils/SlotDerivation.sol b/contracts/utils/SlotDerivation.sol index c248edc01c1..83b9d5639c0 100644 --- a/contracts/utils/SlotDerivation.sol +++ b/contracts/utils/SlotDerivation.sol @@ -34,6 +34,8 @@ pragma solidity ^0.8.20; * * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking * upgrade safety will ignore the slots accessed through this library. + * + * _Available since v5.1._ */ library SlotDerivation { /** diff --git a/contracts/utils/cryptography/Hashes.sol b/contracts/utils/cryptography/Hashes.sol index 85efd6d294e..0ed89b308c0 100644 --- a/contracts/utils/cryptography/Hashes.sol +++ b/contracts/utils/cryptography/Hashes.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.20; /** * @dev Library of standard hash functions. + * + * _Available since v5.1._ */ library Hashes { /** diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index eebedfd34f7..1c46e38b0be 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -14,6 +14,8 @@ import {Errors} from "../Errors.sol"; * Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/d3d423f28a4d8dfcb203c7fa0c47f42592a7378e/src/Secp256r1.sol[implementation of itsobvioustech] (GNU General Public License v3.0). * Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/c4bb1b6e8ae89534d8db3a6b3a6b52219100520f/contracts/Secp256r1.sol[maxrobot] and * https://github.com/tdrerup/elliptic-curve-solidity/blob/59a9c25957d4d190eff53b6610731d81a077a15e/contracts/curves/EllipticCurve.sol[tdrerup] implementations. + * + * _Available since v5.1._ */ library P256 { struct JPoint { diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 689124b5959..c8da4f96206 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -11,6 +11,8 @@ import {Math} from "../math/Math.sol"; * RSA semantically secure for signing messages. * * Inspired by https://github.com/adria0/SolRsaVerify/blob/79c6182cabb9102ea69d4a2e996816091d5f1cd1[Adrià Massanet's work] (GNU General Public License v3.0). + * + * _Available since v5.1._ */ library RSA { /** diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index faaad2f028b..cf1afbbd6ea 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -34,6 +34,8 @@ import {Panic} from "../Panic.sol"; * CircularBuffer.Bytes32CircularBuffer private myBuffer; * } * ``` + * + * _Available since v5.1._ */ library CircularBuffer { /** diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index 17a778f3aa7..d33dfbec8b2 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -33,6 +33,8 @@ import {StorageSlot} from "../StorageSlot.sol"; * IMPORTANT: This library allows for the use of custom comparator functions. Given that manipulating * memory can lead to unexpected behavior. Consider verifying that the comparator does not manipulate * the Heap's state directly and that it follows the Solidity memory safety rules. + * + * _Available since v5.1._ */ library Heap { using Arrays for *; diff --git a/scripts/generate/templates/Packing.js b/scripts/generate/templates/Packing.js index d841c2f816a..9f3b7716a6a 100644 --- a/scripts/generate/templates/Packing.js +++ b/scripts/generate/templates/Packing.js @@ -32,6 +32,8 @@ pragma solidity ^0.8.20; * } * } * \`\`\` + * + * _Available since v5.1._ */ // solhint-disable func-name-mixedcase `; diff --git a/scripts/generate/templates/SlotDerivation.js b/scripts/generate/templates/SlotDerivation.js index 39d0d9e3508..ec4d244b931 100644 --- a/scripts/generate/templates/SlotDerivation.js +++ b/scripts/generate/templates/SlotDerivation.js @@ -36,6 +36,8 @@ pragma solidity ^0.8.20; * * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking * upgrade safety will ignore the slots accessed through this library. + * + * _Available since v5.1._ */ `; From d3ca1d1f006e6e69311d889642716a1ea4ff228b Mon Sep 17 00:00:00 2001 From: plooten Date: Mon, 30 Sep 2024 14:55:03 +0200 Subject: [PATCH 58/81] Fix invalid link and typos (#5232) --- docs/modules/ROOT/pages/utilities.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index 7b2edf11287..adee6477f95 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -32,7 +32,7 @@ WARNING: Getting signature verification right is not trivial: make sure you full ==== P256 Signatures (secp256r1) -P256, also known as secp256r1, is one of the most used signature schemes. P256 signatures are standardized by the National Institute of Standards and Technology (NIST) and it's widely available in consumer hardware and software. +P256, also known as secp256r1, is one of the most used signature schemes. P256 signatures are standardized by the National Institute of Standards and Technology (NIST) and they are widely available in consumer hardware and software. These signatures are different from regular Ethereum Signatures (secp256k1) in that they use a different elliptic curve to perform operations but have similar security guarantees. @@ -51,7 +51,7 @@ function _verify( } ---- -By default, the `verify` function will try calling the (https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md)[RIP-7212] precompile at address `0x100` and will fallback to an implementation in Solidity if not available. We encourage you to use `verifyNative` if you know the precompile is available on the chain you're working on and on any other chain on which you intend to use the same bytecode in the future. In case of any doubts regarding the implementation roadmap of the native precompile `P256` of potential future target chains, please consider using `verifySolidity`. +By default, the `verify` function will try calling the https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md[RIP-7212] precompile at address `0x100` and will fallback to an implementation in Solidity if not available. We encourage you to use `verifyNative` if you know the precompile is available on the chain you're working on and on any other chain on which you intend to use the same bytecode in the future. In case of any doubts regarding the implementation roadmap of the native precompile `P256` of potential future target chains, please consider using `verifySolidity`. [source,solidity] ---- @@ -73,7 +73,7 @@ IMPORTANT: The P256 library only allows for `s` values in the lower order of the ==== RSA -RSA a public-key cryptosystem that was popularized by corporate and governmental public key infrastructures (https://en.wikipedia.org/wiki/Public_key_infrastructure[PKIs]) and https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions[DNSSEC]. +RSA is a public-key cryptosystem that was popularized by corporate and governmental public key infrastructures (https://en.wikipedia.org/wiki/Public_key_infrastructure[PKIs]) and https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions[DNSSEC]. This cryptosystem consists of using a private key that's the product of 2 large prime numbers. The message is signed by applying a modular exponentiation to its hash (commonly SHA256), where both the exponent and modulus compose the public key of the signer. @@ -99,7 +99,7 @@ IMPORTANT: Always use keys of at least 2048 bits. Additionally, be aware that PK === Verifying Merkle Proofs -Developers can build a Merkle Tree off-chain, which allows for verifying that an element (leaf) is part of a set by using a Merkle Proof. This technique is widely used for creating whitelists (e.g. for airdrops) and other advanced use cases. +Developers can build a Merkle Tree off-chain, which allows for verifying that an element (leaf) is part of a set by using a Merkle Proof. This technique is widely used for creating whitelists (e.g., for airdrops) and other advanced use cases. TIP: OpenZeppelin Contracts provides a https://github.com/OpenZeppelin/merkle-tree[JavaScript library] for building trees off-chain and generating proofs. @@ -149,7 +149,7 @@ contract MyContract { [[math]] == Math -Although Solidity already provides math operators (i.e. `+`, `-`, etc.), Contracts includes xref:api:utils.adoc#Math[`Math`]; a set of utilities for dealing with mathematical operators, with support for extra operations (eg. xref:api:utils.adoc#Math-average-uint256-uint256-[`average`]) and xref:api:utils.adoc#SignedMath[`SignedMath`]; a library specialized in signed math operations. +Although Solidity already provides math operators (i.e. `+`, `-`, etc.), Contracts includes xref:api:utils.adoc#Math[`Math`]; a set of utilities for dealing with mathematical operators, with support for extra operations (e.g., xref:api:utils.adoc#Math-average-uint256-uint256-[`average`]) and xref:api:utils.adoc#SignedMath[`SignedMath`]; a library specialized in signed math operations. Include these contracts with `using Math for uint256` or `using SignedMath for int256` and then use their functions in your code: @@ -272,7 +272,7 @@ function replace(Uint256Heap storage self, uint256 newValue) internal returns (u === Packing -The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allow for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one. +The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as a _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allow for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one. Commonly, developers pack values using structs that place values together so they fit better in storage. However, this approach requires to load such struct from either calldata or memory. Although sometimes necessary, it may be useful to pack values in a single slot and treat it as a packed value without involving calldata or memory. From e3cfe1c5ddbea74cca0494bb28361c8096e0160d Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 30 Sep 2024 09:05:44 -0700 Subject: [PATCH 59/81] Fix P256 corner cases (#5218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hadrien Croubois Co-authored-by: Ernesto García --- .solcover.js | 8 +++ contracts/utils/cryptography/P256.sol | 94 ++++++++++++++++++++------- test/helpers/iterate.js | 4 +- test/utils/cryptography/P256.t.sol | 12 ++-- 4 files changed, 88 insertions(+), 30 deletions(-) diff --git a/.solcover.js b/.solcover.js index e0dea5e2c9b..f079998cff3 100644 --- a/.solcover.js +++ b/.solcover.js @@ -10,4 +10,12 @@ module.exports = { fgrep: '[skip-on-coverage]', invert: true, }, + // Work around stack too deep for coverage + configureYulOptimizer: true, + solcOptimizerDetails: { + yul: true, + yulDetails: { + optimizerSteps: '', + }, + }, }; diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 1c46e38b0be..3028505ba75 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -185,6 +185,13 @@ library P256 { /** * @dev Point addition on the jacobian coordinates * Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 + * + * Note that: + * + * - `addition-add-1998-cmo-2` doesn't support identical input points. This version is modified to use + * the `h` and `r` values computed by `addition-add-1998-cmo-2` to detect identical inputs, and fallback to + * `doubling-dbl-1998-cmo-2` if needed. + * - if one of the points is at infinity (i.e. `z=0`), the result is undefined. */ function _jAdd( JPoint memory p1, @@ -197,25 +204,53 @@ library P256 { let z1 := mload(add(p1, 0x40)) let zz1 := mulmod(z1, z1, p) // zz1 = z1² let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³ - let r := addmod(mulmod(y2, mulmod(zz1, z1, p), p), sub(p, s1), p) // r = s2-s1 = y2*z1³-s1 + let r := addmod(mulmod(y2, mulmod(zz1, z1, p), p), sub(p, s1), p) // r = s2-s1 = y2*z1³-s1 = y2*z1³-y1*z2³ let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2² - let h := addmod(mulmod(x2, zz1, p), sub(p, u1), p) // h = u2-u1 = x2*z1²-u1 - let hh := mulmod(h, h, p) // h² + let h := addmod(mulmod(x2, zz1, p), sub(p, u1), p) // h = u2-u1 = x2*z1²-u1 = x2*z1²-x1*z2² + + // detect edge cases where inputs are identical + switch and(iszero(r), iszero(h)) + // case 0: points are different + case 0 { + let hh := mulmod(h, h, p) // h² + + // x' = r²-h³-2*u1*h² + rx := addmod( + addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p), + sub(p, mulmod(2, mulmod(u1, hh, p), p)), + p + ) + // y' = r*(u1*h²-x')-s1*h³ + ry := addmod( + mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), + sub(p, mulmod(s1, mulmod(h, hh, p), p)), + p + ) + // z' = h*z1*z2 + rz := mulmod(h, mulmod(z1, z2, p), p) + } + // case 1: points are equal + case 1 { + let x := x2 + let y := y2 + let z := z2 + let yy := mulmod(y, y, p) + let zz := mulmod(z, z, p) + let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ + let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² + + // x' = t = m²-2*s + rx := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) - // x' = r²-h³-2*u1*h² - rx := addmod( - addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p), - sub(p, mulmod(2, mulmod(u1, hh, p), p)), - p - ) - // y' = r*(u1*h²-x')-s1*h³ - ry := addmod( - mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), - sub(p, mulmod(s1, mulmod(h, hh, p), p)), - p - ) - // z' = h*z1*z2 - rz := mulmod(h, mulmod(z1, z2, p), p) + // y' = m*(s-t)-8*y⁴ = m*(s-x')-8*y⁴ + // cut the computation to avoid stack too deep + let rytmp1 := sub(p, mulmod(8, mulmod(yy, yy, p), p)) // -8*y⁴ + let rytmp2 := addmod(s, sub(p, rx), p) // s-x' + ry := addmod(mulmod(m, rytmp2, p), rytmp1, p) // m*(s-x')-8*y⁴ + + // z' = 2*y*z + rz := mulmod(2, mulmod(y, z, p), p) + } } } @@ -228,8 +263,8 @@ library P256 { let p := P let yy := mulmod(y, y, p) let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ + let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² // x' = t = m²-2*s rx := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) @@ -244,10 +279,11 @@ library P256 { * @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}). * * Uses Strauss Shamir trick for EC multiplication - * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method. - * We optimise on this a bit to do with 2 bits at a time rather than a single bit. - * The individual points for a single pass are precomputed. - * Overall this reduces the number of additions while keeping the same number of doublings. + * https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method + * + * We optimize this for 2 bits at a time rather than a single bit. The individual points for a single pass are + * precomputed. Overall this reduces the number of additions while keeping the same number of + * doublings */ function _jMultShamir( JPoint[16] memory points, @@ -263,9 +299,14 @@ library P256 { (x, y, z) = _jDouble(x, y, z); (x, y, z) = _jDouble(x, y, z); } - // Read 2 bits of u1, and 2 bits of u2. Combining the two give a lookup index in the table. + // Read 2 bits of u1, and 2 bits of u2. Combining the two gives the lookup index in the table. uint256 pos = ((u1 >> 252) & 0xc) | ((u2 >> 254) & 0x3); - if (pos > 0) { + // Points that have z = 0 are points at infinity. They are the additive 0 of the group + // - if the lookup point is a 0, we can skip it + // - otherwise: + // - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P) + // - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd` + if (points[pos].z != 0) { if (z == 0) { (x, y, z) = (points[pos].x, points[pos].y, points[pos].z); } else { @@ -291,6 +332,11 @@ library P256 { * │ 8 │ 2g 2g+p 2g+2p 2g+3p │ * │ 12 │ 3g 3g+p 3g+2p 3g+3p │ * └────┴─────────────────────┘ + * + * Note that `_jAdd` (and thus `_jAddPoint`) does not handle the case where one of the inputs is a point at + * infinity (z = 0). However, we know that since `N ≡ 1 mod 2` and `N ≡ 1 mod 3`, there is no point P such that + * 2P = 0 or 3P = 0. This guarantees that g, 2g, 3g, p, 2p, 3p are all non-zero, and that all `_jAddPoint` calls + * have valid inputs. */ function _preComputeJacobianPoints(uint256 px, uint256 py) private pure returns (JPoint[16] memory points) { points[0x00] = JPoint(0, 0, 0); // 0,0 diff --git a/test/helpers/iterate.js b/test/helpers/iterate.js index ef4526e133f..c7403d52384 100644 --- a/test/helpers/iterate.js +++ b/test/helpers/iterate.js @@ -13,11 +13,11 @@ module.exports = { // Range from start to end in increment // Example: range(17,42,7) → [17,24,31,38] range: (start, stop = undefined, step = 1) => { - if (!stop) { + if (stop == undefined) { stop = start; start = 0; } - return start < stop ? Array.from({ length: Math.ceil((stop - start) / step) }, (_, i) => start + i * step) : []; + return start < stop ? Array.from({ length: (stop - start + step - 1) / step }, (_, i) => start + i * step) : []; }, // Unique elements, with an optional getter function diff --git a/test/utils/cryptography/P256.t.sol b/test/utils/cryptography/P256.t.sol index 1391afd76ef..8b95ff2259d 100644 --- a/test/utils/cryptography/P256.t.sol +++ b/test/utils/cryptography/P256.t.sol @@ -9,8 +9,8 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract P256Test is Test { /// forge-config: default.fuzz.runs = 512 - function testVerify(uint256 seed, bytes32 digest) public { - uint256 privateKey = bound(uint256(keccak256(abi.encode(seed))), 1, P256.N - 1); + function testVerify(bytes32 digest, uint256 seed) public { + uint256 privateKey = _asPrivateKey(seed); (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); @@ -20,8 +20,8 @@ contract P256Test is Test { } /// forge-config: default.fuzz.runs = 512 - function testRecover(uint256 seed, bytes32 digest) public { - uint256 privateKey = bound(uint256(keccak256(abi.encode(seed))), 1, P256.N - 1); + function testRecover(bytes32 digest, uint256 seed) public { + uint256 privateKey = _asPrivateKey(seed); (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); @@ -31,6 +31,10 @@ contract P256Test is Test { assertTrue((qx0 == x && qy0 == y) || (qx1 == x && qy1 == y)); } + function _asPrivateKey(uint256 seed) private pure returns (uint256) { + return bound(seed, 1, P256.N - 1); + } + function _ensureLowerS(bytes32 s) private pure returns (bytes32) { uint256 _s = uint256(s); unchecked { From b72e3da0ec1f47e4a7911a4c06dc92e78c646607 Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 30 Sep 2024 14:38:42 -0700 Subject: [PATCH 60/81] Bump forge-std to v1.9.3 (#5230) --- lib/forge-std | 2 +- test/utils/cryptography/P256.t.sol | 105 ++--------------------------- 2 files changed, 6 insertions(+), 101 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index ae570fec082..8f24d6b04c9 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce +Subproject commit 8f24d6b04c92975e0795b5868aa0d783251cdeaa diff --git a/test/utils/cryptography/P256.t.sol b/test/utils/cryptography/P256.t.sol index 8b95ff2259d..0c9b2c78a04 100644 --- a/test/utils/cryptography/P256.t.sol +++ b/test/utils/cryptography/P256.t.sol @@ -12,23 +12,23 @@ contract P256Test is Test { function testVerify(bytes32 digest, uint256 seed) public { uint256 privateKey = _asPrivateKey(seed); - (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = _ensureLowerS(s); - assertTrue(P256.verify(digest, r, s, x, y)); - assertTrue(P256.verifySolidity(digest, r, s, x, y)); + assertTrue(P256.verify(digest, r, s, bytes32(x), bytes32(y))); + assertTrue(P256.verifySolidity(digest, r, s, bytes32(x), bytes32(y))); } /// forge-config: default.fuzz.runs = 512 function testRecover(bytes32 digest, uint256 seed) public { uint256 privateKey = _asPrivateKey(seed); - (bytes32 x, bytes32 y) = P256PublicKey.getPublicKey(privateKey); + (uint256 x, uint256 y) = vm.publicKeyP256(privateKey); (bytes32 r, bytes32 s) = vm.signP256(privateKey, digest); s = _ensureLowerS(s); (bytes32 qx0, bytes32 qy0) = P256.recovery(digest, 0, r, s); (bytes32 qx1, bytes32 qy1) = P256.recovery(digest, 1, r, s); - assertTrue((qx0 == x && qy0 == y) || (qx1 == x && qy1 == y)); + assertTrue((qx0 == bytes32(x) && qy0 == bytes32(y)) || (qx1 == bytes32(x) && qy1 == bytes32(y))); } function _asPrivateKey(uint256 seed) private pure returns (uint256) { @@ -42,98 +42,3 @@ contract P256Test is Test { } } } - -/** - * @dev Library to derive P256 public key from private key - * Should be removed if Foundry adds this functionality - * See https://github.com/foundry-rs/foundry/issues/7908 - */ -library P256PublicKey { - function getPublicKey(uint256 privateKey) internal view returns (bytes32, bytes32) { - (uint256 x, uint256 y, uint256 z) = _jMult(P256.GX, P256.GY, 1, privateKey); - return _affineFromJacobian(x, y, z); - } - - function _jMult( - uint256 x, - uint256 y, - uint256 z, - uint256 k - ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - unchecked { - for (uint256 i = 0; i < 256; ++i) { - if (rz > 0) { - (rx, ry, rz) = _jDouble(rx, ry, rz); - } - if (k >> 255 > 0) { - if (rz == 0) { - (rx, ry, rz) = (x, y, z); - } else { - (rx, ry, rz) = _jAdd(rx, ry, rz, x, y, z); - } - } - k <<= 1; - } - } - } - - /// From P256.sol - - function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (bytes32 ax, bytes32 ay) { - if (jz == 0) return (0, 0); - uint256 zinv = Math.invModPrime(jz, P256.P); - uint256 zzinv = mulmod(zinv, zinv, P256.P); - uint256 zzzinv = mulmod(zzinv, zinv, P256.P); - ax = bytes32(mulmod(jx, zzinv, P256.P)); - ay = bytes32(mulmod(jy, zzzinv, P256.P)); - } - - function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - uint256 p = P256.P; - uint256 a = P256.A; - assembly ("memory-safe") { - let yy := mulmod(y, y, p) - let zz := mulmod(z, z, p) - let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y² - let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(a, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴ - let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s - - // x' = t - rx := t - // y' = m*(s-t)-8*y⁴ - ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p) - // z' = 2*y*z - rz := mulmod(2, mulmod(y, z, p), p) - } - } - - function _jAdd( - uint256 x1, - uint256 y1, - uint256 z1, - uint256 x2, - uint256 y2, - uint256 z2 - ) private pure returns (uint256 rx, uint256 ry, uint256 rz) { - uint256 p = P256.P; - assembly ("memory-safe") { - let zz1 := mulmod(z1, z1, p) // zz1 = z1² - let zz2 := mulmod(z2, z2, p) // zz2 = z2² - let u1 := mulmod(x1, zz2, p) // u1 = x1*z2² - let u2 := mulmod(x2, zz1, p) // u2 = x2*z1² - let s1 := mulmod(y1, mulmod(zz2, z2, p), p) // s1 = y1*z2³ - let s2 := mulmod(y2, mulmod(zz1, z1, p), p) // s2 = y2*z1³ - let h := addmod(u2, sub(p, u1), p) // h = u2-u1 - let hh := mulmod(h, h, p) // h² - let hhh := mulmod(h, hh, p) // h³ - let r := addmod(s2, sub(p, s1), p) // r = s2-s1 - - // x' = r²-h³-2*u1*h² - rx := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p) - // y' = r*(u1*h²-x')-s1*h³ - ry := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p), sub(p, mulmod(s1, hhh, p)), p) - // z' = h*z1*z2 - rz := mulmod(h, mulmod(z1, z2, p), p) - } - } -} From 49cd64565aafa5b8f6863bf60a30ef015861614c Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 1 Oct 2024 19:50:58 +0200 Subject: [PATCH 61/81] Add warning about low public key exponent (#5234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García --- contracts/utils/cryptography/RSA.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index c8da4f96206..70c38fd15bb 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -36,8 +36,12 @@ library RSA { * 2048 bits. If you use a smaller key, consider replacing it with a larger, more secure, one. * * WARNING: This verification algorithm doesn't prevent replayability. If called multiple times with the same - * digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce or - * unique identifier in the message to prevent replay attacks. + * digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce + * or unique identifier in the message to prevent replay attacks. + * + * WARNING: This verification algorithm supports any exponent. NIST recommends using `65537` (or higher). + * That is the default value many libraries use, such as OpenSSL. Developers may choose to reject public keys + * using a low exponent out of security concerns. * * @param digest the digest to verify * @param s is a buffer containing the signature From 8b591baef460523e5ca1c53712c464bcc1a1c467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Thu, 3 Oct 2024 13:24:43 -0600 Subject: [PATCH 62/81] Add 5.1 audit report (#5237) --- audits/2024-10-v5.1.pdf | Bin 0 -> 395831 bytes audits/README.md | 1 + 2 files changed, 1 insertion(+) create mode 100644 audits/2024-10-v5.1.pdf diff --git a/audits/2024-10-v5.1.pdf b/audits/2024-10-v5.1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d85dc7ba9ea633910f31cbf526611ab626f322a GIT binary patch literal 395831 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
>u|qF z&JT-u>?G2{ZOCkxbbq-T2qk?>3Z4s7*;<>nFMDNIs-W~JzOO)`sj0N z?^Z?$72pitwfM5NO*-{`t-{G$Ze*_#{~CVP9$FCVA}%)i)cxaQKP4O6+v+oGSnKK2 z#eF{-17$(wnpCn{L$I9w*5EWYzIZIB$nl5MZ>N$fCY2i&>NLY-KYe;DadU#@k6d*z z4VBjPpdglH8ua-!^wel#Ajy`=X_LE&Z+*=NKRyPrQ6e35y3)$9=RpkwwP%0z# z2jN(ys@&^LD|qwOcM5d|5Be%R4qM2~zDL+dX!Yt&JfHMxVl^Ddqj9y3s<*TEYmP|F z%zYz~3(vqaT1m`npA&S^?7P@&ZRe=O(d=l>y=zT_najNWHXnFvW`T~#3lFPWIj@jR z_P4wZzP9}9=jx7U_&nWinrq`A-9>}gr2{T~cu3=49xxYTwkC&80AGMT#*l8K!%0^- ze9!U7j-ebXP2dGSAKq_JpF1ZcH5w@T5&9MPUd>x6n#oNGoz#lc+Q&z8n|idI*?^D8 z-hcKVui$(!>!~(Ux-t^uA;n9mY^sjxKApJc95EwebcbiD_{O7kST(~Tcs0Q?0K81H zwz(wD0uTEMfa9zl^toJ=A$)N}igsgk{{l<-`fqz>4c-^egPq@Y4v&y)<;1@bRMx%F2mE6ReSr+mTBiy*FcBe$!f`OiD8aU83P$GvV%}4 z$4HRy(Q$5!HL7QxpDx3Xf$pj>B6oskJaQETGVRgkWmp*7^Ng`_1Ikjx{{?sgo-?u3 zaMZ-x1PPdNy%DfM&s1%D_2<0~)!Bzk@M|G}R?NvVrF}L&p)>JPKvh4>OeD#APb)_q!y7&;)5^7ANK6A)ISbw9D$O>3iNW-vy z4)7l&sW@|haY7S%Lrs}M5sRkAkU+SL!>mL7%3`W&5;Z_N5t?c~t&`iLz2RLT+2W)O zP`e-K1_G};ij0zMP_hATf~*K=8?lW{TAK0)37t?jE)cS|Ns>Z*_ zbW%5x%f!(+Z8$9xc7mq07{n=tl^x5%qmfu3TV24Q*iSo3W0P)aLX<@!1#U1IR`^i_ zJ31N<+iQt(iAW?n9GA;crKU{0Od)CWLp7Wdxs}JcE1*8OcT+&F05TzKt&C)UJgE1qVc~Qo$0>k4tK0Pv47_ zhnCv~Ini}4__ApOrFqZxiws%maWg?@OQMJB)VIIc6JZAJ=1*MMlL~_H@qcLt#1J3c zR-^NgWTFiwlk1+-u%gbVScNQWEMrW@GQdG|G6=}OFlrZ$&MMx~sSl0_vIB*izDZna z6=d?hzOpha9)R`iNjQL9B-mG4;w}IW1IGHs^bjbabpag+9<#FtGszqy!V)vFHaLRsL>oWFMgOfv#3eA)7ETtZO;URBoG+PaMdO z6X+hVu3IE{Y7Qs5aY2~EekkU^H^6@zRUx?hY<$ng{kAWy5;)Jd$0$scCN@Xy|{ zy9I;)c=d|2>~kkeemD-pzgl|kdb782)6cp^bD?!}3Ks<4cX+n%cQFdx0Zcl&@5b(& z`|qR$Tzg{u-A@bn%VGR4#GmhN8+LEE&CA-Feby=!J?P2JR_~`(f~+5THSc9MX+6C= z74OKij>qg;KQv-29)ZVg%`b2?+#>z4-Mp>w3}s;abF2Mqg$&=ZOtW zVN?EfuEdzBsW_`%S7}zJUxtkXM@QYfkTn1BbG~?;@8+g_YyfIWWtX4ld*p0t(#0SI zV_)}8bET>6nMBGc-%nfhjyx+cF51a#YjutX(z3&#Md7EZRj-TgUeLrts zr?rn-1uKcK#jG=y&2xxZL2{w|Q`#teVlYTU~c;zeaokDTl7 z-L5p_#y(Df@Z#t0UeDhncn-tV_x)OSBs~9oxxfB;xtC9rckKJF0MP4ZN!Q-Ef=R0# z-$gp&ndk1-jx~NA*t2IP68n;4Oqey+{P^r=u{w81ekCAx&#!ccchCX*uTJ8ihL6L8o!t9o>Ko3?G~lGs@CJzbbS0vGu%8SxwlY{fcGVeL*p2d z_(nvnlkyqAJ#T{XYV}apv5mHGNwCXfn{DtbcxomZL^s7Oa2q^=^L>1_!$qEcFE)Ge z4#xvpAfk~15D{HbYU}-J?82JQD0j>ub7E}IZ)JXYghHGYjozAqaD&TT8ZV#Kfp?1j zr8b?Yn;OWnOuj*X0vZA#O6uN#)>MM+R0yeXMS0ad$)h5*#E|`-fV( zsilt6GLZDlYja*e+O^syd|$*Y@lzQhfe9-BYds zoUq;dr(0w&n(u`s_}8jF%aDYD0ScV`6B1nsx=}7fpBQFw1I0^hcJjTpTH0g$xVzaw ze8KQZs_0KGyA%puwTcnh#rY>AlP+p%f$52cQ=1{hVm-_O6i12lI?8n=?>TFHHgGvx zCna~56d5PZS=zBJ?&{$LwGwDbvLk2dgtsn(k&bl=7Gd(}UZ>S2ytV$Y90Z&MuT7Pf zJm@Fc3@50^2DSc_t5EM0)+3NkkFZT~u@y;v%e1Z~lita5e;Mx9+Vu>R>Gvh0bziy} z$BUqY7E!u%CubPF4?v$azJ!!^l#;=`x^rKKX-A?}gb*vdgC#`GL2aS+rsdX>Y7TTR zvwyrRY?bI;r}4rj9vb~ndBzw3^s?9(p*9qFk7g8`tvpw90yn^p3^Z4=ys=8%GN!qpE%WVXbtxUhT{;q4P=UR!OZHJDXy zqPTGN{QR1$rNOsHJ#@pFkL~_qh%}?7eB=BPDebKQU7p)DO?C5c+Lo8+fH!Mjg4MUp z6{tyiE$;etuDovmLbo{FQ7Rs5_ zcKN3H!tl&#L`m&Xo99_2|JZ--h@QR$7D? zhk>F^mP3P$r7F=3@V{#wsBqmiKV7GMG8{t-E0c?aG}L*{LpZs|{=NN9+Yn_Zvyh>h#d-F=CQ*hRiSC~$98f&v^l)MqNnE+0SrBAO^2bKw_^ z%B}yL;gJ7e-;cckG;>xi*()axlD=^T<^UdY);<#VgKS^4XOkGVgH5S|6%`# zf9zkoj6dcx?->@WF{$X;3$!PeYmF(uywCx9%Y z%ce`o9UNok$IcB$4ANrxNnw!&agHT{e^FY7RESU55Q50{Db3xL;a9^0|KypOm2k@- zQIW{}Aw-ISmOKZD35@M zP}-o6&3q7*b0$RO5~p`VH2y??AonDFEtmL9uZ=K8an=ODTS(Imo?0f%RRuf1$*b-? zMMl1y$K~Dz{sm z4wCrMaL_tQf~jsJI)#2He(rcQB&aTYK}td}QjFphqSP6HoS<;&&cHY-t~ts?Z1Lz8 zo%j+)>J!krGB0|X>RpdZ3J%CL{n`?OU?C|TF{(cFd^CpOUY-cKq@D<^-h^$aHuFFD zX6!Bo!b7HYFl5U=b(@rCU|1}m^@v1nV47;7^$FnQx7%MZ$C^+xI-`V+yRL%@w8`_P z3Winw1VL#4gC%1sVvm0}AMre1g;=YQ6rgdic+x0sV~s_5*VzUpL$}V|N2AcEfrI51 zY+>>W;LMy%1F@Md%c&9ROz`?pldOp%&9hYNgX7~P5Q(Y7@EX$gb%o9H$(5I8OVVZZ!Z zEfg(Wrrlbh|3sw4YSLkjEXTi67^k)7z7_+Eb%Fdc?@Ws5=*+mac1rFGV_<;G+lq8V zm)2wIW*3r+;Pe)yG|V9bqsF>@5epsjLXjQ$fr70AbruVhx2ob`S2gIpzo8|eDF=j4 zFg)m2ca6`H$o2;1xq`~l_ zfR&Abins87*f30eOHVQ%p)j1`Qomxc` z#;cvmKK?Tzm6cCZ$j~?Jw#g$YreVBaiR)0S4 zSm!*>Iox{BRhb)(kqvaYAqYc^W3kfcEq;WP{K3cfBbzvsycCwX(~<*9KN`$Wd~P^M zv_lMC9#kK# z==cUw7(+J`_zP-KMU{S0*iMoOlfcZI!&$GajgMj_XG051PH@s%Ui#(w78~#<%1r*6 zRD?cInDp{0=ha+Rwq!mL;kDKJfvnQhBzjUUE^hhWlpn8;W@TD?Agyp))=wpUHqlEAJ_^Sg;A<8#ktRH+8~(B zH?j8_^orGau3)gGqc$+HMNXPUt}s2Zc;Wi;BWz7HPi z*9$uAmoiAMlUt#&LUn<57D}|!eo=Ulc@g5E{~*yuC#?@ONBSE6m^$ce*xV+(L5_z~`x*x2jym<81!WTR0qyD@@OMV% zibm4mKwXt9@eY?INoA+T>EM`qf;#chhiR6RO8LQMJh~l)-ZvuEw~el`VgnTq36t{! zd^U_nd??IIUDQW)wMpr?DxRH8%%ff0qy5^3bXHX45x@GJ8K5`Moy3umru?|Ou(bQP zKzSTyKB8hA1{YP;q&TKxoTc`kEQ_fznW^I&!`ob@yRlbQDv1RH6G>WCWZW;QzUX8{ zZSO07>olmUrJ%9%-;pv?Z5xMabWUHbeltUF%R-&PtifCjMvZyTLI#+Toq%w12~WX^ zb2yD~b|&EJj=gfpcGt*#!Hxgw(#wMwC~wVXKg!beDNXIElVvD?wnapZj~1* zd@VL&7H{ta($P2CxT(n8+6w#p|dirL}K=h zo{-^%ZPYR|JqK}g3gKkWVja=kg|E&M`*o_rscfl^77tREb5v6?L$&M;unnHr6{ z1p*R)c}N@TcL@ep1`k54;Hj+tj?p>f*CcCL<&U&gC+3GjKv965?w4@o;)1w?VHE0T z=7NJ5ws<0ICMFwYm(ovVnD6&42rX;i zH`;1xlt7SOw0`Uj;um<_*oxV&C@>c^$jcQ@@M#4+@C)g9CD<1g9=!Fe5=@}LT$Vssc`*r9FPESC58YpNj3?j>VET|Iqdy!4;!pwrLON_x!g}!zi4442 z5k*nEbkLT*{W}`tzhip&cW61JW?#wkD??6nG}*_QE@=Fm&HSq%U<^T43E0wx8VRi^ zcCCq|O)O}#P3mO&H|uF4qm5Rrcrtq<(jrkCf2&DJbVe*H&pbDZMK~w!P&Xk*1yH>} zk>z*I_|H7E8I;8lkU%>IyZ3(vwO&Jx1i3=#$wOX4>A`G6ZvP(EMXV00*%Zk%M+X_a zGR#{MmO(iVe~!E#f*6E=(!nS4(jlQk!zXjnq3#i+Fk5 z^N{N`;`rn^2@Ew0$e1Z2R2$trh(ZTPqek1~yCtECfsh>}?i2#;<{wl-sgf z>wWIw+9dXr=&QV-ylS(7yrCB8t)U+#lTQRsKUYb6E%B&Jck+sFrUJacE|NI`XeAK+O zzEio$GS$=bBQ$N|VuzuL=C6jpElK+IRBBJ%)w+Kfe)h~Lb9HCd^AQqH?6DC&UX0D(8q|oQrF|wfLv<<>j%3#% zyw15P3z~-4xK(6dK)VBD_VcbHFLR)C4&?|<4$j56Wj#8bAhljVUmI%mq{SVaR-=SD zNzJgC`5R``*CY#nfvleNal8ZecUnmNhVjMJvOAV7Q2$@f^B6>*TJP)F7SuX{u?H2G z!VaO&|C>Sg$bQnsq8`Z1V-s#RLH0l%;*&Ewc%yW04wV>Qp?7ay;I_&P*puuTr@aqK zMApF&_23#klZe_IQX$gPMiHc=syEWZw~6@r^*QtMs(zr=Gd<82|1K(S$M0FWCx5au zB`7II-4Ka_p|#^~(|XT(AnY?0l}JYcv1M0M!ga^Q+H%SD`C)4uG2x}8c+_J#*W<#kTKl=sW@9#`X^8|S zJ<9vCd)oClBEJw5jvdF$Hf93|ACVBbNoHQT1>?T79r@^NusWZ~68`=aoZLlt>e1zS z)#Mf%HP1nJi+OIkbDL_zM>sa1utOg9Y__mWkYUsD3=6%bg-OngYG~Ckc~4d2je(Fz z#XF8IY#W{eIoNY|Hz{8l%;L4>w|d^Fmmbd9T?r?@GpDl)M%Yo5VCQ>kymfgAcHdU^ z{1XF+z0Bb@?q*R%I>s&d;@49yI>JHIt%<}U(tci&X_itw|qg< zGaQa$rYM-a(i|E{Y6>1-Yv*L0RkMMEJ_vOGSR}Y`@o=47L%*a}I{fJ#)3#fquBT_b z@9!|0nENoGan2NrpROievY2RrJla&RJCDaJ^+!Lu)AxKT|&SGx@Nn>ew*)j*nU5Ybahor`t0&{(&?(d>C$2T@xkv{7zRIB z&ydGd$}38y^%T;go;)a2@nxXy@5fcB6L4c~X@8MpSt(Y>Z0xFi+P29v=BDl_HaRb_ z(ASES*iAv*vDQofKBnVMJ*&tR`Oz{tFmt=iQsW$0cME<-T#ozwQ(a81447`JMRH9w zrppe@U@9*w8}C-&>lH?sH`Gz6-q7f(%=aOT{Q#}|4RTesRpG3Upz8u^hsST!6g&x+%A{TTxE2Wezl#|zCU^n!PXbghb;&|Bd> zbfgB?&7&roim`Vky2&e}L^cB!sv_L^K-XBqvysb}@qa4H&cBoYX*Aq-7@o0U=n_i} zB|BJCf);%fGMVh8^t@`g+u`OkAGzbEGukQjbg3=GC2-tdKPt)^j=juXV(WbNksT(` z0>0Rfu_4FKM(dIIYX1EOSvP>iq$fX?qsvpK%>BdYM!_e3^z?P`d40)|ix2;{;rm%x z`R8m&HJQgk*cUTcR8e-r_;526Hm2@AUE^6;@4MknbG7BRxKdZk@Obmk&txRjXgkF- z;dvx5CAV(g&RuCS!`g*L6#I2rENoTzw7Tth(qWx!#^*aqood6*3Y4lP<@|B%^=RL- zvb9rW$yi_B+V&P;BC~r?WlyU0yngfJv6f1kgAvkOt9F(u%dl^ksl?-p?iM2*ejn@- z3-u;oun_7@%_s_N|M2>@s7bpUC@;0p_amAEAh)ZBWG(+8RWQ+Ax+OWwHM_;D^OP0r zkTQWm%PX2S`x`#R9lB-+L!0o{-+|;>HV-Sjs~LXyrj%s`@uy*37o6v-jGY?$s(t)i zQ?ti)_cHWp9gPxG399LQ4CEjcj2Pk(#Kgba{Ea{R-57;wrnn7m8!OTHdW5w)uX8Dj zY_KmPZ%)@afybZqIxDT6vC+?w`$YMqA*0-EU0Zd&!uDHD>B*w1Y&Mt_!KO#0p>LUZ z!=DIgjC;~3bpwkAxewm)OdPO%Q&=xDJDazwqR`hQ)X48#^5R4_kE1GC_$&QfVSkhDNl9 za;!q115;1ASs=vm8h%P{p8(Xd{`1k6gZn)Jl;^}WZ{yM7<&tpbJ((QMc&RA+Wzi-! z(LMacREWNOr3Dxs352WQBlm@1x!g!=A)edLtT@_QDv!VSE&XqN(t8yjxeBT_UxyZc zgkcx&y2zt*d$8K;_14Siw-D=?i(4>vO_%wiYuYq|eW7Wj@P-ZxW7kQdp$8T`BAxdy zJ1pC84=bH3u6JmY_qQ|N>$k#oh&SHIkRp@nO=^~Sr)hyMU;qDIWb?md&Lo+jUj^XB^ z2fg@n<_orM?N)b3>*z`kubM^%cC*ULVVk|&>%;6xigAcr&P&*Io71k2m(@BROLis}58~P_cks9NeVDZ}D@kVZX-up}ar_8^`;Zq-4>ysuSB=&*1^>*n zr9dscj@GJ&uT|yBi$-*b=WeFwVz@8trK(Lst%WSxDJQF~F6YIJv(!t_2f;az`3dy) zYNtY{J8i~RuSs<+zC4(j@HfD1VSlesu%J9u)E8N7fh!K`<-{=W!&F{BL1x6$CPC_! zstN{8UV%3mKp8eUD@_foye&FiE2$Oy6@BY%mo|OdB)MsRS+V;AG^s0OHM}NZh z3Kwd}RsuPCRS))hzW3g1U^4~Yn=jUP)#kHMZsyxP!HN7mAAGpb*!8I|b)eOUaJm(` zMsRS??!+SBM>FufPv^VR&9r4#_K)>8N)Nf@_ibwqGTH|xzd7HG1VjvO3Sy=j zE*e-f`KW%n?!-0HPM29MKl7d%a!#u3Q~=~!IY{n-*OaQbq&gwq3555@2dUarpq3C{ zDo6Me^es(93d@W!!OQvd`ePM#3#v+A3Y2$YFOAFnkUB-gS7*$@+FaMVktWyn5(#=t9)zM-MZWvd=_JhP0^-CK zM;PFRzt$$I(^efLyjw3^pi9`;LKHp(p+$+n)iE9;m~i0AW7K5#;V=5IISc`bmG)hf z?~##+!vqB_#2Yyd;;}fbl`Q&I^p@tKA*2g+N!VP$)wAc=PdUEhb3;lqHoB>h*TFjd z+b4dS1r%Ut;S?f;)HU%0#d-MP(nQdVYQOg42e+fr+t6WdKvQ-#JN=?pqSS|)1Kp6x z?ZJ<4Sf>i(`zaF&PJTBivY!chQ$#nMh+zq`$463?#gxqAvV`L0jhItb7cDl$x6Ht= ziQCS;J7rAGsXOI-(G_p|)0<+u?t{u53T90Xw1tf{1nOKIj`2jB-SP6o;_s5Xaek-k z?eyHBZH?L9{q>6A-|e}3-YC;ZdXge9j+~P{&3{nwptg`oNhn33+DBErrBIdXV$w;^ z-zWwu;;EofPb9OBgPT+~NOR(&rKnw~TP`#~ht6N$m%M88)dF&^?9?C@oJxfyPD#7{ z22NGF`~{CZ;zl`tVIJ`mrl%q;OhcX_uvx2lZr(zLhX-Z`H$>om(Uh)VSJk8)D4vI~ zFo%FPhk`aYYKh1o^CmjV_&NYx!6iW)ML3s9j$7h9Nln~z+=~}=QwQ{PcI{Ol?gCQo zWye0y#)=M*$Si`CY3)5ll)}QH%9%)vAEqb}#bE&drrN-ZrhbL_V8MT<6z0>WwsN#d zb79X{Fg0hR%Ns17oQ54*RY--`{O$u|-mju{W>jxMI^zE#5PIU6dR?LNd4JbZXxzL|%QSTo7&_4A^d zI<#y{x?#;nh(lc5pWjkm&09Mit?f@`i5V|rr`_X}C7s^duE%Ya7vZ;1IY7u zIJ3XOG6`s*>8YEd?C|SQ(?B>AZ#h8aW}s7AL@NEP^UO3ekgL^-R1N+LS)vvpS}V+C z)*+L)xcKXauyFk*smFeRWpGVWDpI7Vm32@7lYsxN~n6dODs7H+>#!QbJ$aeV| z8s;J#>pf-FhIC3{9HHVGr0+W56FJ^^osl1fki* z4VH^@Lc)OnrGtk^`SmJNiUF1v>xUqKkBo<649Zm~F)#uZ%%`p(@=zQw8#JF+3)bx4 z?f*dbw&WN7fNO_9(04tP(QQi&hxU7&+^_V5tQLg)VrNq)2soe*jUNt_3S~6N-~~(d>q~?H zCB67fU@0Kf{zC$=yGjBIH*f)({vLIt+8)(SI{wT7yaZ4>;548qKlZ^?v3NC+j%kSS z=;*C!{r*O$MInx?g^%Fc5q^=_5liiCind{-<~2H9)7_lYYU^)6N&@l^$|MM;&3~2|k^1eE1|X9F;h_SQ3-ZQ~VA~E-%93Fc$`A=m z1*XQx;$R>dD&l*WBt6bZX_f6^N_N#=o6_ihxP&ZR;9U&yb;f*h5P*3o#EF_kl+Za* z4nYprDXVux(Uod+2;B@PFJ#vg${E*xC&!Tf94P!WVHv*yXRJ;wA3)lcPqrOO#| zoiOX?^`hS%aeVQ~p>{Lnqu+se5%dYeN3&{-!#^BxL*l2SFk|E&lXj!zACkXk>f@ny zBjq2ce9`ogyP5u@J1`9c4VXB85!Ic>21T$wNOeQ*0cO6U_K9h^W7{69zp`!-a#)~W zA9{aqT_1ydfa4zsdui0oBl$$e-mQB@Z6o14;`L0Ayn^uck%K9FkCwg?e`(Y$Vtyq} z-B zs{w8%%fcO`fa`p%2R4r^yi!UJY}&0Qt8;@(=XzB4qnY;P792}80&k%|O+I~o$QdRn z)r79es((8|O&5_LYdq3)t_w%LWo@g6Mox&Ev-kQsmBo9)zMeK|x+b;P={pyCW(%f5 z#jCYH4k8YlBfFBDo0GeTSSPG=dP2o7Z8Vb1J}2K7$$e%mTA#8WC)8toQ5#)a=RLd? zcTX~{nR}qtMrYB+(AK(pjt$;U?u}dk(^DsZKQm5hK4B96 z`Cl*7&}zt?r!ZCLD&==G2AKa9N_qnMv#Lc!*t;zKTB_%_tOu$!QR#8&EZ&=SwH|cW zSYJI>t1AN%6b#nF*l&0%40YdnTEOA!*l?20eSOWa>nCQsEgyz2twk0b^I}clhq5)_y@pp{o&`ZgMxhO zx*ufYVY{$R!oGAzHIDY2)*<#4WSBCX!tKc8x0ii-)23b`<(G51UKh*3_m+`hBU}?o z!*wKV*c<(U8fM;$86%e2SM4GHv~a_ZBL14~l+v0WV!Z^0OMDx8gSr0+>HkZy`TvCU z|9Ml&_}@%l)#d7iviQu$cbd87x&EN*V#>Bkk&=Xo0!q0G2`L2mvw+ZFM#ofCM3Dha zM3e@Sg2<-8@Vd)j2%O3VR75}kATa)Qm2JlU_UYwwn2X|IoTkTE4M}yJ*TZ)${ArWP{uS}cP&`qG6lw8PBQ@Ic?K9XiM2rM-7FYCd1AbB zoL>jAo+^shBQVK&rTC^#(w6zYLsIIcG02=)UpozK%-*Y#UQ#W?zOdTQjuNm95{axu z_{D1a+wYA(CB=bUhS!`S_1%oMI=Qyx#U&tf5=d+Fe6{y)7N2-`?u-LLMZoK1<7+?& z=_R&cj#l~)K(fIX*S*2g!wS+!AW`5`0yogi$k!`ld>iH(d!P598VZ1!B+CcD_~yTp zn~~mvCZjf|clTj1FSa=4|E4LL+{cinRRjUqg4O5?LcCPI!$*!|rqMjYze|IHDripI z83KM=95zUg=uT+^HRK_FS230FosgPh?^hyY{C;7y=t=Gr=Ngb9EW}5|nPTV*K45qRKiv=Lti{pWsTmg! zYznVWdp;9^@(=Fh@f8;B8Q}!q%?Za%(T%{4Ceq8J^``9XK`PO-L(4IqyQ;v2(#z8a zO)GNwHA&l!N$%g}yqPOA!0tKjMsM#dd8t2=glH%4j-$M*p_^ytt5)+qrK1#Rzx5)JR=0!it#4&p|Ws=Qv<=C?#y*k%2Cg&p;>D<_#>m^fed%JPCsxxXp-9-Smo;_y7l^Q)}&NR6YdA`q~}fpK|w6Ge>e#zhr z%!v}IrinuY%;1i^j?9kSjtKVz_mKMrIi|XXTgH6)d`8^{KKd?9zD%L%gh$!}5&|j$ zh5}*&x&jsgvI082U_WVp7J`t0%D|@~F_7nx3qaXHY#`c6_h$dB1X%*Nf^{R>Y4=kH z*@AkZ*opMB1@VDj!L=jUG4JK~|LLF7yV85m3+(;qZRlO=ed~n*3GSWvMGh_pWrdK1 zy@Fi^u8Zwvr0=^k-uwP*8O#P22iA-2Cb_p2q!rW#ZUgT{d8elL(_j3TJQ$;(fgq#c zn4qR0soONZtTc|CpPTrT&9i6`Sz*x{+Xak{sAw!`A;RYdS zp>bg=!kD;7p}{yt!fHbG5FqR+bUJ=PFb%XOmV@P}4&ifX6~ZdYoysT-VQpw9`kmyc zI^p)e%CbB0(Z0BSLNlR2A!4C@;dvoP;Yi_1q1Tyv;Y=ZVI9zlO(G%7D?EKAqrIY@d zbK%tBEu;=AC)tybf2#pzLVEDt)DHtQ)55L6Ul?zmC*3nW!tx;=1407@0}1_119t=C z{eK3|2Iyds`{`gZG1y3}c&%(SY}2q@tXHB9J`Jb`u3)s#pEOny4W>i8(4K5oMhx^q zywRVeS90ol`eTB#TR;K*wDu_~(lN!ON*22`$C_CGgy3|oO`0W|rkXYN51QI3Ln)cG zty)+0Jm&6$d-Xil?qhc)haeMXDYi8H8ZLmZCY%;n&8gtfAn{=G5VN4;;M9=ipygma z#I4_aTwX#i2RE3zj{Tnf$#$+UaJ#KR+nC*4Ue+(eH{|`fFutrG**CHMJ&@mQAICSF zyAb`cA>YVIhz8+d(GuaKF%(f{Vfw^aM07D2n2c=wn2f9i#CvC9U`QT=&PeQ{8)){f zgCIx<;yifuoO?rI>_l6!FD!fKVYo;=q8HH|DE35qR>pS5#>PU%9!0DTBn@11c$`EH zoDM1nFQbz2^TcN2Q;|u;(W9jC6U6EwTiCA(2Ziy0@zY}6zh7MrQbw!e*NJ^2Hc;F7 z?<5Y&Dafd3sK}@|si-RmDtN}x4#PSqq@u8p52G^BoSjwll8~B7&Qi)$70(OVM0rsk z#TFnGb49(8AB7jD%J>W6#oe-^kg_~M0M1m;dG>RR2Rw9-Ac=fMSY`TZTMxR^AKH3 z7w6N{il-rdlsCx|^1IW8biM7c58}J#1$#aJ;8oNo^ON0$!bN*qTw7V&U|UpMXWLv` zMq5W4%pPD*Y(zFJ6NjDHMowEk>-TZYU&5=DHtv13k?QDmjAxOJNPFACF8pVUjb(f8 zJ)g*RY*&gak&V@totM#uB};+UOp} zZhjw=5AWOaJ^V;trmxqR<-P6bZ;r2tm+L*>@NML8^^f|Oc_@FVT_}5~awtwHQmAGq zI4D`DLSz7P2`NzSM0N(bM!t=7cPi)|8BY>J#*J?`ItT%|TjGV!z^wl($Oy^|Ss2*} zdH(nP?+N7gL8Ne@y_G@g-)1B(q)((+;p}8vDQ#5t)_Wy`rO0}cUhKE?gVy2vO?$=wYUe7IT%S>GHb~< zCJXVoYsvS77_w|hc*z(^IoTGnRMN(=WAav#SVfoTFSLD+39P^T>PriI!PTYk4p=Gm9FJ=ayuv=PZv^^ zwq-sE9vc^Im3$>O)8EX_b{Fy&94!bf6fMFnFfH6IiY-_zoGs9cnv0_4GUM1dt;N=| znsQi3C$n9I*Jv$VORD9xGFuog!<&xhbY(rsFAJOK=UR#{CEiJ`)z@+@Am(FAvSqk4 z@zU^8@RIRza}sjWZDi@C=;doAkLR)H=w)o>dPuxw-hv*e=SoXjOQ+>}$h>_z61o{a zJ07s-Q%kpVeAM609-2?F=W``|={^@9T<7?uzKK59A9CkiOYtT6Grr`-1VTuO*@#Jr zxeFNzi2^6hN831zjE7fX&4w4EVxlUU(Tq zg(=mTZdyCu-S$X0J=PT7lrQsD>S#S(i>cMrX8MEDQT^x(NSt2UkXBV!l~#37RaF&G zbw{I>LVR98!)Pg^X1SEC$=uk{aAano8QG9xx|!juzVxYi-pFRcoBSxWbgp@A;+^oQ zy7a8sWBM8I%y90ulzx$S5q9x%(R8tKv3F5n#rOVkQF0;OIAWp~@2%=$=Hl9t)Wz`r zc44yWYFs<6o7hF@N${S2k=nRD{>AWqe__4KZ~C3nMgM+%k+`~*HIsFcRg?wH`p9a? zTFZLNf^iJWns6XHk{Pp@PS2drEIiWAbTpFlof^-2cUU^)Nx3(ky63Dt?99BkpGxK2 zKKw{%BtNdrGU6oRbaaSxpmuEExOc#Iq#x_zX{B`qTvcsYI=nmRv`0GH zPId7<=WIYY;*UKZ@MXU%U1hf8ANZua%U&V0b31+md9%2aJg2Y>qZ`No%vJ77N|w>h zQy%`!LtJZIeO#+t16(6(Mygdd=Wt6}RjvSatz`f^Ah(Xs%zN=I=8^aueaW_(U&~bs zK_eFMJq9z{crbKSd|+~ndZc<#?+)jVejE+N&O8QYW-Q0MI&TH=@LqRxX5QIM{N=v( z?+m@in_ka)Hr&E?zdrWPcy`{3>D<11I>H_2j(26_=iq1JC*9TSIjeJx6av#m!>3 zt)CjYmg<7$f-VUf9ZgrSt?DMGUp3egnjHlmg-`j#VOJ{{AI(R%t@>u9e;ei*^-IUC zVMYrDfTEM6lBAiWr=q5!rK0C#5U=2*IXhTDDS9dD80itYP+%EpLvuzC&~(z9?I;+D z$f5D*K4~s+jpUd{NZ|`QWZU4t!&R)^pgi+l~*WT!sbSM?Q z6;+-(N7YN#Ua!8=&Hi?Bs1^65OG%_?| zIyopwB$*_|IO!lcDPXKHZM?hoL!kscrhtrib%6+~S_(D_UL^kAep) zO*>6vO(9K>^yaFfst%woTR~O3mCEW%gQUs)glXy|b>c)+1F6Zxgj&+3)|0|&p-G_0 zbV`@flgnyKL$%5Jgb&raYD>?B#A>-i2~QPI2~RsuWoKSz=Z4x9MNgSF4ZiXgz{lhj zcvo}xdI)$exGO$vP1#oU@qK-}lRcDA?os&ic=bHYP4ZR!@_wy7^i9I2;n(#kAu1y( zAnGQnCTcBcE2s#l<>)CW-6c;nKFcmZXZK!J~NT{|d>uhr{K3tJ9J6sr#8Ly;9 zS2;^wR^3u@*E-xxMo{6?esw!M8b_zzF7xWSn;);Gc2)UOYj3?%I1C?eRP9rZSA|kN zRb8q`s5qnYTnwu?s!&wjuOw5It$+=$i_PyKWQ3kr@R+)Rx(R@a&rOU#H zT4U>x!en?xV{>0~d^1$@Y4g%r!rB?Y^D=DhXic$s-;#V$t~OhTyH>4+^-t;oU9FCy zr|qS1bLEchDqb_(T26D9q33$bP|IS==2N0|s&&t0*d@wDoq-i> zCeXAV%!<>}2=_wt|D)|Kfa+Shv|$JYNpKR}-QC^Y3GVLhatIRK-QAtQ!5xCT2Pe4e zIS~Bk-goXdcjkR(=9{nTuUfVG*-v*@@2=Xrd$p~u8jdm2S*BfnTlVM8UHB<}s&cD- z%W&)G&fgvmhyVltvH`Kr-<~6$bDoo*gZPIDET$ZVb)hk*ik(_@nKcHk*6wxDJ*YWj z+5_7oPIlY4!*z!Brs#Lhx73|%S`gPD?hx)+?lA6%@3@?+HHg zH4(gfHklE`_&X0p084}RKM(#B zI6Dl(hq91FJ{78i>P3R)ML9)8pD2-fd#Yf~`I$}vv@N-vPvbF> zY|3D6kkUMQYM|Wl>vn~F@GZ-r5Z&_fkr0BR)Nu@pA){@3P7n`$q1sjp`Ch z&syxm2_c6;IL>}e)+qMBRNL}tLwK*XghS@cq&cwRy(fK-4W$0ZOKtz9cO4j8jS#i} zOesR-K=46&zPp6pg6~21r-b-t0y+nd57m?8Qfw={2OW~^ofLE&91*GziN6>`I0TwJ z5r$K)_?iXk-Lf`PU}&A_yzsmz7ZDB-4sixi2C+5~G~5>Pbwk(_9f@G%@D$0vL7P9m z0FHkZ6InM&Qq=%=Lf6OhuVCmPXnM-wWF_|5U)r7e^#$z%f}QwYQq z$sf;j@AxABgwagVTvKmRZ%IgPIgH_OjM`Ao#4asRpR}OAV)W+Py%Rx4$?%7eFzkh+ z!wPs$T4xB|^yZNU=1s}>*u2w6zZAlulL~roRY!z~QPdmAra2A$F~d+8oz61wy?k9B zJO*2DAjQ8X|13fL-wXTSLsZW#2n@WQKqyT|h>u8yy^wTV{_kPy3b;yZ_0CSyIMKtO zJJRv>0QjRF%j?-Z_e36UQw5v{jUFKIElP0LkA;;H`#xVQF6;tt4GV+Pd2+-h>%7&E z*H$&Ntfifb=}YT1)86=Juj~tNtxsC#K@SQZRv?t9qahp;LT>;u7ZWsW$ku;F-ooJh zU38XaxGO8$+p~d$t0-S3d&uz8zCk5A8loY|^#w?CF~P%9{Q8UhN$L0x(e0dHU1yh# zZ}ail2UTPP*3TFOd4@xvmK}+3kxcsoY`NkPL-nABpz!DW1H8GEkYSI13Gw5Vg+hTH z^9Yds2ePhot49bAD+~(C$&d&M;YUCK7ZVCB%=X_%9|WX-2=GTh3fD9mtoF8$ES_aJ z6ur|=6GubJ8YE95Y9z5A0byK@NU)Q?dbshpLZJ>Ew>~3v^chw51n6^3e}wh^CB%q# z80_*}AC}G?0q;#TLu`aNg*ov4y)Nt!kXijk4!v*Hux0-Hd;C$88dRY_-;kY$A$N(p z6rf;0>3)jW5(b6ov_*oX`y(Kd%MlfpVY^5CeQDAYlg+%cz!}>RZ z-vo-jR~#Cw%C?XU9tn4-)Fg3&OW5wehFOQKb}={xI;#Thm1F9%^|HCf z&G(-dYY_yupCi_5=kJrfySFcC5e14Tks&X(ZFHV;0T3J;4wL)M#y2|O@2l(j5y8!| zf5_XdXsbrg6UNUlTHV5n4skcQyH7CO-NKWbMw1TdH#+-IDcs#=|A5aaUfpIJ4(WF~ z`_Cz%kCTt|H-2Lsn=FESNiU;re-rSF`TXO|$@3xD59Hn5`!^8|-OTIXWDMP`?{`@K z)qHxX59VKpnHcqkz}hb(S^ylMGZ*@MOv084qf z1x=9E6ZGD*4k8u+hs?4i48u36)<+_ZM0d`L{=cnVi33{tL*9Oz&&QN6wmdLrHMC0k0M}Vo~Z4q z`O9cv4HgaZAN_hi7P85{KwOwPT-v@-MyQnwp%k>Xon+-%PXp8x5+Cp7>EFRH5)rTe zvA+`RHy)yS$oxO8IVcf)lw-p-q>_{A;eXh^e;Kr)jzVlgzoXcN*+hXta46<3?{P2dilFJJA!TKH z63}dW`bW{~>Y=OXAZcZLM&F(rAdyexdU(+5CZYd>jr)h~`%eUtC*CF3mQ&A;{}5#C zn-$FWPXvJ{-=*hPZ!0ucD#QWWmhQVOS$`LJM+V4A@}B<<$$PnF^3GSzKNjAtC#VwM zksY#zqGtiRB?CeOearftu6!930iS~Z5d0+W2d@cD( z$Q|BqTrGj8qjElhC%C5)R-ga!IN$&x2g(y^i`)Od0ZR}aFk9>pwUC~OJvI>fP?tFV z|4vCsLKl&~n?;2@3iEdr6XGEu!bgpZf%}WjpRqs({1f|MB>p>%V*LJ_CM2=?Kk}kD zzK7O@gwzn~`TQsB#dM7S_d@=j?{8)Q|3v>~X0zVC<3JrJ4{Q}1;>(wPmlZAE( zH)MDt7aywFMA-iN>?+0oa|&JZz)CU4WVjl%UNw>y8ED>cLrTDIOF{C7{Q`46#w^NCVBp{@=+Cl0Xfy%Lq7q)LvQ=PjTql zu&vLieQRL`NQNb#m&5)A`F~aE|5O!}2sjZ`VJZ?hap?RoqED#bNCK6_{#%kt3_3V0 z4+V9NIFMJ&G4yZ;3AMy2izI@Hz7Oa%_+v2xB=QY0FnEi|6OLC5UyT17Q~_iGY5{x! zcES5TSh9>h7(U#~KU%F`7-(D+TPAaK3!3%yY-h0U>nqMe$3#ImUB+-9L?ovcFj{<7 z6Br%LjbUdp!||s=ir${4LW09X;UaRhB>8j5F6Y8D(aQ!qhbMP)$bk$^0LO>o`Hx1N zpu`IlAW7&6aRsscc8opjk5QOA#PXlRF}?5qqFw*9r9bx9fbDo|iy&)})Q=5||6LKN z9~%`DoK;Ekp3F>o0~`JU1C-vzV?^^%`2=v_KSpAZ(6B2J-0?b3Dw+vjT_-XfV zswCuCcBn~rh0FZ``hZI~P2g%a-sA}C$vJy~aY^ef_^an;6H246TON1tJhuI<_3mC5 zIY7odVtD&*N9^bLMMHie0Uw4SB4xqI-JA)AAe_Y&x+6ySq4`y${US_meQ&AlB{gnr z({&e(B|hH(j6=^wZMe|qDyOHKjDd>^t+$}jD0;PS@QT(3qr2Yxe-0?#u0Mf!>dP2l ztkU|Y0B)<7)AkY(*S8;Lv+my@lwhm*6{!u$@B1@=vxd)IRn2mP4o|z8i+vdE#KlQ= zvFq?YrA~Fxc;*s=2l#fY0ly%>7Jv~xv98D8(C*!4s-Y3P#$8(5*$xo!(z(#h1A5$n zMoUi5lgL!l=a!ZkJZ{r@8UuH%QzUJN?g;3*?uY_|FZpdkn0Wm!#58?Bia;YPBmeSe z3hh02biHFZJxv$=DmS&59xi~o-@qfM#A@D--LM|DY(HqrY0{L*ygHF~WMTrzMPCY2HU)643mL@Asfb4gZ9$>eLbXpx* z-qETin9%BklqxM}^JPQ3i*)fMnCq*0hWZ@68{Tl1hT6_4Gkk7`ee%T(0h?4htyPPu z^W$@pNS{T=&Wg94H|^EOIZx* zzr)8?mgi=8BeBw#Hkg|$OB27?<}L^{w9nCiCsP`4yA)1r1L0mzkJC+(M^>w$ZQXq& zOHI$tkKd-khEJSp7Zxft+sSMcYt7Y3YRBJJzVsG1mKWd3H+OTNIz7#tTeh$}Qst>h zUt&o2Z|9T{&AJ1{Mg>g8|@2ay{)A79(^O)p=T zKJgp^+2=FJE$p%uWVLx`$Vako$WDq)Z6EjVZfCUmY~^}vPzw+%A_!Vm`q%!p>uHTbfP+os_?i8-k7~yq5yXR@;h>vV~<#+CsKkiiA z#c^I2tx+6q44!|E8jhXlCLc`uI=1iP3=prWE_!h%>87}5Cg~<$Lf(Cd>9`?{=N)cL z(e;Qw;dFkMJ3b@3HhQp_l=9JFY8=lN>zeq%)STc$8~*Zs<_K$D9%PUvwNGF0Dh7I% zW4VFMhNcL+k6T+6e2t zos$LaA)w#FSxFXNI8;*a-bj$N4;RPFlIL0(?6X1|Wx}Pr?G+?r$3(m5Lm$)?J?~Z! z@Q9ltjMM6jP5aJ_$?_w_OrVuF7QSwNw%Zq74nvm8t5+mN1{z(yP3;FA0s-Nb(kc3P z#-ZxiO_W#5r?<;O(Dy0I;5kdjWtQXPGoAsr+$V#5OA(97&1%p3ZZ)Gi6JJWzOVvs> zEWU6qO+c1krK6Bz0@z2H;?|} z(laVipJ)f9uT%n)sK}*9R3egSilvWKLXxP>q?c7sG|k6w{PzSHxSBXa11M`XTFHu27MRrxN6v9Og^PVN<&7q*lJd7*3~Lxo?#-1 zr$|esluajtbRxN}sIu6m$lfYPtS^tvK${3Goj@iVQ6!U5B+FoxZz^Sfq-_=Nnhn%! z)7K_hCt9bXPqt66Pf3v;E7mFUJ5oR5IC4KivI=%B1*){kw&}DkoeZ7Coz$J=o%EfgowS{los0k?0F_<5K!QMuK)gV*K%ziulZ=6s zfsBE)f$XN#YSGzj^Q_10-7L}(?2+UV<&nh^=8?t`r&TGa9Voj(e-{4|cPsN%=CSB` z7V?PkD9=@jhwhmkKR!lgsAy*v-b#vxIy>PdUP0EPs9~0SgqGFWACC%lq}MqNk0x{k zgIhNyj&zTTTRSG+WRHtmUm*^CkBnPOAzpQljazRrj&+ZYTW2!f1s->I5UZ=W8;rdOqJF(c1${aLM#gxDIESRvQO*(0v9aB~YkKEy-%8(1-*(^74v{WZedFhD?GVayx*6um%+FiTf>)i9+`*?*u1w9o$B|bGgMLtzNWp=2(=xvZ+q}&aE z9roE1+yh+u-TObK`YH*K5vIJR#12dDnOr;Ff8!&|O;Q|r9WLEtz1F(VI#t6W7lQv1 zm}E#U2CvrlDH5$lj4J+Z#k~3l)pwaJv|2K%aG8XJsv@d#nT&)QQ>yg0b9~jiRIL$e zBdXWQ?~0YERU(qnioZ~+jwXL7Rw-AhNX98v|4v=iN8Mqpah&8mkk3YSKz%?ak>iNJ&ry1JtVVW=hAwW zR@qjaR!ObmwaO~>bJcSV0wsbki&V(eHpy=q+s97urSBTmv zO{q?4NS0xiVOC(4V^&66C$3aAl?BpNkS@s1%PuG%TY_2saI@5@i(eX}L0N{rpuSMv zRIX8$I6rdy2`uAQ!6V-(lU*~Zw7QUP9m*qn_Qj&oWxmxq)w|I{j@Y)+PMqTDMa7mu{7A^={?n^3?^0g^T%%1^i>=TWXumdf`j{x!UWOyYj~csAGd;VQ`$c=Ccle0fAa<`OpITah$gbfALFU zj`~!Y#ez$Ph6xaYUg_g(U?U3smydIF+ASFQ;?~OAK#alxYbWiFlss~4Y3=%yf?{hc z?Y5MBGixpFrj)`xYjt|Xv84|{k!mU0MH!$wGqiKWwCr>ao^IVk>Skx45wNrvlMLE zx#DXVuvjXcQAM3mVUw+XlPw-L7$w;8u>w;{JBw<)(x zw=uUhw>h_6w*j~LP2!JIkD8BSk6#|;9(7kM0QrD&Krx^iP-r*TWV30#X|-v?3nT;* zHs%6z8x4SOr)GiKD;FyacFj$akD{xU&*f)xw>GfAZeY*~@mbw%v%45?1^!%&4JDBH ztjJyFQIfYhduG!5fUS1&5Yju3pa}Ma*t?9NL52X(l4+jtG&mW& z`G^v%`XKmr7%R$8U9i+gFN(J3^Wg{Q_w6FO`878{Gjqjhh~^&D zEgWlEX&F4^*vICVsAb;NGz4KM5aUMV<7DLHGMMBWVcG9!o5VY1fB)ICp=rEkxMoSm zZqH)Rk%B!ItrO+9qrStjA8QUh> zZP0tiXb06K=&J1djOX}IQ$BmoC`glO&pyvt&(WW@e0F>$d^UU*d=4Fk9hM!Y9kw0D z9o8M@9rhhY9abG?9d;ci9X1^n9S**RzLvJk0xSX?0?Y#J0;~d@O*jTv1~>-T2DqD8 zt5Iiz&4V6;cY{bfusf1FlsgtXm^&IfoF=7L?cc|K+MP1LFx}#O#d(Z+9)#Rs+{tqq z=CXaV!()!Y8H(B&gf|)HvdUt4VOGGkh-w(*?msbKgFF}Vh~xc0C%R94k z=JW68W9vI>JL~9c=xpZL>#A!SZKZ9ct+mdI9#f9Z?G|mkYYl7J=ga5Z z=dI@qcMIn>YwPD;=L8lr#8?Q0*`Lsrka7CVOl**@PNz3?|Olo8@O97K<%u!ou91 zsMGUF$f&5oSgB9KSaS{>Ibp=ZV>8lwumg_uYeDycD{uKPjkjMvmhOboA@~$q3vb26 zyF`j$f)9Vmp@NuMG(ePpM4zH(c^~X|*3ma6=3oQ%{t80yOh@o`XnwmPX^{Ss#cK~r z|AIJnXe^0^SpS0BBGb%LKCQMsc z$hC@*QM}}n)P>od*4jHp=EX&C{8F ziVb3c|A9iimSWVHEO3?P_U61oIpN%vbYJA?hHk3IE@f(?mF(1RC3g{%ZMHv`KZvSR zE@Fr^IttEi`*BiTRsuk$v5PY836e-Pq!JCt;Pcd7r^PsqSosYWhvlC?z zni^i*f34VR5xvB_>y)VA?V%jqfvtFgS2z)SecMAyswIQ#5p}6geH^&@;bc!5!k1&C%8#k3dUn;wZK(&^>+? zUV)d~+4+L&#tKuFUvv+&awL2tcD->zwt@-;kK`OYk;l&H47zbzI9ua_Y?!e39TtE< zL7iD`lUa?W8%Ps;M{s@4&WzBYYZ=oqW?NM3X6bRw{>eY;ieW{IC${LqlnQAYd^VR7 z4+}cF*b6ev>ALfi84YsPdq3O$bxtC5Izy8G)w=zhMAiSC=Sb$LaAohys&rPU_m(sd z2Qz|JWN8QCW^)okKr6)fqzZ1SG?a;g1akIQXL09l2X5w1mrU;wE)nS1WM_pbA`!Ch ze=K`bmLF*ur4*%@F}tkO)ZTq-KqQAO0xM+h$So0I|7!C4AisiU%b==Fy_QvFG+{hR zLO)&}kuS<$m2g+q_|vYsIepUqT74GYqS7jTB{rY*6ufZbJ;l9Bz$U9Q=EIo|aT9Lg zNZt55MJqvDG&0=>Gm&E2BX&+8(G{F!20{J!V%lv(cX;JT@>kQpTD-$;C3^YB)<>(^@~5 zg^XlVZQ4qydJub^Cdwn#2$_pvAwmD3pI~8_+%3nc0S}-Y$_mhRF;1WGC;I5$k#6ek zcmmVC|I~-42u%N>?|i{{5sjhhb{}}#Wmiqxu_4G8L?zuY0ioX&?rNDDTC>J)3hcBf zA3J4^FH0BXdn@lEeujnKi!ajBQp+lZF-|!@WteB_Fs+7^bV^L=jP^QVj%{8>bfVuh zqLX(x>eMulI%8QeqG^-6R}%j*TjK#J>bf|7=sC}Ak4V2N6lg@b2r4v9Hnp7c<)bPg0e^h(IL{Xq^zi@Qsj^mS6X zmI`DgFol)(QWy0AaK*@e$8vn>8Ue)!N5vcXgnEZ^huDoB9iD@|!sbA6kjKQXQGP|NtSR0j6m8$6 zn?+aF<~d7L7_Ex6SG)B^uyZDZ6m=vzP0_QzaU2uw>XRipSeKOA;@l<)WRr2au> zkS6E6Aq5EPTK4WKz3=!&!nNtFjBG^??G-1Tr!UgFWJ`2It9MZ;uZWFx`ziK_8j3B$ zR!b;#gW{N)jY<);0D74l>~9Y%MaakzDI2t5=UxV-Kz28CWv>|9ecu#4tj;b%FY)+t z8?+SefuNL21^)iS@4z_MeR$cM5F9R3uPZ0uG`lC-tC05Vo5H2wB-JswYnAGV411<* zh^?WgLQNflmLd|x>&n;t@P?4=-7wlv0$y&OdjgpkyRKbYTdrB1G@XL2f7Lfabjzr+u(J2*=>Y7q- z;}=&KC9sztW6a8Sn zXc5X6{HJU@X3Ni zS1GsY2{J^;c_}l^Ry)bCl6}sxf=-IlnwV@n9cyh?%vUH)3OTy7r1F{b1W#bNM9*E>M4O|P zAnQCc^7MW#C@XYj|ABmkb}8|_kV|->!rbk1iV+yfAc{{#z;YTUu9sJi1Y>5SFfwFN zt?JawwehSGfjqbA-W^RYK0VQ!FBAUbrv$v0q3Jy!eeFy>*~OE1ruX8wlN2!cmeWK_ zq8G;ZjQ<#qwQ5EzCg#JsTe=mL)sLR9jjEgR~2Ewp;><;3+7z_J#jh`K99G8?Sf#1=5YRL zq&tyebkHs=$`m5UX)eLsFsYO@4w@^T8#NWM#!@8^o}KSunTfqZ9dn1UFvw6$TVjV@ zJXda-ugQ;l#F0qx(Y#9L&i1xd`k}5rZ8Cm$wSnK7SO;YCvhFpGZEiVuG%|vgO(w$l zXz3DhSVoxl5g6x5x$ay{tZ6n@H-bDR2OAeUKVssV`SnKBuFbN_-VojI8BEj+vg zu|10;(0(x8iR^2^3bJXlcy5$)Q`eE1w$$Ri)nT&!g^QdzCN+N$*{1i#5z`n43#nov z+=>t^sBDRM82WdJJ*%BfLPJoOQNp%ZMZEgK3^Aqr6$?9=&6R-c9lAvtc~K?^$;*n( zPL`mWr){ml{ZV@s<$@dSY$#=}!^!3JrL` z2Mk3uZO;D0+^;$(q8(wKR=`zf$*!s^o#-Uci^#P4p4rvCd|$gL5givh)vHM8yd1d(|PI3?5qH22K!uDx)K4x%V^k;6(jSc#`tFZUsZ~KA(fi1~e z4@S}91>F);UiqnamcQ9pn?+42-%TLYNLoP%>^~35H)njeCp;k(3lfV?xnZBq^APOx z8En^Sq*JvL%qjwT!zd*T$>E$eqz?eoDc{r5H|bbbobo?YS*2PSlJX0sUt`hhP=g8c zhp0fN<5u>}wRxn!BN)9V^XvoSBm^ax0%F&0!lXxkDplq0iK$S`kz<*3iIAjS_>c2- z7HZeD${fv!)E@!Uo_1E<_8*R*V1xH-SWP^a(mN))PonP5c;qL{ID|VT+d`3?i8G{B z_~=mEu74XZ`;MF3E-(I|@(-1z~EA+fFU>D+tQflZ zdonL!nyWrp4ve7dkkUm*+qt&Gt3L+e@W_7CLz}q5e}L^dZjc~U_dR!dae5@FEs<8i zpotxXidL*T(IUK4lz#R8SIb;2c{(>UK&F1eXFS34!xbq-ia~_4&L$YxP-l9^|l)GW|l-o?1gXNW;cFt1M4fCJn*}2NF^P^$RQt z6=g|z<&S4Ua=&FZIhZoXae#cKuVSgwj)LsJiKrW>PNu(vj+h#!*y@USccy6p-HEjZ z!#DIk0zzdT9T!^$nwuj)u8$sCAaJ&;70Y<=emGOMV#Z4*@3x+~ifxWRiegcQ*fu1f z-htWVkZkNRQo4`0<}PpK>oBwiqRquUN1$nQ;q({JSn+(=ai|Ds`B4&*SQeuq9=#8e zjwEsogx&;)b0rT6rTu$Qk>iO`;veIaeURzQ?bYch(<7%`zhyXWd!3f11={V0)1*h( zW-(^2K*anWW>{`Df6f4RO$fi)+h)SC7|$!uuI%2vrFB+6r#d8}JQZiF@B6z<2l)2o zulPhy_$3?FzK}`--q{INX~jVrZ>O<+M4a`pa_;JvjNiBZP*_?IPc}!UjkaJfDG+7? z2+!nSuio5nlm7oaa6|*Q(gOu3C{Z~+I(j|s9>?v(u&D`7ioTLx9 zr*Xd}|0C+IG1GFl*;wP7Don`Y+k8N9i|y1%y(zTU_Mly}YRTm^Iyzd!#gV}rVSULa z=VZV>Cj4a6XLD)YxKn@Bg^6tMq!HJg#C~l;>ch6us4yC4kpFV&x(r}}PqAc(5T_5i3I1MMbTF)h6X zkzzbSq+M~}lQdfm$JernP#T*$)m(;nKKtW1E9+ zr^L01Fz8Tx;8P_U-%aFaixV2F#8DBg}#`CR2S zF0J8dr6fXsf2VVXDHUQ$5a127G?RA|rzK#r-`wzMmBG|RxY^;Qs61`Z%#Ar=z_yv{ z{nVPKtQ#@Lmw8S9BYS`UBl#YgE#%h{3tE>;^4SkJw%Y_>TSM(3##O)KR7;T&U@h`> zU7*d59^D(ISWMGVf1cU1VV6wBATz6bvYs@lyl2PRliZ zMgBH^80+AANF00*2=Lm%jBw%cros|Mek9?~d9=Le>Y5@RTbFh*$ALUN?+>hnS9t8Q zlw#wg9GRp}c>!}K#zv;&5|G`xx)yYZI7%5o zoR~31reGo#d3>2P9(z3(QGG3|5Lk(S{(0jr%X%1)^W=1Kv*wfNQ|h^YBI@C$2G-d0 z=yKV($x(gqvAep~FcIp6IbxUUk>h?Jj+K@@K_n}16!X=+kB&76`9X2$IkDk7sD8`T ze+VoO4v8Rv6_$w1&vwiSX0l9W88dlJS5biviwUW7GGQgNa%Pub=pjPuN z>*7SwI#f}-@8^E34eTx+DrBlQU@)ZAkT54eelFk_jJRiHoi{#sU)URIwlcyO&6Uv~ z61b$(30-aRX_LWwhY&5Bqh$F`oxoXo5uV!J~GKwJj>F=q4s^A zhvzei-7g51il9o=1(!6fy@>V#rWDmSp_RSu)74G-FLG)%q>o4Ez)x zQ1T6{?7+_lG8BpSI_mG((k|8IwjHyJf^JRw=IZvD4ZpA8JUXAFPtP-jPtc#5eL{vR zz^goL)OO%2Y9~7LKY_%TA0NOreBfO(`Ra0RFdO}Z6E$}-d$MhQ?ke)TU41JdLW-7a zhlE$CB zafvoIcoWy{^X?*(JgibK>cP4Ur&OsZaePq6fuw+-GXW*if>pVbe2$mzid0$JB&pG~ zQOd91L!HZoGwLz>cZ0up_t5Vr!V8G{*)qCa=zUkQ-?>eNF+J>|mzl-1SQBw4Aw{4| zk@W>JN5$R3GdIDWd{7s8j>7lB+Jh6k|19)M?F!+I5_+aki_0L%=zz}_c@M&&5lA~R z=(!e^+WyG4bY+FS^I~EaTpn}b=7JN>XzbSGqj%Za(x8k8@?^Pj0n^r+wESqB8xO_p zSSUHE-B0?F><-oke4_tNrXGK_{~7~s{T|2ku-x093nZtev&*9zC z9RdsWJFUoa*A<YwJlS-{kpkh=TT5dFEdux5hJb;LVETN%lkovc|y`HR*1=u)FT?dU7MMp)XOMM z_UxcC$)f_H#TsF1V#wA=cI*`xRdr9Q7G%f;d%b|DRL>K2AwB+%1S-^|vKDBt0xv~? zGZ!bcCjU)SFJ#bO#DtrK3S)Fsv-%gz*KD{E&ujXGK}*IEZOGLTYemw@Hgvh#hhT~7 zv_=H$$u#(BE&E_iQn-uARk!dja_vjfHCPUXm{qG2Rr#u?YKCgAwoNwV*fqtKTiVn9(6-9v0>>9s^mS{3vtEqylK&4fluwB|MRL@}j-HyW0lU3!l$sfY zOd`ZDGBV+Y)%#Wa0cXMWa3WTH2qU<2mQy7*totEbLmtv)8DU51M&(vMcqiu zpq!K2O14J2ZHOOr!oz8x1^uujVk{YKwTM8wX$dU;UXlHY6D%1QGD;q45pJ2qy(F~b ztGoY|G!QvTL9-;!n)j+TQ45}^f=(mDIqnmJ^gR4nG$_;1+~6%KA;KgIhrMa=s; zI1~yuOM5{{mo7taR_N4HMJhZnQOW+9i{!qFjS7@yX2%&wldb`tQS&a`S$Vw4)GE-? zs$|Ty1Z@&=wsXRVJaeSszabg&zH5Bf`X0o9=awxs+2g@u?lvi2&i~cVcn9ZQ_$|%= zdbsQv15^!2;5q6pob}iGnJdswboI(gEGy>MI=k8=a{nbe<;db%-sZ1$MYM9pjP(to z_aOW!pMa+^dF7Ai`QNAc_S$?A>DHrO+qr69C7B>@%U4y%lCm?I_a#LiuvBFAs)cM%Yp;_nGG&MF)k;tl1IcpK+XVLLu{y^XT%C7C_Dyx z2Ua#rFC?{i?#+4gKukz?I&+F^DDxmj(2AexJ~a%jd9u@|DoxEtGRk{sxwA*g{7xte z&F}RzExJwblxOcSu<6fB+l+sOkJpmGvrGY(S}H9)s-tWfUO2QeW!w-xV;x9Qh(JCR zF;yB2FGP9~rWUdTmT^*3D7$>hZ@uF0C<+Gstawn7uYGSAF?P++mbr9|ejOS-KJOeY zZ%&TK6k9y@Z>KL`lvwNp^|`$uKN$=?W7|)UZ}N`W@wqxYMV*u$hq}3S?+cx_B%?RkkWMXOwr7YZ+G8BrwU8*QiQVcYE z8c4(_phO>(Y^&456RBjD_~Aq(&|2J(oR!7970RMsoh7yluN|$h+?%I4mF@IZnvTSo z#S&MYJURN4vw&{WXq5%Kl54=1v?{TO8f;ibraV%5)OhS|DgQ=0KOJXY1`i0HXNBUr zM%SK&K*3qGU-?0Xzm{8uJ4GB<2}5}M@i|CZwLi7}e$&J$4h z?i3S*3PxO_j(?I_C^9j&TMJH8b>wsZ0ebi{cake96-ZOx@YBdOcVe#8em%`7HB~65f*{yenC7@&=fy0oALu6+$QQkb9A3cc zHx-Qk@l1g@D(F~TDxin9pR9#@rD+Kc#i|W`njyQV?ubn@vewL~ZEWNtg(u~u>XEor zqwyUGN(bG~hMXHIh+McoLZdgu>H@*YXUO;A)uj$@-HtHaNlx$aklr6TL3HND@ld`* zV-eMA_1upAto?~Ou)Fh142Rjuy5}8BNHeq`l^R`XBxwVLeS9JDD5GJq%1G1>Puh8B zho#-Q-JY>s7Xjx-U4%-^@RNGJ=O}_I5qPqnx+9-|Qm;q3Uz!`jBdM^Dx5Lw|+tpc! zbbKKNZ|n7z0;I0bCKb;1g_vC-PX>UUx$kzN7|{Dy)#25G5ZldDt;65$ahB@bmGecg zD;#2D0ZNK&;u6+<^tk8D@N&~b;ITjr#~&Rh3CJf`&%(QC+_UWb?4nUYxfo5dRPw$g}%)?OeKla#5^~$D2}`a1x`-Z z__6HTdk;rGe9JAH?!pOi;R0{?b0gv7y`)Rf-rm#Vy-&tslX`I^qa2lrPPpiMf&Grs z&G?H$Mo`rXRv6Cr2GJo~`R$6UVAU68PP!bw3WpLKCXVb)&P2kNqjLc3oJsKJ0VVR? z1RLVuL;C|vdtf$ybo&Nz_68wgIQ#^5Ocb{3A}k;N#$!*5ElvDEY-D*~8}`fcs`1KQ zOV3z4w!+yX8%%vCUy-@a4(6{id=7_mR$x?l<-`!qpqoR~+kjU!0$ySckceK|Z0)8XghRX^#4gCIhWLX zo{E)o?4Y*+M$)GSQ!;F4&PDxexAbNs`W^_GQpfe800LM#p$R`-x~dP+qpJM)1<6r6 z;olGtcZoH9l3Z9Nl!|EuI23K$>Lh0E`Z}?Sl8~4r;Scy=TFzCU^yyG=y0=KK z!ZGR5cp2kq77}05*0R2SW7lQSPhMl2`-m#gj{A!^Sw*IMQ`VqAl8cB2l9U;yrWIZ! z1R7anIId+CtLEt2-3KntX}ixi+{LM4cD#XpeJN#{)iU41yxi2b|3Av^DMoZC%met? zw(i(-$F^F8e0n1v z86L0T8YJ$XXXv)>_s|RvbZwP(JD+J|qQ+cNs`gB8AbgiIFkX*E)r) zo!=9^1^343NR56%`)t_mgY`nUq{DrC$nAkee!b_Z$DM-BJ~54OxK`0hp+8S!5RK(_ zFMjUQx_spt#k?6Vj7FWUz;~`VFM^x2_amnRv!ekQd*2nJ7+up7s8nD0S9hnhifqxhu37UQI7TF+SN(ifbi|M^S5iNUj zN4avc3yv=8V8-&NDfP5I#x`xQV>Z~HLp61)ngNN_Emx5euN%BAoxosWAv{*CjpcyVpiJ~p0 zSIm{61z0YS$lK8$wN5+91GtytqJmRxKNOo+o{s;*`j<9%JTr9PR4-j74t_p?yt`73 zdjaMRQq60S>GJyaPCsUB9)g+FKBPYmHy~&RD5o8-Q;Gd6Nl;z58Q}bn7jV+v9d-U< z*Dzy`V2pu)E6K~4m}^B7mBbHoDQ1+9Qj%g3(ZViJj51W0-V;b&8kmVQGfGRlsixJT z(m&L{xhrgK9H=CK;Tpk-yH_tfXnjPVM2!u(o>s) zS=1W8Z-$(2_}8{qHGF>rgXLvx3n6`x7&4*@UFA*d%yhwR+hiP`yDMkZq(Wx zNbxw|6bofD(QIWJLUa36WpE+k2FGZ;QO-ILivQXhv8K?2L;fi%RDs#CL<2m;Fr|uF zNzOm^Q$(Q*G9@-~tP1g>oLmFfa0ehAEC3~%-S$=An$Y3Td&mUq2<=H7P6|nv{vye@;E3x9~oaok#>WV zyih)328R4c&}ogUZb;)1@#w+@c4f;hS;9tUl3CNABv40#;L8QY1;>L3TAf>`6LOJ_ zL6Q2~>XwyMifU#Xrc(%-_dfxdqg8M;;M;#s_IU=)E*K|(=~^uao4cD7+5VN>pU5oB zDdsJSpm)pZ6Uj&dJb|baGUMbi3*b9BnT^aJUTsAptraDRO{XCS=BA3_TwTOkJiiKu z=awKBZ44xwym$p+Zb-NLkqehA5!1Q+0C7^;Aj=T(A@J?&XtZcP-oGHSY#&?e830z# zF<5>cLTD#*!HGdMh0kW`9Gc(7pzH7i(EnmTVwD&wU>1+a^EG(t^nZQBm$=O4IdeoU zr3C)^?bXKH+xu%g+e>5e9JpUDgIyE<#gw4<8sAPg@=ZQm%iSOQK(H*CcIG>9p~z1b zQb0LoYAy9b>YINT$I9ZQT6)aiD8Fv@T&0S9W8k1hI-svy#c|*{rF>wFEpHK!cS)jH z9@?}r9hRPq<49bXQ<5Z&cOPWl8HBlFTaI3~?XHxM>tL{4HqyB^3T_`O&Wm*<7Vso8 zPx26cQ%2-M8jRb$?dHhkw{crJ5~?Nz&=cG$7+dqv&xu>@;kPsC*C0RDf4)RY>FmWc^(%+az=~G=2_^*o~ z8W0mgWfeLloMYD)hER9x&@tMq1Mp%0V=%|dkZhMPPsV+m)gRmQJCLY1-X(6etwGqH zBpwr-fLfy%%(qVG!I*ZR(<#74&D%$RtkvEahnGixq7y0+N!EWF1ag+={sVb zm4|Gif5MmjDAq^Bv&gLY+49d!IWG-aCmFso%&()-OBlX6mDROD#3R~C8E80ehfmcX z1vI9|E#t#*1n*CkJya5xt|ShkfC91+mk*h4dl5Hi~&whbJfDq+lvb?Gv0(Zv;Uc{+dkh>V|r7=G@#|0pe!d z*m4@^z68zoiLr^puFqBzI0;D9sti%EcM~HqDPPsEXa&o>{L4@AUDFulvQTA}63w8M zgautX<|w+x@6J;VixK~*p9kv*`na2v++tCVo?w?@dam}?#h1AcHS3x4ob<!+OTp-LciU!JUs(P&DMFzFfSp9&fy}9 z)|5n)sQ7dZfHMHoNFN0*(tPW0km?&XH78|0K&vmUteA93 zEQZj^veMl{de0k9fk#Z|5Dm`1n14T z8*npdH;^(rh~o35@+O5uDx-^t50V%g)?K>MgbFMYm9AbHbgt+v2~7`Ss6=_hQKwmi zVS-IOQ9)4&Uv*RkE;EGASu%#qZ%RNi!Z>~tWJbr}EB1TcyWo*Jo7=`KPE`hV;g;t> z$idU&73Eu18Ay=lve6)_GSjWcBopfW2xajBnlr@-AQu%l(`&Z=D1o+5;l+}l$6AD< zy=6)O22IX7xy@#@Rm<`NO%oXinC8tZ#&)heDk7NjAPZxegz%r_RhuPs5pecH;o^cv z2a^}&iUzY7(3!`2BKjUDf)z@{-%Um{FQFfu0qGBe1U)eT)rD_vr`g!~7wCc>5DP5& z^&z22VIfvkO$dlNh3w3^awMDi5#)H%MJWcWQwwClY!qK=N5vCjk>r|W$*gKVQz_^R zjmbs}q?&hS(X84ejTyZF$!mcX7U4ChO9V~K*oBnQYdO~OQ<+qcLa_v|`7P>HwJ(;3 z7O<`*&09Lx_H9|9aV^zGwlqL29y7~qjGifSEAIQmoEx2#?o1-26&?73X9Y*1{Sx9s z@z}%*EXpe2VsRgTrHF{05mKoh*Yd5VD+cM}l{%m;a|f3_Ah})uOI-W@u^}d3BzX4DH(@t^$dVGDcL7%s%y3tMpd%{MFq1#ezqFQh}O`#o{*`g|Fm-j%kB1}2s;(_cN~ju?5?dUY_Y&PH5R7IA=}MNYv}u!@R=F0HyNmKT9) z3|r^7pBhBQrK?#yHtcTb^6|YbAw%>OEosEE!$*mDUYa>4V363@pk|CKBfQI6s5dMM zv|*ts@62P{abkJ|N`T+@Zwf{{2$&rieHC2rkZecR6n>fVqPS38<#5to+&KgM#xFo7t6bR?5r57hm zw!0;j_*@Hepq`w~RgFjrKOp%PrKc`=?*xS_vH_P0J;W7BW_!z?;cx|6s6PqZ$to0A zH!qU(2e{?-&O}HLm}lkQeG-Y;rn;~}Uekr~7Em>6Zb{Sd0v8T2CLE_Ct%TS0#2UZ( zjW$G*QY|^(b(KPdH%L$8pU)&8(e!i$8BoN~Jsa=e!bJLAk)H(2UtZq5OE|DfXKZz#-%7Cv;c_U zz<8m>i;2owb*h3TA9KRwV7BG-^>~@SuduuQ4QT> zKA5YNL;XUP2wx&IRXtk2y~b`1>xxV(gv0?(60oR$!kO7{g!lReYSP$|Y1C0HlPV9w zP=p6lC3mJNGi5QboJlpa{oQa`F~uT32S-6#)--8l)+nOn*pGp{xM|olhUWQaYU-Jr zg@^{SgIC()96yw|ff0Y~2=}dUxhYAHnrM5LnG&;Xk0`lrzzUK`Yt?zLQ?hJ7P|q8Li{+Za#{7j%-Bt;R5!EhOC4ENdx$rISiqAy3wwxCRB7Xa}a}+Ua1^j^_54DRe>)X^-3XO z8m{YNG}Hq!l^AV|E3Wt(yxXt-o>&7tM|8Rd3X0HT^{6rVaQlvu*GCIR0JM$*OHepl zO4sTGg>f$qOM=rvZ^NFIw+WJEgl2Gnk(m^ij3t_WT%==XGOod*bMp+2>=v6;5K`jZ zga+G9*6b@+;}YN8&w<-g^iQBSOo|Yux$fFUd^?HT$PHe$aeDo>br>De8m$t9ocfyl z?(7~byd_Q{4z61<#w4WzKsf-*ry`{+Df<+D+b8sj$Rbd~1jrWW{>>);^H8ai5->Um zhkL>r_XQeKS^e@1FHpL~izqw_dx#ZOqY5!@Fg@r~PDW*8$*KX$I3<4Wx)TK^L~V(3 zgLXL20C4I-Xf1-#>M#}2dXtwv@~8LivdRNvhCjH)l{U6H$+D9Bh$Z4<4za_A21_cY ziObJ-EnmRH=mXp_jehwZ#%7^^2PIMpr#21m7B(c%l!Kp9qi-iMvkbymD`?5<7(EN1 z&kKiG{*0RVgXi^UPgDhGbS26bP7D>>QsW@m6?2pnMb{<&w=@caD9PgRoeO zu(`NA=Z>Y#Wq&gWYh^av9#ykOI4L7lT@sB}T}po*8hQ*FaCd7V0X;hWl!KS<(#2dM zdY)?o_i>-dQ^+cbW*<{^Q1plyB@oV0W=(mvoWT}0XeiGXg zT%_=tSp&EnJZOa5zUU9m#o_8vpvFX&Eo8M&nVRLjtq`U10f=>mEYq(TNMnvL8MZ3><&!fo2|p;|gS{IR4m`VJy^xSVe6 zZIIfd-dhp>0Zg6JB`Qe~8@|zPamIyzEU29wz;19+R5oz^@<5w1KBx2O( zDlqhyCYj36y{nr6ZJC)StYUVd3Q@xbhQ&s)TogsWDP>qnbynuKY=-uCTmn-lv7MrQ z7=5K@ZKK854iV7~}@qwpBe`Aa>F91p=C{(T|{a3^niYr@eP*dV zL4dRNbX16Z^t%Fk3lRSkZ->ZV~O&vhi*V!R*wDR}JEeZ8g(r zY=#9%*Fj$=Ji(UJv`5}QP&b^@v$dYk-+9MbwyU1sy#*}axrC`(lg2xdLu{rN zc!ENFjP-&iyB9F)XhxhdK4)N{P3`XTqr=z}I1nL$wC!Mp6afcMIlj1I8(jYx}-XJ2k*3R3!Z4N?kl7*bkzVHr~To;IL_4SVVZ zHOR(9x;?D5@T>Z<^xwI zkQ2l9sxvRx_TzVO28puA5v+?x=1w0xF~BrIlUd1_F%{2}GOcfLauLc(-3WCRmi^QR z1vkP-X!PV?%M(rbX4YfWnBR&3JL^n9n~hZcTTuReVzetDIcqK4gHw%%Oj zRi|=EhSmraq!Jaa^eXu-2gLYhk|%6a;7t3VNQ8~e+OX1Dtwot3wk(9SD!#=pj2s^W z)Ezs&(a-2F_ZTO;)v@QDSM%qMVtSZ?KZ9edlL-$A^;`W9My42gP7ng4hf>-S{aR1c zC$_S%tlI>m2g-t@1|~=8REAI|;4rr(a=9t}>xGGavPk_GqkKdh=7sB2-9HCfA9FnA zAZLby-bhY=4X81+XL?fxc@Px@>ZK^ea2KV+vY@8+QTVdpIg}HU;*; zjR&gsJN&z&?P--jxyqwT@l7v><4RWxF=W%eFw2}_ZaBa~cR|j5AZ_=UX06k6 zD4rhUx$|?cNo@B%8Q8hOPY=FazD$kuqYHE?E&~%37+W(apuCr9#Dx0QN)t3NEg!9z z&8myF#AOlfvh_J{Oe*xg8o9pCxV>z#W}YnToPqqc3oIn2tE$NrneGH!;OCpOgkT5c zLrYt4`IbLbyVmrtiqdykNNA>`tg`H_hwSh(bEiyz)%A02U^GY6I%N=!jaCF@0^WY9 zReTCa6|jv{8~frGowYxEia!|_p94Z z%ZKxV$VP-2&Z!_12Mm@rZvt|?F91cod1bDQ;Zj#}E)M)I^TxvwDrfy_NBO#h$L7jH z6nfSl$;(Lb}tAUFPLaF%VCM$l6g zGa0t7;O;X@oRJB*Lq|fnn9ZUZJl6nayor4rnGKsQdWNxr*rB*@LPRf-23M)$2K<~#%T@1Ar(|sRu;YuNH6V7Yj@b%Ub8zLi`2o|k^MXgR zkoS!Y2a2@-{XPVk5{u`E=Ry&eVel|QeLz&-917Le0iO9uzdFzX-rN>_?Q26@6zeh$ zDLgg7`!;MTcj{P9C4?`vr$iJ#8hl`fjV6X>@`xfP+2F0Z?ic;8lQ|I{H$cc*k!K_e z5B)Lnz4rJy{dNaR(q^8rLo>n1#qmHHUXCn4v5rqW92yl;W<1iM_AkdKo{!Z{vC1jj zIW%j@D_??#3+qkHhe!XH(Enko^|lpa52^J$#d@bZ{xz| zjoobX7`;wV^Y;b@*GV;Zt3Y(MGMBE~_=~rOBcXbnI1&P4i8og8_3r)*@Ja z-^6(9gZ1J!#gFU=Z1+ab$iy_ep|;A>Ue-gC^B>(_^cH&b_BHzt+7H@j7p4!W$hFPXiynY_E7Kej5qj?Rst?l4}yCCl4hw5wh&nd~9Ssx;h|JG0C`v@E0TsH;{w$E{iO53Q{d4&WNB z=a!HdZ@3ZTg|}#JMGNzo`h# z6t`*fM6|kel+~${=CI0tb;bYgk^g6--Jp#n_>uYn)7bOC>#f(CZ;RdX)7$z(Pr6LYtQbB_m1xE`rG3R zaaQDUiX-Ay*+Bz?;j`yxt7FzT-Dq_MB~_zTLsdI{^5IQy%wgO2p@hU_!A|rB^uJ7e zo?fqaW_HJFR&gA3lb~1YWapNUvtIJSJTJU3US;c6Ws>fH8Q6QQ`_r{xLQc13zJOkE zJN?VfH^Qv&u`|K%JbP_JfzB<$u<>t-_!KacqRX2XSDsU}+d%)05w;ihtemK7?b+pc zS4@uv@L^6P4xUNydYU`$z|pboap<;06XCP_Z{Y)5WfW-0+YwiT@B+$k?;F4;gg?RG z+3^c77iNXD1eu*Z2p`yOpn|o8UaRUHvtlA)dTG5#hivtB>onW4=)~Y-qa$EO$r6^a z-AHB9v}UwNF}?K>Ybl7@;0bjOdsgm)E(cx&f1+_xB2G+zp9TpH*<@J3HHq1xF5&6H zLLKcpiYamW)tz3-?TAzJKSY9^wFHuMf(zUB<;Up&uul)R<{}a^q4P5JL+Mrm<6?D z#qEwU^Mx_9@Jpvz>Xh=RBxohWd+)XeAl0v;`U?BjDiR|4L5mhCRfflGR$?cms; zd>#WO9mgI6GsSB-3FxfE#AAaSW(-E3G63p``F3r5@X_RKR0P(>QxR`qa5it0E#xEW zpg$se_AjbcEa$Kw6>rP6Mmty#&52BltnnRh3%Rl|{y5rN$CXS&rs%Ur$JI%M{bx0eXen)W^}~87HMQ(_laF?5n7;@&x^fj zB`r#41$o=t&6Y-=z522$6dCuYDyRF+GTH{w{D6XPx~ z?2(pp-vo8$G|#g6*)nhBqBct@b*7&@1gt1-!@gHRDs5bEemhqcwm#TeeHD#5#t>aq z_|ck+$u`pgBA8KwO*X%3+PNq$*3S<~9zYBMXbBmU8> zfwEiV!b;8bD6by$&NS#=MH+_AH(dyj2>)s|kf${nLKkVIjmBsAjqVJ#>T1kht)wDH5#urm;pVwtBd{xG z^XIzLYL~%5FrO^A&SFw8S~}!wL_jGJ^$J=Mg;|v4m^|3U)(>Yf4BUbZ{Y0*(^Ido9 zY>vA3svP!eqU1i9!(h8* z8&_H;A%;7<>9I{9J+u8}TPHhwYtH5r_XPL&_*Udr<<@T7OS^1);8yEa?3VZqR}*I@SnT)0h~7Urw+I`n}Z2v6QC zxiwdP-r#IlMLydw*jwBk#x7eojn}a|`h5d8`(FiDeP4N>dB5gPcwcgmdtY=savP{(Y=ytZ8gYO!4R`enm{Ph|z)n zC}FIVP*wET8q_`dink_~O?bgn(uS-oEDF8z>uNx!MPNxM!rQ zf_z8$EpukaU5i(p`gr)H_{jM5@Cf}#^^`6zp)yU<(xllqPPh4@wLp(|x1WhJE` zB{7wkW@#`oq=nANGlJx38O3=LllEc2GQ@@b;4)=OKiXlx4YMucGI>eroVh%`etl!J z*~z_neQg8YksT8qbB&sbNHruA(LB6^+sWXfScUUpa)thj*ycoBhed`-Mo30uBc|f1 zqNw7!I&O8fZ64l)b<)}xZW-EF+t-eCqyIX!`)#?uPHk-1EqX4hE(R~NkIZMo>&Id1 z5bvKKg-_lC-UwcdAH&b{*U4XTmKzz27)EkTmRyWnCN0BGGJr^5exM?#8B!D3g`_L~ zZZ{|w$!q(tv($C=lBee*dfT#-_Ohqu19@Bb=G*6`w&N!SBV~<~o~oYCUc*57AYjN7 zxs{}a^qc!vnZ-k*eGa>f-!N2)Ex=v$T=V>URbMhn#I51kwS2gUE9&W%*g;ZE5-r|A zP|h%(-Na!oxzH$XOYEKWXuWWrgUFre33uymJzLIA<;n8YReHLT<1owW>9h49Hp|#e z=z-}0dux2b+@4y!8lYq8$vuDhr1Nw0)o9d<5!02v#Ut+}mhBpqLC`+n)v)%bpwu9_3d*0pj(y~+g_+9ie&Li)w z@Nx7)ajUp(s+{}Bd*|vwIhXgH^!d+xYuRm853D!0x9gL4=Pj#uH$^x_pdysQNa+jR%Qm*1`iH;PsWTZ4>QE>^Z zBz7_jOEsOfKnBV6O4FOUw0Y&iN~yJsC)2N23vKTEpN8#VQ%h5!x#=PeW?^)9^k9_w zZ;>i99Mdd=EaNJs6-;!DRLoTjJO&=~&&Yq&hrLnYQM_2P*tD=wm}VGjwtId4MBpc| zOz>HBb{l=z;4^q@Tz1QS;9xemZH{?dceKE}xUc7ZM8LAJP}ob3bAJ4TV9{7iE^`0! z!~=h)N~~uw{FY$WIFD<2=wM&obKm^dfiZ9#oM!|0B-*(I z@BNM7vv^$|cgTUdSf9^(E&Ze5xqrOe#irtP1V!K|uo%q8rU)EE+d}_hHh7Gs5ZuRc z3qHfS+m4|N#=x@kGMSU}Dq(TBmsyv2o?YZ?hTPy#!hcQ9v4;9^J&w)ohT0Dzg<^(6 z4w}GK;V)3}Secxe+?6PdpZ!v$?>}Q7D;(ov1!H4=X zer|=?5WUCliK4`&u^*%$Did9Z{Kc@p4YMY~#jCd*+Clsf^L=P@FRxohesIudim-LN|r6+cWw7M(?Z-xB{sq#C8geg7T5DaylrpB#T1 z%|&;AG&Y$op8gw2XJBM}U?`qyn4(B;WTaxWWI#8n8S7$w=%14Q#eI3Q-?UCD(?1w~ zlHbfv@~7|^eJ8ncQBzP;ml>%@EcMWGnOP(!D5o>BjHf#&Dj6zCE9ouGS1zi+8*#@I z8g-^Ts4oYrh}28p?0U#-j~LwWJ&x7Y{10E3vk&^pr6G zpsBKNw@gTQc6TnoqpEJ?tw=ygl2<9{r!Jn<6x1a#Tbt%8SZvDZm-pg!;!zrJ*Eh;i zESr?tY2{Hp0PV3=nBxW?el|}%2p&>qJ3F(WZbIVXl@)LR#N-+}Di3n@Uzb%kaFS>B zaop(TLM|l)4nS5V!ab+&**Xq9#k{S8Q)c4<*q$u~X2{UVsJPew>N3t4x2~>kTvXyD zBb(D$P-Lx9*jkgh5z3dO41~}5P6+yo* zetN)A8```)O>T_mTrbTExhbma>L~ZgnO;^>wX}5LYRLt-kEOV(pi4vH>hIgxh*4raEBilWH6DjZpAUSwBb=rpolKCowsaJNcVn7vT{ zi35bRd=LyShS4vTbtkF{umH*iL?~r6T_W$P8(L>7UEzrlk!iEqaRH?XrFB}Ot?T5w zN1KPjx1zDV1met~?Jn@bwe1B;olSL-_kgjz1=8%3Z7cXx+p!0&iOZc6J}2Cn2W?5C z?Je+vyzK%~ok(?+?|@mEL0!9&|7@DOUtjOKgwlfJt&Uu1@2Z5pGTZsh)p7NlLuv8Y zUKc!K^iV)>b|+q0!mKMeG(fMHnsM2iF$V|xq#Ht8byg@fSMJ{L8Slfyq$Cn6Cvx;3 zf4^t>kozrGK*h`&2kxE>g=w-L}r7g>tO7G*G z0(sUq+Q89Bid}Y5>v6KE5K$?B5#7m>p%F6G!71V_LBbHD@FUlF_;QhqxZk`0B@gVRp-?%kOQILuzs9SQU5jjpHotrd_4Q6AF7suZ*v z{)Ek6mw1KLj=}_yBeJ5p(X+yY!7q}KbA$>+T*tQwplA0Ih5#!=4ZxgE4br=Rg(Sd; zlgh7-1=L540(_!N^IONS!da}vkYbsr){{ci)xeITIFY;zpeNDW2Q%a&gAqykrx@^& z1nhMTHDdHo9NnLWvxB5Snc50QBq5<`n5-N(em_v_$n z+2O`rNtmam!fr~^k%rbnAtt%^Y2}yA=XWK6R~SN^9vwgQ15aQNvWDtGz0PHf-Z$r$ z3H0!wD1#&-F@U-cdr6zMAnnW#=SjYmP_47VSH;dP$K?*nf+x36s*lZ*ICoq&3HYce z(s7;)f`sOc9Nihix!y5Fj~}1v_+wAYs%c%@v4-U7bG!P3RePjRyP1KKcsW8qF`d~j zyI2Bah4e$Ne|FU%JJf+VdV-Q28HCp$nXOXRvj&8#p*HC7_v&~pHvz3})Fd#W4J|4s z*r|3(C?JZJR1*8^=v`LalE@W7bDdqVu0`}QRoa87f>d})uR^ld*}81AoxMI+l0@G$ zZSHW;1kv_GKtN1uo(7ko4WM}@TksR=QbVHPVbo32*K(vO^VQm3FaSP!TA~vJP4|>j zIl)<#X|uvo`aUfu+}nfqN??WWR~Dh_wbyjeqd_|-%Ry-Hg*Aau<<}$E^s8J}Rn^oh zyUYMT1K6>pMxCl$+57szOqXDS<$hCG} zOkuyf;-WWpidj3;Jmb8y|%~lGD?3yfE!0{O%c1Qy$baWS0 zHABc!EeAI`RaUrvyRR&Y+Rs)Mr`Vf}w4zDvro?~^(NZ;2Gt+iPOHoHZV^A~FZq)$0 zyrQIF&uDCllOYXUa9}PqM>j7tuXk>t5KTRvC%o*)23iWPT?wx*o7YMgt9fTXyJ>jK zC+sDo-%}LAA(@%?E6@<_&d&dotG~!IjG3H?NAi_c_9+vptdIbf_+(aoBRKPMp-^)r z8Lr|$n?0#qaue@+7?lVaT26#_SX<}p-w20zp4ow2!U=?hP%wuX5>=L5D=|h@FyQL2eHj30(%#?zL;7d{6UK%C$QlOPWzljWUXItBrAK5 z0)>TcZTAC=muVWlJ-}{>I~7a{nzWOOjboZuClxQ_rnY(>UV1fx*#dy`0?z37;ihDj z-%ERCy)Ahu0rY6RM{oo3G_7E2pWQAW1d?M??Scp2hRURx0R}65_hwLjD{)vE0+ujz ze2M@{v1iTinE{2UN%nxjs2g@@W_>URQNbuPc^ha7Y=Trha=<39<;Z#4Fx45sSagfI zEo1Q0OBHHGfQ_H#(>`V{dR38}+Q4&^I#qy;Uyr(1p%uW5hqtUY^wSt979`eKdP#Zh-6r%0-VbK70~?^6HLs9yo2e}^FLyIwz6x%o8DOmL8xC|>g1HY@$ak(oQV^Nf_l}T!u^*~s@ zr>A^orebOacd+v+Sw=M(+EZxsjrn;4G{!?>n&QCv>EVvYIJg39%W4zE{BF`W$Rz323i$B*w+7z-dN&>S6!Add7il@_tzeB%_w3zl7?eEvlGlg+3L#alf=5@s8ZgRVZ z>WPK6xl-rCneEWctwi?h|4fDzOUyE{ge z!;wU3-leDWY@Ar)_4TV7+O0**X2!x`->H`M%x(OhRLpIKK7qhh_RW}7IIuI! z)%l&=jv)+Z%H-Qe{(c@|lX3WM$xX1;*v!029S#ITziFTbvu3*)>RVUGL$M0bVqMeB zt+^5XtrH>2vFe+SKcos=$d( zZk`c2BaWK92C4x?JPJ4y0Kd=7#Hy#Y@UvmU;NPy3}b!NVqSZ6 z<3#9miP>hJyKpu!?4KeHt0CPoKM<*eDTEOQCCThC_&9lTvG+DREy`B|<~A z$SFir$2+OtQG;A+?sxS`oScWKgg{p<2r!DueTH3RAb6(}GLV0#d=Ylj^?xBbTh!g}MDN+$ z`{3xZ?f(EvdgJu^0lT$v!}EL5cKZQ*`-a>V-S@`W@g4jCJom3!F6UD?bmji{(Z| z@MBOz+ltzQ-Tv1G-D~$G;U8Ij{M#5DVjsf1oHER6bma2z#evhm4iIfu?Sh(68u3-d zRY}u1>N(Ym%14(ktuHR`=AHc9+S>Wmi_6C+kJ27~-_CFPufuw91Nf1g$lon?GK?zh zH%3KPLw|GL_GSMgla@fo{7+f1T0dC7SU-86W^g-XIm8p@1#1Q4hS3e?1shjvJ??(a zuixAk@6qIaa!>_cmsJaQhoQ^*6`m-O;jot8v^Vj~#^ydP6*?w{+ts?(a#Xu>tZDVJ z;+%O=SGqQHhpyZFJ@cG;aaa07>W8k|+GF&w<~+CLMd~(_hwf{#Y4_1~F|;IB3NQ29 z@O|j9{G4&oS2{OyLrOOdE)6cDhsoQR!-&J`ed96uTyqg|(Y54Onl~*b!-we)&$i1oA*I z1cAaFq`W{=9=bu0jX)Iw*$_0OfGdIg838dQcd$+XzZjYaw0BUCK*XHv87&NgLNHT5 zISdLI2n6PU7=hv#lG{F(eo`Fu5k&id8G-B=`UnK*fZD!BKQTlA zxmbXDn+!QhM36zg5IO2Zko|z0et=!KT_Bx&8nG1GL@>pGl70rAb{&LDzCaqm6p9IC zV=#7bW>99ZW{_s^CP*foXcKu7nFsZ(O zotzpm6Le%SXAoyF$$)}>dYzUUK^2M$q${K=bY-w-kY_OSfOmjso30z58Wk6L74ix+ zc2HafW1%>MMuSF!vj>m|5(g9q76*Rg%=)c&Rrt`<0+n}J zcU^Y@b)s(aZsMjcO`eM`_%il13de91DgY!1DylB!7o8C z!SF%8kRQRm(0EXIka*BPh&+fqs65EuR9-Yqkh&1yL4gCQ1F8cA`*nBCchPs%c0#W* zuR5>%Y?PnmILJOoG0?owWI>4ov;(yRwgVLVuKP`PB5YJy$lnMv&^r0rZTMO!UIL+Z z;%$G)3CTjj_YHzDC@zM`={1%j=?z%vRicm&he7H))5xC@5{GgOXy@@sBfE!r40P#* zugIR!!NbZ8GR#oGBLat+^WrxK;=M2i*>|z=cjP%pjOXG!CsFL?1{WOdn7mR3BIuNM{kLL?{<%XHm@{R|-igs>`-*8`EF@N&cD4WHMR%U>&Tpo$Q@; z=YFp1sz}xrZOb^hT}YRT0VR8Qcpra1cOS*6qFqjxp%zIas%offsB3s>pMSq+ALmYB zj&z$*fT$Lo5N##$Viwty)806Vq;ix7=Pbn|6pI?4*E{|j{45Z zDg8C3UB^c~Wf-GD%}2pU-$!_pw3n)v>|5MN%SU{ZwwI=t;#{P0gienfs$e&i3jAm+vp;}Gb+@SW@( z?OlSW=BM_j#HTtRi65mO<2Mljx<6Wcq+sOeaQ-mrzVsdDoyHyKUCL{hr#2sH9{NnA zf5eG+-714V5@VD=gJ?IIJ_dSZ@<53rwMbNot4azQ{aGniMfU$x87}I{qyVI(RTL}I zO%swzw^ax$>QYFalHX-POH`@lS!6&6%)mfy2$YhdC(`u9dTZ3qo!=|4_q{p~LB%eYdom}c{6~(5O zMIoO~m!wviU@pa4sx@zWCUHh_W`2h89|hq!#Z=-yZ{j$^RO+$xQj%yEmU*sx}3Ey1Eo?soK2W{MNkn8Nr#pRjh0N zVd8(xh08LCG8ZLJ^AKmmR>hC?&C=_1c8RSr8zqwe6obY8^n%Q1PXDBWA&(`GDUZ#~ zvH*#7dVbn*mFPv9|I!#Cw-IrM`d`Gs-LQ#{7v&;I>Rq{fb4r$v*LUBW9wtw zW9_5j8ULB@8R(h8yx-Z*eE7WN{OG)YiDHRj=}gIwOunq$Jmx=HVZmeRW9ol&g}(H@ zq`tKHiM`3a>3#`uGJ^lqh57&Fg){TeXC;r(|J+2`t}^d>JoLGV(=sO|f+Y`08rJCy zQkdlVi^O_K46;#~tl^o1Q^?0sP1AU0aU2&@%*Xb}nl3rq+c}2U_6lq-Oq7}4S>BoO z$EGe0E`%-yE>Ru%9erK_+)Fv;99fWAkeLfp!Be7>|AYaPgHxoF*vE;-axQ6^oCYQY zX44!e87@=BQ(nhD4_GdNUe#~fP2+SK;!UO;*>$X#S<#cj$0#lp9g}?1eA|3~e0ts> z9>gBV9!y<2yrR4+y|TPI*Nj`UXeU`GS*MyNo2EFk99VX2dxw3h-Z+}3_(pZDYFRd> zT&5l-m(0VxdOJpRZE9IpvU86i9zL};>R5KndN;b)ed6ErA8;Og&Ewzv9zc&ykZG< zfNokkCDCf@+_DCS=}*vV^EVrn_f$HdLKkS^XJ~X)C1{7twDw4tHo#YcF` zMWya+gc%LCPJw*km91x3A%%h`*W|%4qSQt|0^>lQnj*6dF8YLfs{lW;un8@j3pSvQISScq zl!5hc(Kd4Y+rfL?4GQrOHkQ()x!V@AoEqgkk0ZU88?`Tb>Ey+!dSC3UiI~50v<#Ub zkOrB~*Dki_syT;!shqM2(qd=;qQr*sO!q7q-|iBzP!Nk`J|;zc!D0*l{sXBnGTsUk z@dLst{RE;KI2%68dpUU{gmMZ-_W9eVShC)Xxj8WP8)k|6g!zmtjXS@S;Ub=!pj;E4Xtb~G~7!{rbA=UR5p*j|#{BHffel@#~tXOOtb=q(2g z0p%|LKw8mLFR6ZF<=b#w@Pzrmgl?JK4%pS)g$y6$V4z1_=fzSu2{gCYOKut4m`CnF z(DPwWD-Mx_4@A6mptQ9zN72HKp&Cvghdo2Uu3=H4HMB0Bu#Oc2rvtzf6==dp6F5UL z-i09it>`fRYt~kmaEOh?nR$=r(f`TMcs25e?8HO8Z$2)XgCP8_t5hb~P)Jqb&5Zj* z_}cb~E|3_dYR}L&;{q>^0!XibxJ667Mz_V8T-ghJ(Z0rzSQ&+gjO@;km(H7NXchg} zIvuW-g7$1KF%crBC~H7*#Gg%?#i(RjLAb4cPtJ3kV3fixsq9s_6d)&E=#ZY&m4GNN zWPYbvXfdq|_Ylht4StyM4CDURK(Y6b{-(OGdg2Xly-^v5c-UXBmfDxI-5%U4-6GFUW8nLKzYCPyI#E_cpwb zuN{Q*DJElLLW%lzi@Ats3|fAeh3X*%Q&Og>y<^Y1{tSPDHL}3?H_5#@muBAQVCLf%%RwYNDnv*BC$@~`@rcCbw|7* zT8#DI8w?yD#YP30*VhQF{@fmIqH-BlO%Nq6L)&r^TTMa{4i4{`FLfr0tTygfr`aJ% zD=`RXgs@^nz9k)<@T=CGFdJ&&xp|-nNNK>Y$84?FFvK@GR}l!K3?)d15Ag?&BkLkK z=52PSiGcrDk(bbQT^iwr6d5|I`oWKU|I!Jixl^RFfYPqdSmsJB)q?exz~B$Pzoevq zr$OqJ4vjN$tc-24ni0Ze~vuaJ{CH!b+IPWGT5sy8EMmH>53-`t-)kaPzEuU`~yJvXw=HGy}F|wA2Jzf`wK@jN|9;p0;r$nLdz6lC%lNm9}xppmXpvx`5SDh7F#3GW8~%2f9RRK8shdoJ5^ToTz*l8WC})RSrsSg}Mz{!y7IQ`*1GdeL?=TRxgb`6P z!b=;}7S`)Q*0B~Js}vbuBv<1fvDZcd5nkI;fkkgl9D#TX6Gd1ReQ6Tvu1ni!7r72X=lm-!^ZAH87LusFcfyZ7ly32=zCSP5oD{*zlRAZy* z;)2jm)8#7rQLJA(c-rF4tMKpMe{|*Bqw5+pHs8(Poex0FdBz9wA=^jYe%FApKh7kX5@7Td?+!DZQOyGzE^%hBcZ>%dN=>&g@0bg^TM44fKg9# zg-S3Wy8%V~F60qcA|x|J$=(&`We$;Mp*m68D6)fpa=7@#wdW1o_$Ulz&IM8yq0+$w zOo7%qn9EdZmqw{l}fi+z7Acox;Fqu^(&O5KGndH zcdz|OZjbZ}ta9K<)LPKe)T7aHm}N8cFct6|h%Udu1OdE(&_?Ptg)(c4qW#r$+v#D< zmSxPs!A39^YQ~drE-go$a}cid^k>l}i&`oPz2E}5Qe?tXgej>D{-x9j(U=R>wv2GS znu@P_fnUnVwt5QWSOV+JL^B#jOZt{keFbTi8{i;=21BLBA7Tbcb~fm$xNcDGR~~eX zq0Vv&Tj!aljOpM|^~9iBIaa?KG>b~)n!wt5!16tKPhqXfScqb41jB<{K(JxR%e#yh zK$ZGCTqfj?j2{u;3$z4Tu-%M*jC9f3ZNj1)wg^rLB1W=?Lth{wLBA#2xwM>zUV@XXk+8F4|f~cPMZuXdmiWm0jdPW3Ly)F4v^nQ$$UtsQzogPyaI& z4slT|f%MAO-vVbt`tM1!REh8_KFaNIf z5UGuC=sQAH-e}n-l(2E%-HKeo(+K=BP=+@|v9NcfKlaQU1N8gNVz2N)EA;0wB*+V3bqEl z@0eCWtuf6?O{|up92_W{V(5Az2R}2q8+0?rBNTyuin$T)e6Kz06i~>P6ugCm(q9M+ zd%bYp1~>W&c$+q6d~EVk$%XWJH35M69BZ^{>ZMNWEsg!McVsEoDq)zWWTJF1FSqMS zvl;UAUM|(`wip%4Qr=eOnTy<7-0x+3%&;^-R^(gd(mVaCIvh{T1v|=vKe+>Nn&3bo z>?u?n?iI)p6g1MLSI8M-jl!a>`@wIc(zC@Xz`H|pkg`ht*s3q(yNobOlH+?1N z9o+d4=8>{2GheIf67NhRoZ7YyFdFyhlO&@7nTt78WmU=EBgn5PlcY!Q zN#Yz^9_SM&H;bj?Pyg757B|cuG=mYk^?4fmDZ<7dxiiO(BkU#WrD$+^+w@bgQX*d= zQLDRz`TamAYA%H&&GgJ-*TkMKXl;xBUBr2s;{-j3$ zL>)D}QLf$gb3kO0&6m=}m$gg=`cf8^vxkzqZW-BPv6OVtW!fE0$zP={5?xbGJlan+ zHl(;NO!o9PRSb=>*jNm-S=~OmsLvDvXSrH(e77D9{pke4XWONZqV8b$x4ArXSDqq3 zRLP6A>QVte75+G@wK#X_E0L|FK3DK+^eUBC60gwPazXl>znL<2B)3z_11(=UtJY1~ zIpOSp3yn!KtW9p7K>Lxt%CJ3}JB;s6Jw@J&DAhxP{gx%o0Tr2tC6`^!Un9GMjjRD@ zb?9G?k78OW0W_w*p7;!^Qc*%;)ozUt%5IuWanXK4GaNw28<#d}ii%haIWZOxsa6E+ z_x=kcZR>x6ENAY&jfIGVNGi?H*B2Wr%U;Y?P77eZ!nzeN-9+AUH0^;yW62nC#77uWG)t(Z;I%Bad3!g5?xI-E4rM5e{>b?4Oj4^?)e{V zb?}Bb5lm?egG6mN0Qry?L!GxlpPk(?uZx?i|@d#7PO=~(WYL~x< zF%$sU@D!!cY9m~;`0z9k$@U!Svg5=wh&LnC_0-#S&DDUvqJ;6@6B;oa>JA4;U9P<^ z*&{u9&Y^`Q)!L|K++9<@#YlMr^#S--pp&2mbJ#rcz2`@UIz?5P18Hi!>d71md!`v$ zmq~#`fqwv~6`lnMh$gnA#Y4gs=ArP_h`hK|8pMVz_@{JnB(fH?VfHGo%}v56Ai4q~ z8_Ud3aZZ4s&)?zA(I5p(S3q{B!UCa3-qfSW>(K;+un)N+g4>~MD${4E6B`Edy^hh0 z6K%foY&B0cwz)1&*!L=Kvlbl!-+LP66x%oV5PjeZ;jA?t<>!Q#;x=EzzXOLl2M-?0 z!TMGqAV9T+(XJiH2rof49r{)nElK=ql0!m8Vb6q)f#m1P*u)LAE&B63%Hd-Kk8m$s zVQBm9KYFL`Gb)K5hkU~edfR2cL}&=F70VhVE68dRvd&gZ-S?SBJS8k8)xqz8Y=$xD z5t14YVod1l7A21Ur zeY%EK{s_e;C(0{V2wL>S-Z8M*Z3XIe2)F1nYF8m=h3Sp-WI<;d%$=ZBC|b@u_1fHC z%}z4_NW@9kt36o|Y#f3CtEi%37`(C$MljWc>k|rVLgMwt?iV5Or${!+q3(1JUkBAgBnV(|s@{h_d`3tsfwT3d9lvG7iMLc!`)#y`zowy6Q zt4QVc=UKZbacLzu;#jm3CnhU&%@m?Wy;xI-@$!rO1Osmgw}Ry0FCDK@cV}=R&#@Z- zcR_7}));2NoS$8(FKWrBM>8@|I7kWp$F~#@PK!}Xdj`$rz;|$FxE!+qETeH=-I>@K zQnOM8%#W!01;tZFH}3QgCl`mxm|wvGxk`s}0nXu8Y^5bh6MgJ-s}v3kfAQ z>QAf1qkyJ>p-QPCrA1{Mm_mXm1m=oW$&z$)mBQZ{W4lx5=nI<7&h-=7SkA1Vf_(ASb{B!}su!L+n4$DJEZl|C`{H@~R_%t}eWz5;v^q%~qmHDAatl&oFe z0XlC!nbH#*C3-%&vVNBZ>nHPxwk(Qqw0uvzPekl{gcXF9WK*syb&t$%Ptf2iPAQ&s z2MI%s4z?q>5YTlR0ui;_7D=Wd7h6ykKNf5rPm~P<`#C|Pgck~MHNL9#Ifu251s#$asgoe5;L5-7*=R<0uR5_aEw?3(;^RYq58cO$yI<;9k|w0>2{l6 zU*IyWCP*}){^xBH{a?bsK)2CgKG)J&`+n_<&~JG3tjgAns#t2LCcblJ?uyqE@C0B( zqBf%7YqJw?+w3A68vb@+0J>bs1n-L*seNqSd=SB~=c-?nEAi=>_Vk-arpj z>s#7VJEUCc?TFf`A&7f`nIH1NFI}!=yMiW-`V9ov`Z{56Rb~J(w%h~%87j-RML31> zwto?nY!YV;xtk@w{qDzd)kgbvU#!Q2!vRjS_|{0*0>?JKLoM(f+XgJ>?MZ~CVj(KR zr3Y5~1+v@gP})wiR%{PnfW$!hEGFagk+vKd{*#&;IO6Ownoy7G6%DZDt3Y-dN_c0L zpJW#Z4`X~IuH*$$Rs}&;{-UbF5!-s*sIZylfiD|PVX>v4OOm)(how@71vXD>kn0Xk zZo;nYTAi|$%A>!){qZZo<8Ln?w{6kwwQVbb;!(~~i2gAZXVboI!G z9~(9gO__vg0X?!=jq(|8sCt;hSxr0PwwO0)*K+8r`BR=k_huu(pdAv0Y4g6G$lE`I zwWqF`JRh!@j3~pZPr|wm)8^kr^CL^#NaK12AISoP8!>PF(#m{N*;nbTF3S8hxfgss zUVdAK%pck~21DkX42Zediia%45G#FU3?8$0&80@E&QP`4FjuUC`{HP2D_b*2KfE#< zL&?$Jwn9Nn$7AxvEI&!%#K^T?(Y?f=vUwV?b`Dz>g6z-qC6Vw~Nv8R@Kg^v(e>UT5 z&hZEH&Afmau&~<{l6v#2@Sb3kqEa@BCol`3KgY=J#BIR00FsCFo~RLe?W9W|D%nQ} zk3-Fk32Qk80axl@%*{QO339b5skDn%yL>!j-?s*qe3BbAZXn~3Up+suK`rG@e}5pC zi^|e2bty4y+*nMKuYv`eii(Zk>?xS@H#JQQo@NDfYln_y`h@8+ zZ0-(w-Z%I&bLbUz(!1Uxmju$W8DY}PYubq}{8TjL|6%B3D1NGs!7ibyODh-n7W~}y z3&{GE=)IhyOu2zHavR8@a#=#Ye zFMr7w?%s^vJ9uGH9*l_>^Qt=$SESX8@H5|F-8i*9W$qNZOCb=9^gL5Bnw)tWm5$t# znbin>L5(d*HQ@)fdV>#I^KoK-gVLBKG1<#?U9;NzWeG$k5b8lQ*B9mcilBj2p}f=Z z(Nv#FNKu1nb`8vtNO+o=U9a9lcn?Z&QwZFFmaZIK`JSV~uynTKgLAZXv0-~VgVaT% zFZ|ENvvheFi$C*;;@C@sKT{~Cq;QY}EXuZ!!{(8dxp9WjbosB6cowkF`SZ2EGkZ!2 zC9mz?>$=SIgv@WXR{rE(j!RuRy542bER%RS9S{HQ}Xh~&{DS5S(pYKt+c zNxLd9>ytG~1n|=Yb*xwXeIUFqUw5f*fxEi}x1KO#TS@ubC3(@a=lMO4QelmYc{Yz` zuY#GXM>9PtO(TYbYEk-8+wu*|Z}dKVIfBYN)Mc0%F+0_6yIJWHgmH&`L1u`wi96j7 zM&<8VL35HrZU^@pq_QXfYagiKMOA=*(6tIzRU3jU1!};l2BIsENr_7)n&GCNjTNP4 zU8wIOsKAl|j^d~h&S_tF*n4GQw-=JV=enri^xf`ddXr7srB9%)g=e~QFhzsT_c}Wq zPe{iaqS~y}%}D6s$nRT*PT8PFbyh>tBgIqGLw>LaWGi7rp!3&uY%ynhG|Jf%C^N)| zXvf6IVpL1q%8rQHSg0D+6+^bKYcP6a{_=1jc9Ldg^!Nt#PTYHb0+7eM@^>$=u0Iog z98JPKCHSR2V;PB89AX1dZxP#w6P3n?B=Lt?wEA>ebay~s+@6O^OE;^{MTsJ*qOC$qHpT>kumCX2sbUqJ!EI!jA4v!%2c zMeD+tWVcR9(@wli=i^uKC~gwL|*I&X!F`q&ZoJjRm$z$yhbx_Z}I3KlJETMl2Z?AoH|e; ziNjER=pd4w8CKz)mwLINDNtg&IxOS-@sxdx_8Hr^RrlMeNuOMi!E?V{cSMrWjBjPQ zva)~R(OUJ(wSbFh{R@sk%1zd3NX)bR_>~UuHm1Dwga{iE?V`ceTi%y`ORx{u2UyDj zI#L$d=NT4O5-pRDAGQ2DzN@CtUS8;8pTu(&gS(Ba@IQp8`(;r_ck--9xk%Sp3}hW? zIIwb4HS6QHVxdELG5^8sJ{8-$GDL1)1%u2#mpl^M_=p#G=!1$#=tr?xa2wBB9lW8* z+Fw6Apjim?B7=v=0o8@tlOt=lsDuNh_YO&!z^=l%RhKuf{vU45D5nZhk@R!0r6n zMvHGN^XP1AbLjL480Flc^_K0f^6}*p$Yop^^>Xp8AfQUtFK;I>_QjN4v=FxtQyV5tZ5^8Yz90FX>~70YOWE4m=lMrqwzE`HqjHZBYXn) zd%4qg{Kz~$RcdgzGrb85h8W{7oiVv%SB7ql;A6*Xyrz1OVRwb)5S?bJGt43SXs@Q? zDJ7d%ddn&D?U7P3i*5(?67lCc*#2=mreHIm<(R+QmRO<`&(>`G;p*a~Bfputs(Upy zvQJp=^^>|5%>U2&VHCK627d7v71^o&`N4MpypY*2{5<>iFdX6tDTF({<`)6FXzo6a z(7wMXfNDkf|7K^MlyF^5z2t~ zH_~NDhcrU|4E?hV;15CVs6bLUDF_|w9^)&|hRq*83L$sV6WDth@cZ_E4Y6?cxb0JgKdx8`&Bp!#|ivCwtE7^=!8m|)o}qV7?-NH@GQLEvnfPX_G8eN zKRyDp^LQ)cxrb}MZ(LC8YN@!Z;9@Rw18WdKdh86cPwdNdSk_#>K}0ZxJxJjMkcmDv zCh2s#Q=&lGg}RPLN`W^kzkQU#fXF>;)+>9w+9R%QP zk0~yn)tju?*ow&P_PQDUxa)m+;$G}=bI5f$U#$35roavlMH&K8%r->qPneJ13%=5q z8#@c*;>mlS>*eslHNyS7|AEU>L9=nw?fy3=cs$a8@GTw^0cKWxtAHt!NSxKJZm$3v zGjJww(UG<%L9+k{Hw*7$E1`(~AX4K)yf!8dv3&d&ZSxn{Fh7hwTo%72^jK5iiIcKZ zb{oO{ZFTs`0n|9d=ty|@GGV2gkmaXXNHx#CNE?Nwt*HL20Q%__XV^#$?$gj0lE~kp z-L2!0*P*E%&N=JT{P8wPTU#>Cg%6*~ul1EOL2;AS`htg4hg!3jR;SS3o?Ns$k4dy! z%Q&cc(A&)6xjCpi`JUXK+)sRA6~X$|$?bkO929JBKhO+^0dLK!F?m&}c!$%QO?EWt zYN47yW7z2&%8(V8>=)*D$zJ@x?!+G3l-02HU6LlQ&F4nQUTAS_>O%YW1GBGfY5nB} zoCzFH4H)vVTLusrUOq{Y@ql^oBcK$zLEh9oUyc-4stSl+pWx4e^u}2!Dwg0++u#^& zDv7tt4g{H;LO*z!fIyCMp={IBzG~ zoOLrsX~7i>87T|yTKTqq2pro%BJ1p=(6%Kx05kNt?9A@Nw;R8Fbmt9XqYn}n*aUo1 zB)OfhS)Hcei&8u|HH17!MY8E6cH+E*Z5ON>zmzo-`Mjm78iaxf+5J zBEEXa{dHY|;KfL1aZlwB$G`L3$~R0s-q0Y=G#l2?Jle)Zufh;oT99Q&c?>0ODL?A6 zBh7>J9po;J``~4!6mG;Y)5p~&CQ)5`Q`)i$S~lv3I60PAI3+5S0hHXlik0gXi@REW zbIiTH4Rf+7sSX|O_IatwZYc3kf5Jtt>DGYYndkgwcQ13;ye�R0pl!Gh|V83jf>^ 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

Sve@6z;dti&qM@hjdwzFR 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 Date: Wed, 9 Oct 2024 00:41:30 +0530 Subject: [PATCH 63/81] Fix typo in CHANGELOG.md (#5240) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f327a1cb446..b64fabab220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Breaking changes - `ERC1967Utils`: Removed duplicate declaration of the `Upgraded`, `AdminChanged` and `BeaconUpgraded` events. These events are still available through the `IERC1967` interface located under the `contracts/interfaces/` directory. Minimum pragma version is now 0.8.21. -- `Governor`, `GovernorCountingSimple`: The `_countVotes` virtual function now returns an `uint256` with the total votes casted. This change allows for more flexibility for partial and fractional voting. Upgrading users may get a compilation error that can be fixed by adding a return statement to the `_countVotes` function. +- `Governor`, `GovernorCountingSimple`: The `_countVote` virtual function now returns an `uint256` with the total votes casted. This change allows for more flexibility for partial and fractional voting. Upgrading users may get a compilation error that can be fixed by adding a return statement to the `_countVote` function. ### Custom error changes From 632500967504310a07f9d2c70ad378cf53be0109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 8 Oct 2024 13:39:53 -0600 Subject: [PATCH 64/81] Split StorageSlot into TransientSlot (#5239) Co-authored-by: Hadrien Croubois --- contracts/mocks/StorageSlotMock.sol | 52 +---- contracts/mocks/TransientSlotMock.sol | 61 ++++++ .../draft-ERC20TemporaryApproval.sol | 11 +- contracts/utils/README.adoc | 5 +- contracts/utils/ReentrancyGuardTransient.sol | 4 +- contracts/utils/StorageSlot.sol | 170 +--------------- contracts/utils/TransientSlot.sol | 182 ++++++++++++++++++ docs/modules/ROOT/pages/utilities.adoc | 2 +- scripts/generate/run.js | 2 + scripts/generate/templates/StorageSlot.js | 56 +----- scripts/generate/templates/StorageSlotMock.js | 15 +- scripts/generate/templates/TransientSlot.js | 80 ++++++++ .../generate/templates/TransientSlotMock.js | 35 ++++ test/utils/StorageSlot.test.js | 33 ---- test/utils/TransientSlot.test.js | 59 ++++++ 15 files changed, 434 insertions(+), 333 deletions(-) create mode 100644 contracts/mocks/TransientSlotMock.sol create mode 100644 contracts/utils/TransientSlot.sol create mode 100644 scripts/generate/templates/TransientSlot.js create mode 100644 scripts/generate/templates/TransientSlotMock.js create mode 100644 test/utils/TransientSlot.test.js diff --git a/contracts/mocks/StorageSlotMock.sol b/contracts/mocks/StorageSlotMock.sol index e69bd36a249..ec176e21fb5 100644 --- a/contracts/mocks/StorageSlotMock.sol +++ b/contracts/mocks/StorageSlotMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // This file was procedurally generated from scripts/generate/templates/StorageSlotMock.js. -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; import {Multicall} from "../utils/Multicall.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; @@ -84,54 +84,4 @@ contract StorageSlotMock is Multicall { function getBytesStorage(uint256 key) public view returns (bytes memory) { return bytesMap[key].getBytesSlot().value; } - - event AddressValue(bytes32 slot, address value); - - function tloadAddress(bytes32 slot) public { - emit AddressValue(slot, slot.asAddress().tload()); - } - - function tstore(bytes32 slot, address value) public { - slot.asAddress().tstore(value); - } - - event BooleanValue(bytes32 slot, bool value); - - function tloadBoolean(bytes32 slot) public { - emit BooleanValue(slot, slot.asBoolean().tload()); - } - - function tstore(bytes32 slot, bool value) public { - slot.asBoolean().tstore(value); - } - - event Bytes32Value(bytes32 slot, bytes32 value); - - function tloadBytes32(bytes32 slot) public { - emit Bytes32Value(slot, slot.asBytes32().tload()); - } - - function tstore(bytes32 slot, bytes32 value) public { - slot.asBytes32().tstore(value); - } - - event Uint256Value(bytes32 slot, uint256 value); - - function tloadUint256(bytes32 slot) public { - emit Uint256Value(slot, slot.asUint256().tload()); - } - - function tstore(bytes32 slot, uint256 value) public { - slot.asUint256().tstore(value); - } - - event Int256Value(bytes32 slot, int256 value); - - function tloadInt256(bytes32 slot) public { - emit Int256Value(slot, slot.asInt256().tload()); - } - - function tstore(bytes32 slot, int256 value) public { - slot.asInt256().tstore(value); - } } diff --git a/contracts/mocks/TransientSlotMock.sol b/contracts/mocks/TransientSlotMock.sol new file mode 100644 index 00000000000..6b18fa52f29 --- /dev/null +++ b/contracts/mocks/TransientSlotMock.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/TransientSlotMock.js. + +pragma solidity ^0.8.24; + +import {Multicall} from "../utils/Multicall.sol"; +import {TransientSlot} from "../utils/TransientSlot.sol"; + +contract TransientSlotMock is Multicall { + using TransientSlot for *; + + event AddressValue(bytes32 slot, address value); + + function tloadAddress(bytes32 slot) public { + emit AddressValue(slot, slot.asAddress().tload()); + } + + function tstore(bytes32 slot, address value) public { + slot.asAddress().tstore(value); + } + + event BooleanValue(bytes32 slot, bool value); + + function tloadBoolean(bytes32 slot) public { + emit BooleanValue(slot, slot.asBoolean().tload()); + } + + function tstore(bytes32 slot, bool value) public { + slot.asBoolean().tstore(value); + } + + event Bytes32Value(bytes32 slot, bytes32 value); + + function tloadBytes32(bytes32 slot) public { + emit Bytes32Value(slot, slot.asBytes32().tload()); + } + + function tstore(bytes32 slot, bytes32 value) public { + slot.asBytes32().tstore(value); + } + + event Uint256Value(bytes32 slot, uint256 value); + + function tloadUint256(bytes32 slot) public { + emit Uint256Value(slot, slot.asUint256().tload()); + } + + function tstore(bytes32 slot, uint256 value) public { + slot.asUint256().tstore(value); + } + + event Int256Value(bytes32 slot, int256 value); + + function tloadInt256(bytes32 slot) public { + emit Int256Value(slot, slot.asInt256().tload()); + } + + function tstore(bytes32 slot, int256 value) public { + slot.asInt256().tstore(value); + } +} diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index 515b080ea16..c5a29685c7f 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -6,7 +6,7 @@ import {IERC20, ERC20} from "../ERC20.sol"; import {IERC7674} from "../../../interfaces/draft-IERC7674.sol"; import {Math} from "../../../utils/math/Math.sol"; import {SlotDerivation} from "../../../utils/SlotDerivation.sol"; -import {StorageSlot} from "../../../utils/StorageSlot.sol"; +import {TransientSlot} from "../../../utils/TransientSlot.sol"; /** * @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674. @@ -17,8 +17,8 @@ import {StorageSlot} from "../../../utils/StorageSlot.sol"; */ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 { using SlotDerivation for bytes32; - using StorageSlot for bytes32; - using StorageSlot for StorageSlot.Uint256SlotType; + using TransientSlot for bytes32; + using TransientSlot for TransientSlot.Uint256Slot; // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE = @@ -112,10 +112,7 @@ abstract contract ERC20TemporaryApproval is ERC20, IERC7674 { } } - function _temporaryAllowanceSlot( - address owner, - address spender - ) private pure returns (StorageSlot.Uint256SlotType) { + function _temporaryAllowanceSlot(address owner, address spender) private pure returns (TransientSlot.Uint256Slot) { return ERC20_TEMPORARY_APPROVAL_STORAGE.deriveMapping(owner).deriveMapping(spender).asUint256(); } } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 4b40a967efd..24b95b4e6f8 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -34,7 +34,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. - * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. Also include primitives for reading from and writing to transient storage (only value types are currently supported). + * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. + * {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported). * {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. * {Context}: A utility for abstracting the sender and calldata in the current execution context. * {Packing}: A library for packing and unpacking multiple values into bytes32 @@ -130,6 +131,8 @@ Ethereum contracts have no native concept of an interface, so applications must {{StorageSlot}} +{{TransientSlot}} + {{Multicall}} {{Context}} diff --git a/contracts/utils/ReentrancyGuardTransient.sol b/contracts/utils/ReentrancyGuardTransient.sol index 54df0a71ac8..ba8cbb65b1f 100644 --- a/contracts/utils/ReentrancyGuardTransient.sol +++ b/contracts/utils/ReentrancyGuardTransient.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.24; -import {StorageSlot} from "./StorageSlot.sol"; +import {TransientSlot} from "./TransientSlot.sol"; /** * @dev Variant of {ReentrancyGuard} that uses transient storage. @@ -12,7 +12,7 @@ import {StorageSlot} from "./StorageSlot.sol"; * _Available since v5.1._ */ abstract contract ReentrancyGuardTransient { - using StorageSlot for *; + using TransientSlot for *; // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant REENTRANCY_GUARD_STORAGE = diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 2e4f736d8d1..2df32cb9278 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -2,7 +2,7 @@ // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; /** * @dev Library for reading and writing primitive types to specific storage slots. @@ -29,24 +29,6 @@ pragma solidity ^0.8.24; * } * ``` * - * Since version 5.1, this library also support writing and reading value types to and from transient storage. - * - * * Example using transient storage: - * ```solidity - * contract Lock { - * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. - * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; - * - * modifier locked() { - * require(!_LOCK_SLOT.asBoolean().tload()); - * - * _LOCK_SLOT.asBoolean().tstore(true); - * _; - * _LOCK_SLOT.asBoolean().tstore(false); - * } - * } - * ``` - * * TIP: Consider using this library along with {SlotDerivation}. */ library StorageSlot { @@ -158,154 +140,4 @@ library StorageSlot { r.slot := store.slot } } - - /** - * @dev UDVT that represent a slot holding a address. - */ - type AddressSlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a AddressSlotType. - */ - function asAddress(bytes32 slot) internal pure returns (AddressSlotType) { - return AddressSlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a bool. - */ - type BooleanSlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a BooleanSlotType. - */ - function asBoolean(bytes32 slot) internal pure returns (BooleanSlotType) { - return BooleanSlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a bytes32. - */ - type Bytes32SlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a Bytes32SlotType. - */ - function asBytes32(bytes32 slot) internal pure returns (Bytes32SlotType) { - return Bytes32SlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a uint256. - */ - type Uint256SlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a Uint256SlotType. - */ - function asUint256(bytes32 slot) internal pure returns (Uint256SlotType) { - return Uint256SlotType.wrap(slot); - } - - /** - * @dev UDVT that represent a slot holding a int256. - */ - type Int256SlotType is bytes32; - - /** - * @dev Cast an arbitrary slot to a Int256SlotType. - */ - function asInt256(bytes32 slot) internal pure returns (Int256SlotType) { - return Int256SlotType.wrap(slot); - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(AddressSlotType slot) internal view returns (address value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(AddressSlotType slot, address value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(BooleanSlotType slot) internal view returns (bool value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(BooleanSlotType slot, bool value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(Bytes32SlotType slot) internal view returns (bytes32 value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(Bytes32SlotType slot, bytes32 value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(Uint256SlotType slot) internal view returns (uint256 value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(Uint256SlotType slot, uint256 value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } - - /** - * @dev Load the value held at location `slot` in transient storage. - */ - function tload(Int256SlotType slot) internal view returns (int256 value) { - assembly ("memory-safe") { - value := tload(slot) - } - } - - /** - * @dev Store `value` at location `slot` in transient storage. - */ - function tstore(Int256SlotType slot, int256 value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } - } } diff --git a/contracts/utils/TransientSlot.sol b/contracts/utils/TransientSlot.sol new file mode 100644 index 00000000000..f1f67bdceb0 --- /dev/null +++ b/contracts/utils/TransientSlot.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MIT +// This file was procedurally generated from scripts/generate/templates/TransientSlot.js. + +pragma solidity ^0.8.24; + +/** + * @dev Library for reading and writing value-types to specific transient storage slots. + * + * Transient slots are often used to store temporary values that are removed after the current transaction. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * * Example reading and writing values using transient storage: + * ```solidity + * contract Lock { + * using TransientSlot for *; + * + * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. + * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; + * + * modifier locked() { + * require(!_LOCK_SLOT.asBoolean().tload()); + * + * _LOCK_SLOT.asBoolean().tstore(true); + * _; + * _LOCK_SLOT.asBoolean().tstore(false); + * } + * } + * ``` + * + * TIP: Consider using this library along with {SlotDerivation}. + */ +library TransientSlot { + /** + * @dev UDVT that represent a slot holding a address. + */ + type AddressSlot is bytes32; + + /** + * @dev Cast an arbitrary slot to a AddressSlot. + */ + function asAddress(bytes32 slot) internal pure returns (AddressSlot) { + return AddressSlot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a bool. + */ + type BooleanSlot is bytes32; + + /** + * @dev Cast an arbitrary slot to a BooleanSlot. + */ + function asBoolean(bytes32 slot) internal pure returns (BooleanSlot) { + return BooleanSlot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a bytes32. + */ + type Bytes32Slot is bytes32; + + /** + * @dev Cast an arbitrary slot to a Bytes32Slot. + */ + function asBytes32(bytes32 slot) internal pure returns (Bytes32Slot) { + return Bytes32Slot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a uint256. + */ + type Uint256Slot is bytes32; + + /** + * @dev Cast an arbitrary slot to a Uint256Slot. + */ + function asUint256(bytes32 slot) internal pure returns (Uint256Slot) { + return Uint256Slot.wrap(slot); + } + + /** + * @dev UDVT that represent a slot holding a int256. + */ + type Int256Slot is bytes32; + + /** + * @dev Cast an arbitrary slot to a Int256Slot. + */ + function asInt256(bytes32 slot) internal pure returns (Int256Slot) { + return Int256Slot.wrap(slot); + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(AddressSlot slot) internal view returns (address value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(AddressSlot slot, address value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(BooleanSlot slot) internal view returns (bool value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(BooleanSlot slot, bool value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(Bytes32Slot slot) internal view returns (bytes32 value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(Bytes32Slot slot, bytes32 value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(Uint256Slot slot) internal view returns (uint256 value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(Uint256Slot slot, uint256 value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } + + /** + * @dev Load the value held at location `slot` in transient storage. + */ + function tload(Int256Slot slot) internal view returns (int256 value) { + assembly ("memory-safe") { + value := tload(slot) + } + } + + /** + * @dev Store `value` at location `slot` in transient storage. + */ + function tstore(Int256Slot slot, int256 value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } + } +} diff --git a/docs/modules/ROOT/pages/utilities.adoc b/docs/modules/ROOT/pages/utilities.adoc index adee6477f95..bb519907d7a 100644 --- a/docs/modules/ROOT/pages/utilities.adoc +++ b/docs/modules/ROOT/pages/utilities.adoc @@ -319,7 +319,7 @@ function _setImplementation(address newImplementation) internal { } ---- -The xref:api:utils.adoc#StorageSlot[`StorageSlot`] library also supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity. +The xref:api:utils.adoc#TransientSlot[`TransientSlot`] library supports transient storage through user defined value types (https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types[UDVTs]), which enables the same value types as in Solidity. [source,solidity] ---- diff --git a/scripts/generate/run.js b/scripts/generate/run.js index c28d1175b12..e4947eb12bd 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -39,9 +39,11 @@ for (const [file, template] of Object.entries({ 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', 'utils/SlotDerivation.sol': './templates/SlotDerivation.js', 'utils/StorageSlot.sol': './templates/StorageSlot.js', + 'utils/TransientSlot.sol': './templates/TransientSlot.js', 'utils/Arrays.sol': './templates/Arrays.js', 'utils/Packing.sol': './templates/Packing.js', 'mocks/StorageSlotMock.sol': './templates/StorageSlotMock.js', + 'mocks/TransientSlotMock.sol': './templates/TransientSlotMock.js', })) { generateFromTemplate(file, template, './contracts/'); } diff --git a/scripts/generate/templates/StorageSlot.js b/scripts/generate/templates/StorageSlot.js index 7a00f5e225c..53287b81fd9 100644 --- a/scripts/generate/templates/StorageSlot.js +++ b/scripts/generate/templates/StorageSlot.js @@ -2,7 +2,7 @@ const format = require('../format-lines'); const { TYPES } = require('./Slot.opts'); const header = `\ -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; /** * @dev Library for reading and writing primitive types to specific storage slots. @@ -29,24 +29,6 @@ pragma solidity ^0.8.24; * } * \`\`\` * - * Since version 5.1, this library also support writing and reading value types to and from transient storage. - * - * * Example using transient storage: - * \`\`\`solidity - * contract Lock { - * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. - * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; - * - * modifier locked() { - * require(!_LOCK_SLOT.asBoolean().tload()); - * - * _LOCK_SLOT.asBoolean().tstore(true); - * _; - * _LOCK_SLOT.asBoolean().tstore(false); - * } - * } - * \`\`\` - * * TIP: Consider using this library along with {SlotDerivation}. */ `; @@ -81,40 +63,6 @@ function get${name}Slot(${type} storage store) internal pure returns (${name}Slo } `; -const udvt = ({ type, name }) => `\ -/** - * @dev UDVT that represent a slot holding a ${type}. - */ -type ${name}SlotType is bytes32; - -/** - * @dev Cast an arbitrary slot to a ${name}SlotType. - */ -function as${name}(bytes32 slot) internal pure returns (${name}SlotType) { - return ${name}SlotType.wrap(slot); -} -`; - -const transient = ({ type, name }) => `\ -/** - * @dev Load the value held at location \`slot\` in transient storage. - */ -function tload(${name}SlotType slot) internal view returns (${type} value) { - assembly ("memory-safe") { - value := tload(slot) - } -} - -/** - * @dev Store \`value\` at location \`slot\` in transient storage. - */ -function tstore(${name}SlotType slot, ${type} value) internal { - assembly ("memory-safe") { - tstore(slot, value) - } -} -`; - // GENERATE module.exports = format( header.trimEnd(), @@ -123,8 +71,6 @@ module.exports = format( [].concat( TYPES.map(type => struct(type)), TYPES.flatMap(type => [get(type), !type.isValueType && getStorage(type)].filter(Boolean)), - TYPES.filter(type => type.isValueType).map(type => udvt(type)), - TYPES.filter(type => type.isValueType).map(type => transient(type)), ), ).trimEnd(), '}', diff --git a/scripts/generate/templates/StorageSlotMock.js b/scripts/generate/templates/StorageSlotMock.js index 623a6759247..c6d326a5e26 100644 --- a/scripts/generate/templates/StorageSlotMock.js +++ b/scripts/generate/templates/StorageSlotMock.js @@ -2,7 +2,7 @@ const format = require('../format-lines'); const { TYPES } = require('./Slot.opts'); const header = `\ -pragma solidity ^0.8.24; +pragma solidity ^0.8.20; import {Multicall} from "../utils/Multicall.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; @@ -40,18 +40,6 @@ function get${name}Storage(uint256 key) public view returns (${type} memory) { } `; -const transient = ({ type, name }) => `\ -event ${name}Value(bytes32 slot, ${type} value); - -function tload${name}(bytes32 slot) public { - emit ${name}Value(slot, slot.as${name}().tload()); -} - -function tstore(bytes32 slot, ${type} value) public { - slot.as${name}().tstore(value); -} -`; - // GENERATE module.exports = format( header, @@ -63,7 +51,6 @@ module.exports = format( TYPES.filter(type => type.isValueType).map(type => storageSetValueType(type)), TYPES.filter(type => type.isValueType).map(type => storageGetValueType(type)), TYPES.filter(type => !type.isValueType).map(type => storageSetNonValueType(type)), - TYPES.filter(type => type.isValueType).map(type => transient(type)), ), ).trimEnd(), '}', diff --git a/scripts/generate/templates/TransientSlot.js b/scripts/generate/templates/TransientSlot.js new file mode 100644 index 00000000000..8e291bc13da --- /dev/null +++ b/scripts/generate/templates/TransientSlot.js @@ -0,0 +1,80 @@ +const format = require('../format-lines'); +const { TYPES } = require('./Slot.opts'); + +const header = `\ +pragma solidity ^0.8.24; + +/** + * @dev Library for reading and writing value-types to specific transient storage slots. + * + * Transient slots are often used to store temporary values that are removed after the current transaction. + * This library helps with reading and writing to such slots without the need for inline assembly. + * + * * Example reading and writing values using transient storage: + * \`\`\`solidity + * contract Lock { + * using TransientSlot for *; + * + * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. + * bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; + * + * modifier locked() { + * require(!_LOCK_SLOT.asBoolean().tload()); + * + * _LOCK_SLOT.asBoolean().tstore(true); + * _; + * _LOCK_SLOT.asBoolean().tstore(false); + * } + * } + * \`\`\` + * + * TIP: Consider using this library along with {SlotDerivation}. + */ +`; + +const udvt = ({ type, name }) => `\ +/** + * @dev UDVT that represent a slot holding a ${type}. + */ +type ${name}Slot is bytes32; + +/** + * @dev Cast an arbitrary slot to a ${name}Slot. + */ +function as${name}(bytes32 slot) internal pure returns (${name}Slot) { + return ${name}Slot.wrap(slot); +} +`; + +const transient = ({ type, name }) => `\ +/** + * @dev Load the value held at location \`slot\` in transient storage. + */ +function tload(${name}Slot slot) internal view returns (${type} value) { + assembly ("memory-safe") { + value := tload(slot) + } +} + +/** + * @dev Store \`value\` at location \`slot\` in transient storage. + */ +function tstore(${name}Slot slot, ${type} value) internal { + assembly ("memory-safe") { + tstore(slot, value) + } +} +`; + +// GENERATE +module.exports = format( + header.trimEnd(), + 'library TransientSlot {', + format( + [].concat( + TYPES.filter(type => type.isValueType).map(type => udvt(type)), + TYPES.filter(type => type.isValueType).map(type => transient(type)), + ), + ).trimEnd(), + '}', +); diff --git a/scripts/generate/templates/TransientSlotMock.js b/scripts/generate/templates/TransientSlotMock.js new file mode 100644 index 00000000000..4807b0cc1ff --- /dev/null +++ b/scripts/generate/templates/TransientSlotMock.js @@ -0,0 +1,35 @@ +const format = require('../format-lines'); +const { TYPES } = require('./Slot.opts'); + +const header = `\ +pragma solidity ^0.8.24; + +import {Multicall} from "../utils/Multicall.sol"; +import {TransientSlot} from "../utils/TransientSlot.sol"; +`; + +const transient = ({ type, name }) => `\ +event ${name}Value(bytes32 slot, ${type} value); + +function tload${name}(bytes32 slot) public { + emit ${name}Value(slot, slot.as${name}().tload()); +} + +function tstore(bytes32 slot, ${type} value) public { + slot.as${name}().tstore(value); +} +`; + +// GENERATE +module.exports = format( + header, + 'contract TransientSlotMock is Multicall {', + format( + [].concat( + 'using TransientSlot for *;', + '', + TYPES.filter(type => type.isValueType).map(type => transient(type)), + ), + ).trimEnd(), + '}', +); diff --git a/test/utils/StorageSlot.test.js b/test/utils/StorageSlot.test.js index 35e83e29e63..ddcf305d1a7 100644 --- a/test/utils/StorageSlot.test.js +++ b/test/utils/StorageSlot.test.js @@ -70,37 +70,4 @@ describe('StorageSlot', function () { }); }); } - - for (const { name, type, value, zero } of TYPES.filter(type => type.isValueType)) { - describe(`${type} transient slot`, function () { - const load = `tload${name}(bytes32)`; - const store = `tstore(bytes32,${type})`; - const event = `${name}Value`; - - it('load', async function () { - await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); - }); - - it('store and load (2 txs)', async function () { - await this.mock[store](slot, value); - await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); - }); - - it('store and load (batched)', async function () { - await expect( - this.mock.multicall([ - this.mock.interface.encodeFunctionData(store, [slot, value]), - this.mock.interface.encodeFunctionData(load, [slot]), - this.mock.interface.encodeFunctionData(load, [otherSlot]), - ]), - ) - .to.emit(this.mock, event) - .withArgs(slot, value) - .to.emit(this.mock, event) - .withArgs(otherSlot, zero); - - await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); - }); - }); - } }); diff --git a/test/utils/TransientSlot.test.js b/test/utils/TransientSlot.test.js new file mode 100644 index 00000000000..7b70be375d4 --- /dev/null +++ b/test/utils/TransientSlot.test.js @@ -0,0 +1,59 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { generators } = require('../helpers/random'); + +const slot = ethers.id('some.storage.slot'); +const otherSlot = ethers.id('some.other.storage.slot'); + +// Non-value types are not supported by the `TransientSlot` library. +const TYPES = [ + { name: 'Boolean', type: 'bool', value: true, zero: false }, + { name: 'Address', type: 'address', value: generators.address(), zero: generators.address.zero }, + { name: 'Bytes32', type: 'bytes32', value: generators.bytes32(), zero: generators.bytes32.zero }, + { name: 'Uint256', type: 'uint256', value: generators.uint256(), zero: generators.uint256.zero }, + { name: 'Int256', type: 'int256', value: generators.int256(), zero: generators.int256.zero }, +]; + +async function fixture() { + return { mock: await ethers.deployContract('TransientSlotMock') }; +} + +describe('TransientSlot', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + for (const { name, type, value, zero } of TYPES) { + describe(`${type} transient slot`, function () { + const load = `tload${name}(bytes32)`; + const store = `tstore(bytes32,${type})`; + const event = `${name}Value`; + + it('load', async function () { + await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); + }); + + it('store and load (2 txs)', async function () { + await this.mock[store](slot, value); + await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); + }); + + it('store and load (batched)', async function () { + await expect( + this.mock.multicall([ + this.mock.interface.encodeFunctionData(store, [slot, value]), + this.mock.interface.encodeFunctionData(load, [slot]), + this.mock.interface.encodeFunctionData(load, [otherSlot]), + ]), + ) + .to.emit(this.mock, event) + .withArgs(slot, value) + .to.emit(this.mock, event) + .withArgs(otherSlot, zero); + + await expect(this.mock[load](slot)).to.emit(this.mock, event).withArgs(slot, zero); + }); + }); + } +}); From 72c152dc1c41f23d7c504e175f5b417fccc89426 Mon Sep 17 00:00:00 2001 From: Elias Rad <146735585+nnsW3@users.noreply.github.com> Date: Thu, 10 Oct 2024 07:23:01 +0300 Subject: [PATCH 65/81] Fix spelling issues in documentation (#5235) --- docs/modules/ROOT/pages/erc1155.adoc | 2 +- docs/modules/ROOT/pages/erc4626.adoc | 4 ++-- docs/modules/ROOT/pages/governance.adoc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/erc1155.adoc b/docs/modules/ROOT/pages/erc1155.adoc index 5bfb49accd4..7f00f3ea4b1 100644 --- a/docs/modules/ROOT/pages/erc1155.adoc +++ b/docs/modules/ROOT/pages/erc1155.adoc @@ -108,7 +108,7 @@ ERC1155InvalidReceiver("

") This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC-1155 protocol, so transfers to it are disabled to *prevent tokens from being locked forever*. As an example, https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d[the Golem contract currently holds over 350k `GNT` tokens], worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. -In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract: +In order for our contract to receive ERC-1155 tokens we can inherit from the convenience contract xref:api:token/ERC1155.adoc#ERC1155Holder[`ERC1155Holder`] which handles the registering for us. However, we need to remember to implement functionality to allow tokens to be transferred out of our contract: [source,solidity] ---- diff --git a/docs/modules/ROOT/pages/erc4626.adoc b/docs/modules/ROOT/pages/erc4626.adoc index 79388c0a2e7..c219595dd5c 100644 --- a/docs/modules/ROOT/pages/erc4626.adoc +++ b/docs/modules/ROOT/pages/erc4626.adoc @@ -29,7 +29,7 @@ image::erc4626-rate-loglogext.png[More exchange rates in logarithmic scale] === The attack -When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current share holders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. +When depositing tokens, the number of shares a user gets is rounded towards zero. This rounding takes away value from the user in favor of the vault (i.e. in favor of all the current shareholders). This rounding is often negligible because of the amount at stake. If you deposit 1e9 shares worth of tokens, the rounding will have you lose at most 0.0000001% of your deposit. However if you deposit 10 shares worth of tokens, you could lose 10% of your deposit. Even worse, if you deposit <1 share worth of tokens, then you get 0 shares, and you basically made a donation. For a given amount of assets, the more shares you receive the safer you are. If you want to limit your losses to at most 1%, you need to receive at least 100 shares. @@ -47,7 +47,7 @@ The idea of an inflation attack is that an attacker can donate assets to the vau image::erc4626-attack.png[Inflation attack without protection] -Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only share holder (from their donation), the attacker would steal all the tokens deposited. +Figure 6 shows how an attacker can manipulate the rate of an empty vault. First the attacker must deposit a small amount of tokens (1 token) and follow up with a donation of 1e5 tokens directly to the vault to move the exchange rate "right". This puts the vault in a state where any deposit smaller than 1e5 would be completely lost to the vault. Given that the attacker is the only shareholder (from their donation), the attacker would steal all the tokens deposited. An attacker would typically wait for a user to do the first deposit into the vault, and would frontrun that operation with the attack described above. The risk is low, and the size of the "donation" required to manipulate the vault is equivalent to the size of the deposit that is being attacked. diff --git a/docs/modules/ROOT/pages/governance.adoc b/docs/modules/ROOT/pages/governance.adoc index 19f23d78d92..5f8e77555aa 100644 --- a/docs/modules/ROOT/pages/governance.adoc +++ b/docs/modules/ROOT/pages/governance.adoc @@ -74,7 +74,7 @@ votingPeriod: How long does a proposal remain open to votes. These parameters are specified in the unit defined in the token's clock. Assuming the token uses block numbers, and assuming block time of around 12 seconds, we will have set votingDelay = 1 day = 7200 blocks, and votingPeriod = 1 week = 50400 blocks. -We can optionally set a proposal threshold as well. This restricts proposal creation to accounts who have enough voting power. +We can optionally set a proposal threshold as well. This restricts proposal creation to accounts that have enough voting power. ```solidity include::api:example$governance/MyGovernor.sol[] From bd588959adda3ebdffb5783bef7fe0a5af6e1e17 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 14 Oct 2024 17:13:33 +0200 Subject: [PATCH 66/81] Add toUint, toInt and hexToUint to Strings (#5166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: cairo Co-authored-by: Ernesto García --- .changeset/eighty-hounds-promise.md | 5 + contracts/governance/Governor.sol | 82 +++----- contracts/utils/README.adoc | 2 +- contracts/utils/Strings.sol | 284 ++++++++++++++++++++++++++++ test/utils/Strings.t.sol | 27 +++ test/utils/Strings.test.js | 171 +++++++++++++++-- 6 files changed, 503 insertions(+), 68 deletions(-) create mode 100644 .changeset/eighty-hounds-promise.md create mode 100644 test/utils/Strings.t.sol diff --git a/.changeset/eighty-hounds-promise.md b/.changeset/eighty-hounds-promise.md new file mode 100644 index 00000000000..3727a6515f0 --- /dev/null +++ b/.changeset/eighty-hounds-promise.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Strings`: Add `parseUint`, `parseInt`, `parseHexUint` and `parseAddress` to parse strings into numbers and addresses. Also provide variants of these functions that parse substrings, and `tryXxx` variants that do not revert on invalid input. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 02adffcb39b..f1851b30ee6 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -13,6 +13,7 @@ import {DoubleEndedQueue} from "../utils/structs/DoubleEndedQueue.sol"; import {Address} from "../utils/Address.sol"; import {Context} from "../utils/Context.sol"; import {Nonces} from "../utils/Nonces.sol"; +import {Strings} from "../utils/Strings.sol"; import {IGovernor, IERC6372} from "./IGovernor.sol"; /** @@ -760,67 +761,25 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 address proposer, string memory description ) internal view virtual returns (bool) { - uint256 len = bytes(description).length; - - // Length is too short to contain a valid proposer suffix - if (len < 52) { - return true; - } - - // Extract what would be the `#proposer=0x` marker beginning the suffix - bytes12 marker; - assembly ("memory-safe") { - // - Start of the string contents in memory = description + 32 - // - First character of the marker = len - 52 - // - Length of "#proposer=0x0000000000000000000000000000000000000000" = 52 - // - We read the memory word starting at the first character of the marker: - // - (description + 32) + (len - 52) = description + (len - 20) - // - Note: Solidity will ignore anything past the first 12 bytes - marker := mload(add(description, sub(len, 20))) - } - - // If the marker is not found, there is no proposer suffix to check - if (marker != bytes12("#proposer=0x")) { - return true; - } + unchecked { + uint256 length = bytes(description).length; - // Parse the 40 characters following the marker as uint160 - uint160 recovered = 0; - for (uint256 i = len - 40; i < len; ++i) { - (bool isHex, uint8 value) = _tryHexToUint(bytes(description)[i]); - // If any of the characters is not a hex digit, ignore the suffix entirely - if (!isHex) { + // Length is too short to contain a valid proposer suffix + if (length < 52) { return true; } - recovered = (recovered << 4) | value; - } - return recovered == uint160(proposer); - } + // Extract what would be the `#proposer=` marker beginning the suffix + bytes10 marker = bytes10(_unsafeReadBytesOffset(bytes(description), length - 52)); - /** - * @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in - * `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16` - */ - function _tryHexToUint(bytes1 char) private pure returns (bool isHex, uint8 value) { - uint8 c = uint8(char); - unchecked { - // Case 0-9 - if (47 < c && c < 58) { - return (true, c - 48); - } - // Case A-F - else if (64 < c && c < 71) { - return (true, c - 55); - } - // Case a-f - else if (96 < c && c < 103) { - return (true, c - 87); - } - // Else: not a hex char - else { - return (false, 0); + // If the marker is not found, there is no proposer suffix to check + if (marker != bytes10("#proposer=")) { + return true; } + + // Check that the last 42 characters (after the marker) are a properly formatted address. + (bool success, address recovered) = Strings.tryParseAddress(description, length - 42, length); + return !success || recovered == proposer; } } @@ -849,4 +808,17 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * @inheritdoc IGovernor */ function quorum(uint256 timepoint) public view virtual returns (uint256); + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } } diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 24b95b4e6f8..245c89c0486 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -34,7 +34,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. - * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. + * {StorageSlot}: Methods for accessing specific storage slots formatted as common primitive types. * {TransientSlot}: Primitives for reading from and writing to transient storage (only value types are currently supported). * {Multicall}: Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. * {Context}: A utility for abstracting the sender and calldata in the current execution context. diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index 5448060b70e..b72588646f7 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -4,12 +4,15 @@ pragma solidity ^0.8.20; import {Math} from "./math/Math.sol"; +import {SafeCast} from "./math/SafeCast.sol"; import {SignedMath} from "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { + using SafeCast for *; + bytes16 private constant HEX_DIGITS = "0123456789abcdef"; uint8 private constant ADDRESS_LENGTH = 20; @@ -18,6 +21,16 @@ library Strings { */ error StringsInsufficientHexLength(uint256 value, uint256 length); + /** + * @dev The string being parsed contains characters that are not in scope of the given base. + */ + error StringsInvalidChar(); + + /** + * @dev The string being parsed is not a properly formatted address. + */ + error StringsInvalidAddressFormat(); + /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ @@ -113,4 +126,275 @@ library Strings { function equal(string memory a, string memory b) internal pure returns (bool) { return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); } + + /** + * @dev Parse a decimal string and returns the value as a `uint256`. + * + * Requirements: + * - The string must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type + */ + function parseUint(string memory input) internal pure returns (uint256) { + return parseUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[0-9]*` + * - The result must fit into an `uint256` type + */ + function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseUint(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) { + return tryParseUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseUint( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + uint256 result = 0; + for (uint256 i = begin; i < end; ++i) { + uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); + if (chr > 9) return (false, 0); + result *= 10; + result += chr; + } + return (true, result); + } + + /** + * @dev Parse a decimal string and returns the value as a `int256`. + * + * Requirements: + * - The string must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. + */ + function parseInt(string memory input) internal pure returns (int256) { + return parseInt(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `[-+]?[0-9]*` + * - The result must fit in an `int256` type. + */ + function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) { + (bool success, int256 value) = tryParseInt(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if + * the result does not fit in a `int256`. + * + * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + */ + function tryParseInt(string memory input) internal pure returns (bool success, int256 value) { + return tryParseInt(input, 0, bytes(input).length); + } + + uint256 private constant ABS_MIN_INT256 = 2 ** 255; + + /** + * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid + * character or if the result does not fit in a `int256`. + * + * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`. + */ + function tryParseInt( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, int256 value) { + bytes memory buffer = bytes(input); + + // Check presence of a negative sign. + bytes1 sign = bytes1(_unsafeReadBytesOffset(buffer, begin)); + bool positiveSign = sign == bytes1("+"); + bool negativeSign = sign == bytes1("-"); + uint256 offset = (positiveSign || negativeSign).toUint(); + + (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end); + + if (absSuccess && absValue < ABS_MIN_INT256) { + return (true, negativeSign ? -int256(absValue) : int256(absValue)); + } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) { + return (true, type(int256).min); + } else return (false, 0); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. + * + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. + */ + function parseHexUint(string memory input) internal pure returns (uint256) { + return parseHexUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHexUint} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]*` + * - The result must fit in an `uint256` type. + */ + function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) { + (bool success, uint256 value) = tryParseHexUint(input, begin, end); + if (!success) revert StringsInvalidChar(); + return value; + } + + /** + * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) { + return tryParseHexUint(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an + * invalid character. + * + * NOTE: This function will revert if the result does not fit in a `uint256`. + */ + function tryParseHexUint( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, uint256 value) { + bytes memory buffer = bytes(input); + + // skip 0x prefix if present + bool hasPrefix = bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); + uint256 offset = hasPrefix.toUint() * 2; + + uint256 result = 0; + for (uint256 i = begin + offset; i < end; ++i) { + uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i))); + if (chr > 15) return (false, 0); + result *= 16; + unchecked { + // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check). + // This guaratees that adding a value < 16 will not cause an overflow, hence the unchecked. + result += chr; + } + } + return (true, result); + } + + /** + * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. + * + * Requirements: + * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}` + */ + function parseAddress(string memory input) internal pure returns (address) { + return parseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress} that parses a substring of `input` located between position `begin` (included) and + * `end` (excluded). + * + * Requirements: + * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}` + */ + function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) { + (bool success, address value) = tryParseAddress(input, begin, end); + if (!success) revert StringsInvalidAddressFormat(); + return value; + } + + /** + * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly + * formatted address. See {parseAddress} requirements. + */ + function tryParseAddress(string memory input) internal pure returns (bool success, address value) { + return tryParseAddress(input, 0, bytes(input).length); + } + + /** + * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly + * formatted address. See {parseAddress} requirements. + */ + function tryParseAddress( + string memory input, + uint256 begin, + uint256 end + ) internal pure returns (bool success, address value) { + // check that input is the correct length + bool hasPrefix = bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); + uint256 expectedLength = 40 + hasPrefix.toUint() * 2; + + if (end - begin == expectedLength) { + // length guarantees that this does not overflow, and value is at most type(uint160).max + (bool s, uint256 v) = tryParseHexUint(input, begin, end); + return (s, address(uint160(v))); + } else { + return (false, address(0)); + } + } + + function _tryParseChr(bytes1 chr) private pure returns (uint8) { + uint8 value = uint8(chr); + + // Try to parse `chr`: + // - Case 1: [0-9] + // - Case 2: [a-f] + // - Case 3: [A-F] + // - otherwise not supported + unchecked { + if (value > 47 && value < 58) value -= 48; + else if (value > 96 && value < 103) value -= 87; + else if (value > 64 && value < 71) value -= 55; + else return type(uint8).max; + } + + return value; + } + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } } diff --git a/test/utils/Strings.t.sol b/test/utils/Strings.t.sol new file mode 100644 index 00000000000..b3eb67a5cd2 --- /dev/null +++ b/test/utils/Strings.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract StringsTest is Test { + using Strings for *; + + function testParse(uint256 value) external { + assertEq(value, value.toString().parseUint()); + } + + function testParseSigned(int256 value) external { + assertEq(value, value.toStringSigned().parseInt()); + } + + function testParseHex(uint256 value) external { + assertEq(value, value.toHexString().parseHexUint()); + } + + function testParseChecksumHex(address value) external { + assertEq(value, value.toChecksumHexString().parseAddress()); + } +} diff --git a/test/utils/Strings.test.js b/test/utils/Strings.test.js index 6353fd886db..5a47d4d10de 100644 --- a/test/utils/Strings.test.js +++ b/test/utils/Strings.test.js @@ -1,6 +1,7 @@ const { ethers } = require('hardhat'); const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); async function fixture() { const mock = await ethers.deployContract('$Strings'); @@ -38,11 +39,15 @@ describe('Strings', function () { it('converts MAX_UINT256', async function () { const value = ethers.MaxUint256; expect(await this.mock.$toString(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseUint(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseUint(value.toString(10))).to.deep.equal([true, value]); }); for (const value of values) { it(`converts ${value}`, async function () { - expect(await this.mock.$toString(value)).to.equal(value); + expect(await this.mock.$toString(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseUint(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseUint(value.toString(10))).to.deep.equal([true, value]); }); } }); @@ -51,21 +56,29 @@ describe('Strings', function () { it('converts MAX_INT256', async function () { const value = ethers.MaxInt256; expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); it('converts MIN_INT256', async function () { const value = ethers.MinInt256; expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); for (const value of values) { it(`convert ${value}`, async function () { - expect(await this.mock.$toStringSigned(value)).to.equal(value); + expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); + expect(await this.mock.$parseInt(value.toString(10))).to.equal(value); + expect(await this.mock.$tryParseInt(value.toString(10))).to.deep.equal([true, value]); }); it(`convert negative ${value}`, async function () { const negated = -value; expect(await this.mock.$toStringSigned(negated)).to.equal(negated.toString(10)); + expect(await this.mock.$parseInt(negated.toString(10))).to.equal(negated); + expect(await this.mock.$tryParseInt(negated.toString(10))).to.deep.equal([true, negated]); }); } }); @@ -73,17 +86,36 @@ describe('Strings', function () { describe('toHexString', function () { it('converts 0', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0n)).to.equal('0x00'); + const value = 0n; + const string = ethers.toBeHex(value); // 0x00 + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); it('converts a positive number', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0x4132n)).to.equal('0x4132'); + const value = 0x4132n; + const string = ethers.toBeHex(value); + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); it('converts MAX_UINT256', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(ethers.MaxUint256)).to.equal( - `0x${ethers.MaxUint256.toString(16)}`, - ); + const value = ethers.MaxUint256; + const string = ethers.toBeHex(value); + + expect(await this.mock.getFunction('$toHexString(uint256)')(value)).to.equal(string); + expect(await this.mock.$parseHexUint(string)).to.equal(value); + expect(await this.mock.$parseHexUint(string.replace(/0x/, ''))).to.equal(value); + expect(await this.mock.$tryParseHexUint(string)).to.deep.equal([true, value]); + expect(await this.mock.$tryParseHexUint(string.replace(/0x/, ''))).to.deep.equal([true, value]); }); }); @@ -97,13 +129,13 @@ describe('Strings', function () { it('converts a positive number (short)', async function () { const length = 1n; await expect(this.mock.getFunction('$toHexString(uint256,uint256)')(0x4132n, length)) - .to.be.revertedWithCustomError(this.mock, `StringsInsufficientHexLength`) + .to.be.revertedWithCustomError(this.mock, 'StringsInsufficientHexLength') .withArgs(0x4132, length); }); it('converts MAX_UINT256', async function () { expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(ethers.MaxUint256, 32n)).to.equal( - `0x${ethers.MaxUint256.toString(16)}`, + ethers.toBeHex(ethers.MaxUint256), ); }); }); @@ -139,9 +171,16 @@ describe('Strings', function () { describe('toChecksumHexString', function () { for (const addr of addresses) { it(`converts ${addr}`, async function () { - expect(await this.mock.getFunction('$toChecksumHexString(address)')(addr)).to.equal( - ethers.getAddress(addr.toLowerCase()), - ); + expect(await this.mock.$toChecksumHexString(addr)).to.equal(ethers.getAddress(addr)); + }); + } + }); + + describe('parseAddress', function () { + for (const addr of addresses) { + it(`converts ${addr}`, async function () { + expect(await this.mock.$parseAddress(addr)).to.equal(ethers.getAddress(addr)); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([true, ethers.getAddress(addr)]); }); } }); @@ -177,4 +216,112 @@ describe('Strings', function () { expect(await this.mock.$equal(str1, str2)).to.be.true; }); }); + + describe('Edge cases: invalid parsing', function () { + it('parseUint overflow', async function () { + await expect(this.mock.$parseUint((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseUint((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + }); + + it('parseUint invalid character', async function () { + await expect(this.mock.$parseUint('0x1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('-10')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseUint('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseUint('0x1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('-10')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1.0')).deep.equal([false, 0n]); + expect(await this.mock.$tryParseUint('1 000')).deep.equal([false, 0n]); + }); + + it('parseInt overflow', async function () { + await expect(this.mock.$parseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$parseInt((-ethers.MaxUint256 - 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseInt((ethers.MaxUint256 + 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseInt((-ethers.MaxUint256 - 1n).toString(10))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$parseInt((ethers.MaxInt256 + 1n).toString(10))).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + await expect(this.mock.$parseInt((ethers.MinInt256 - 1n).toString(10))).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + expect(await this.mock.$tryParseInt((ethers.MaxInt256 + 1n).toString(10))).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt((ethers.MinInt256 - 1n).toString(10))).to.deep.equal([false, 0n]); + }); + + it('parseInt invalid character', async function () { + await expect(this.mock.$parseInt('0x1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseInt('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseInt('0x1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1.0')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseInt('1 000')).to.deep.equal([false, 0n]); + }); + + it('parseHexUint overflow', async function () { + await expect(this.mock.$parseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + await expect(this.mock.$tryParseHexUint((ethers.MaxUint256 + 1n).toString(16))).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); + }); + + it('parseHexUint invalid character', async function () { + await expect(this.mock.$parseHexUint('0123456789abcdefg')).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidChar', + ); + await expect(this.mock.$parseHexUint('-1')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('-f')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('-0xf')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('1.0')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + await expect(this.mock.$parseHexUint('1 000')).to.be.revertedWithCustomError(this.mock, 'StringsInvalidChar'); + expect(await this.mock.$tryParseHexUint('0123456789abcdefg')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-1')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-f')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('-0xf')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('1.0')).to.deep.equal([false, 0n]); + expect(await this.mock.$tryParseHexUint('1 000')).to.deep.equal([false, 0n]); + }); + + it('parseAddress invalid format', async function () { + for (const addr of [ + '0x736a507fB2881d6bB62dcA54673CF5295dC07833', // valid + '0x736a507fB2881d6-B62dcA54673CF5295dC07833', // invalid char + '0x0736a507fB2881d6bB62dcA54673CF5295dC07833', // tooLong + '0x36a507fB2881d6bB62dcA54673CF5295dC07833', // tooShort + '736a507fB2881d6bB62dcA54673CF5295dC07833', // missingPrefix - supported + ]) { + if (ethers.isAddress(addr)) { + expect(await this.mock.$parseAddress(addr)).to.equal(ethers.getAddress(addr)); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([true, ethers.getAddress(addr)]); + } else { + await expect(this.mock.$parseAddress(addr)).to.be.revertedWithCustomError( + this.mock, + 'StringsInvalidAddressFormat', + ); + expect(await this.mock.$tryParseAddress(addr)).to.deep.equal([false, ethers.ZeroAddress]); + } + } + }); + }); }); From fe6249ec2c2dcdbb05e2b6a0d0d3e229f6ffbb37 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Mon, 14 Oct 2024 22:41:08 +0200 Subject: [PATCH 67/81] Bytes library and CAIP2/CAIP10 helpers (#5252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: cairo Co-authored-by: Ernesto García Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- .changeset/healthy-books-shout.md | 5 ++ .changeset/proud-planes-arrive.md | 5 ++ contracts/mocks/Stateless.sol | 3 + contracts/utils/Bytes.sol | 115 ++++++++++++++++++++++++++++++ contracts/utils/CAIP10.sol | 49 +++++++++++++ contracts/utils/CAIP2.sol | 47 ++++++++++++ contracts/utils/README.adoc | 2 + test/helpers/chains.js | 109 ++++++++++++++++++++++++++++ test/utils/Bytes.test.js | 88 +++++++++++++++++++++++ test/utils/CAIP.test.js | 53 ++++++++++++++ 10 files changed, 476 insertions(+) create mode 100644 .changeset/healthy-books-shout.md create mode 100644 .changeset/proud-planes-arrive.md create mode 100644 contracts/utils/Bytes.sol create mode 100644 contracts/utils/CAIP10.sol create mode 100644 contracts/utils/CAIP2.sol create mode 100644 test/helpers/chains.js create mode 100644 test/utils/Bytes.test.js create mode 100644 test/utils/CAIP.test.js diff --git a/.changeset/healthy-books-shout.md b/.changeset/healthy-books-shout.md new file mode 100644 index 00000000000..274e7a48868 --- /dev/null +++ b/.changeset/healthy-books-shout.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`CAIP2` and `CAIP10`: Add libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. diff --git a/.changeset/proud-planes-arrive.md b/.changeset/proud-planes-arrive.md new file mode 100644 index 00000000000..6408976414d --- /dev/null +++ b/.changeset/proud-planes-arrive.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Bytes`: Add a library of common operation that operate on `bytes` objects. diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 846c77d98e8..98e7eaf7443 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -9,6 +9,9 @@ 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 {Bytes} from "../utils/Bytes.sol"; +import {CAIP2} from "../utils/CAIP2.sol"; +import {CAIP10} from "../utils/CAIP10.sol"; import {Checkpoints} from "../utils/structs/Checkpoints.sol"; import {CircularBuffer} from "../utils/structs/CircularBuffer.sol"; import {Clones} from "../proxy/Clones.sol"; diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol new file mode 100644 index 00000000000..84e5a3ed51f --- /dev/null +++ b/contracts/utils/Bytes.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Math} from "./math/Math.sol"; + +/** + * @dev Bytes operations. + */ +library Bytes { + /** + * @dev Forward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the first instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] + */ + function indexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return indexOf(buffer, s, 0); + } + + /** + * @dev Forward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (at or after `pos`), returns the index of the next instance + * * If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf[Javascript's `Array.indexOf`] + */ + function indexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + uint256 length = buffer.length; + for (uint256 i = pos; i < length; ++i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i)) == s) { + return i; + } + } + return type(uint256).max; + } + } + + /** + * @dev Backward search for `s` in `buffer` + * * If `s` is present in the buffer, returns the index of the last instance + * * If `s` is not present in the buffer, returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] + */ + function lastIndexOf(bytes memory buffer, bytes1 s) internal pure returns (uint256) { + return lastIndexOf(buffer, s, type(uint256).max); + } + + /** + * @dev Backward search for `s` in `buffer` starting at position `pos` + * * If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance + * * If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf[Javascript's `Array.lastIndexOf`] + */ + function lastIndexOf(bytes memory buffer, bytes1 s, uint256 pos) internal pure returns (uint256) { + unchecked { + uint256 length = buffer.length; + // NOTE here we cannot do `i = Math.min(pos + 1, length)` because `pos + 1` could overflow + for (uint256 i = Math.min(pos, length - 1) + 1; i > 0; --i) { + if (bytes1(_unsafeReadBytesOffset(buffer, i - 1)) == s) { + return i - 1; + } + } + return type(uint256).max; + } + } + + /** + * @dev Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] + */ + function slice(bytes memory buffer, uint256 start) internal pure returns (bytes memory) { + return slice(buffer, start, buffer.length); + } + + /** + * @dev Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in + * memory. + * + * NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice[Javascript's `Array.slice`] + */ + function slice(bytes memory buffer, uint256 start, uint256 end) internal pure returns (bytes memory) { + // sanitize + uint256 length = buffer.length; + end = Math.min(end, length); + start = Math.min(start, end); + + // allocate and copy + bytes memory result = new bytes(end - start); + assembly ("memory-safe") { + mcopy(add(result, 0x20), add(buffer, add(start, 0x20)), sub(end, start)) + } + + return result; + } + + /** + * @dev Reads a bytes32 from a bytes array without bounds checking. + * + * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the + * assembly block as such would prevent some optimizations. + */ + function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) { + // This is not memory safe in the general case, but all calls to this private function are within bounds. + assembly ("memory-safe") { + value := mload(add(buffer, add(0x20, offset))) + } + } +} diff --git a/contracts/utils/CAIP10.sol b/contracts/utils/CAIP10.sol new file mode 100644 index 00000000000..e9ed17305b6 --- /dev/null +++ b/contracts/utils/CAIP10.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SafeCast} from "./math/SafeCast.sol"; +import {Bytes} from "./Bytes.sol"; +import {CAIP2} from "./CAIP2.sol"; +import {Strings} from "./Strings.sol"; + +/** + * @dev Helper library to format and parse CAIP-10 identifiers + * + * https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md[CAIP-10] defines account identifiers as: + * account_id: chain_id + ":" + account_address + * chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See {CAIP2}) + * account_address: [-.%a-zA-Z0-9]{1,128} + */ +library CAIP10 { + using SafeCast for uint256; + using Strings for address; + using Bytes for bytes; + + /// @dev Return the CAIP-10 identifier for an account on the current (local) chain. + function local(address account) internal view returns (string memory) { + return format(CAIP2.local(), account.toChecksumHexString()); + } + + /** + * @dev Return the CAIP-10 identifier for a given caip2 chain and account. + * + * NOTE: This function does not verify that the inputs are properly formatted. + */ + function format(string memory caip2, string memory account) internal pure returns (string memory) { + return string.concat(caip2, ":", account); + } + + /** + * @dev Parse a CAIP-10 identifier into its components. + * + * NOTE: This function does not verify that the CAIP-10 input is properly formatted. The `caip2` return can be + * parsed using the {CAIP2} library. + */ + function parse(string memory caip10) internal pure returns (string memory caip2, string memory account) { + bytes memory buffer = bytes(caip10); + + uint256 pos = buffer.lastIndexOf(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); + } +} diff --git a/contracts/utils/CAIP2.sol b/contracts/utils/CAIP2.sol new file mode 100644 index 00000000000..13a98f58a46 --- /dev/null +++ b/contracts/utils/CAIP2.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SafeCast} from "./math/SafeCast.sol"; +import {Bytes} from "./Bytes.sol"; +import {Strings} from "./Strings.sol"; + +/** + * @dev Helper library to format and parse CAIP-2 identifiers + * + * https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-2] defines chain identifiers as: + * chain_id: namespace + ":" + reference + * namespace: [-a-z0-9]{3,8} + * reference: [-_a-zA-Z0-9]{1,32} + */ +library CAIP2 { + using SafeCast for uint256; + using Strings for uint256; + using Bytes for bytes; + + /// @dev Return the CAIP-2 identifier for the current (local) chain. + function local() internal view returns (string memory) { + return format("eip155", block.chainid.toString()); + } + + /** + * @dev Return the CAIP-2 identifier for a given namespace and reference. + * + * NOTE: This function does not verify that the inputs are properly formatted. + */ + function format(string memory namespace, string memory ref) internal pure returns (string memory) { + return string.concat(namespace, ":", ref); + } + + /** + * @dev Parse a CAIP-2 identifier into its components. + * + * NOTE: This function does not verify that the CAIP-2 input is properly formatted. + */ + function parse(string memory caip2) internal pure returns (string memory namespace, string memory ref) { + bytes memory buffer = bytes(caip2); + + uint256 pos = buffer.indexOf(":"); + return (string(buffer.slice(0, pos)), string(buffer.slice(pos + 1))); + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index 245c89c0486..eeef84aae7c 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -31,6 +31,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Address}: Collection of functions for overloading Solidity's https://docs.soliditylang.org/en/latest/types.html#address[`address`] type. * {Arrays}: Collection of functions that operate on https://docs.soliditylang.org/en/latest/types.html#arrays[`arrays`]. * {Base64}: On-chain base64 and base64URL encoding according to https://datatracker.ietf.org/doc/html/rfc4648[RFC-4648]. + * {Bytes}: Common operations on bytes objects. * {Strings}: Common operations for strings formatting. * {ShortString}: Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. * {SlotDerivation}: Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. @@ -41,6 +42,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {Packing}: A library for packing and unpacking multiple values into bytes32 * {Panic}: A library to revert with https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require[Solidity panic codes]. * {Comparators}: A library that contains comparator functions to use with with the {Heap} library. + * {CAIP2}, {CAIP10}: Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. [NOTE] ==== diff --git a/test/helpers/chains.js b/test/helpers/chains.js new file mode 100644 index 00000000000..3711a81258e --- /dev/null +++ b/test/helpers/chains.js @@ -0,0 +1,109 @@ +// NOTE: this file defines some examples of CAIP-2 and CAIP-10 identifiers. +// The following listing does not pretend to be exhaustive or even accurate. It SHOULD NOT be used in production. + +const { ethers } = require('hardhat'); +const { mapValues } = require('./iterate'); + +// EVM (https://axelarscan.io/resources/chains?type=evm) +const ethereum = { + Ethereum: '1', + optimism: '10', + binance: '56', + Polygon: '137', + Fantom: '250', + fraxtal: '252', + filecoin: '314', + Moonbeam: '1284', + centrifuge: '2031', + kava: '2222', + mantle: '5000', + base: '8453', + immutable: '13371', + arbitrum: '42161', + celo: '42220', + Avalanche: '43114', + linea: '59144', + blast: '81457', + scroll: '534352', + aurora: '1313161554', +}; + +// Cosmos (https://axelarscan.io/resources/chains?type=cosmos) +const cosmos = { + Axelarnet: 'axelar-dojo-1', + osmosis: 'osmosis-1', + cosmoshub: 'cosmoshub-4', + juno: 'juno-1', + 'e-money': 'emoney-3', + injective: 'injective-1', + crescent: 'crescent-1', + kujira: 'kaiyo-1', + 'secret-snip': 'secret-4', + secret: 'secret-4', + sei: 'pacific-1', + stargaze: 'stargaze-1', + assetmantle: 'mantle-1', + fetch: 'fetchhub-4', + ki: 'kichain-2', + evmos: 'evmos_9001-2', + aura: 'xstaxy-1', + comdex: 'comdex-1', + persistence: 'core-1', + regen: 'regen-1', + umee: 'umee-1', + agoric: 'agoric-3', + xpla: 'dimension_37-1', + acre: 'acre_9052-1', + stride: 'stride-1', + carbon: 'carbon-1', + sommelier: 'sommelier-3', + neutron: 'neutron-1', + rebus: 'reb_1111-1', + archway: 'archway-1', + provenance: 'pio-mainnet-1', + ixo: 'ixo-5', + migaloo: 'migaloo-1', + teritori: 'teritori-1', + haqq: 'haqq_11235-1', + celestia: 'celestia', + ojo: 'agamotto', + chihuahua: 'chihuahua-1', + saga: 'ssc-1', + dymension: 'dymension_1100-1', + fxcore: 'fxcore', + c4e: 'perun-1', + bitsong: 'bitsong-2b', + nolus: 'pirin-1', + lava: 'lava-mainnet-1', + 'terra-2': 'phoenix-1', + terra: 'columbus-5', +}; + +const makeCAIP = ({ namespace, reference, account }) => ({ + namespace, + reference, + account, + caip2: `${namespace}:${reference}`, + caip10: `${namespace}:${reference}:${account}`, + toCaip10: other => `${namespace}:${reference}:${ethers.getAddress(other.target ?? other.address ?? other)}`, +}); + +module.exports = { + CHAINS: mapValues( + Object.assign( + mapValues(ethereum, reference => ({ + namespace: 'eip155', + reference, + account: ethers.Wallet.createRandom().address, + })), + mapValues(cosmos, reference => ({ + namespace: 'cosmos', + reference, + account: ethers.encodeBase58(ethers.randomBytes(32)), + })), + ), + makeCAIP, + ), + getLocalCAIP: account => + ethers.provider.getNetwork().then(({ chainId }) => makeCAIP({ namespace: 'eip155', reference: chainId, account })), +}; diff --git a/test/utils/Bytes.test.js b/test/utils/Bytes.test.js new file mode 100644 index 00000000000..52a1ae95e77 --- /dev/null +++ b/test/utils/Bytes.test.js @@ -0,0 +1,88 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +async function fixture() { + const mock = await ethers.deployContract('$Bytes'); + return { mock }; +} + +const lorem = ethers.toUtf8Bytes( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', +); +const present = lorem.at(1); +const absent = 255; + +describe('Bytes', function () { + before(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('indexOf', function () { + it('first', async function () { + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.indexOf(present)); + }); + + it('from index', async function () { + for (const start in Array(lorem.length + 10).fill()) { + const index = lorem.indexOf(present, start); + const result = index === -1 ? ethers.MaxUint256 : index; + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal(result); + } + }); + + it('absent', async function () { + expect(await this.mock.$indexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256); + }); + }); + + describe('lastIndexOf', function () { + it('first', async function () { + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present))).to.equal(lorem.lastIndexOf(present)); + }); + + it('from index', async function () { + for (const start in Array(lorem.length + 10).fill()) { + const index = lorem.lastIndexOf(present, start); + const result = index === -1 ? ethers.MaxUint256 : index; + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(present), ethers.Typed.uint256(start))).to.equal( + result, + ); + } + }); + + it('absent', async function () { + expect(await this.mock.$lastIndexOf(lorem, ethers.toBeHex(absent))).to.equal(ethers.MaxUint256); + }); + }); + + describe('slice', function () { + describe('slice(bytes, uint256)', function () { + for (const [descr, start] of Object.entries({ + 'start = 0': 0, + 'start within bound': 10, + 'start out of bound': 1000, + })) { + it(descr, async function () { + const result = ethers.hexlify(lorem.slice(start)); + expect(await this.mock.$slice(lorem, start)).to.equal(result); + }); + } + }); + + describe('slice(bytes, uint256, uint256)', function () { + for (const [descr, [start, end]] of Object.entries({ + 'start = 0': [0, 42], + 'start and end within bound': [17, 42], + 'end out of bound': [42, 1000], + 'start = end': [17, 17], + 'start > end': [42, 17], + })) { + it(descr, async function () { + const result = ethers.hexlify(lorem.slice(start, end)); + expect(await this.mock.$slice(lorem, start, ethers.Typed.uint256(end))).to.equal(result); + }); + } + }); + }); +}); diff --git a/test/utils/CAIP.test.js b/test/utils/CAIP.test.js new file mode 100644 index 00000000000..cd5995cade0 --- /dev/null +++ b/test/utils/CAIP.test.js @@ -0,0 +1,53 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { CHAINS, getLocalCAIP } = require('../helpers/chains'); + +async function fixture() { + const caip2 = await ethers.deployContract('$CAIP2'); + const caip10 = await ethers.deployContract('$CAIP10'); + return { caip2, caip10 }; +} + +describe('CAIP utilities', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('CAIP-2', function () { + it('local()', async function () { + const { caip2 } = await getLocalCAIP(); + expect(await this.caip2.$local()).to.equal(caip2); + }); + + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) + it(`format(${namespace}, ${reference})`, async function () { + expect(await this.caip2.$format(namespace, reference)).to.equal(caip2); + }); + + for (const { namespace, reference, caip2 } of Object.values(CHAINS)) + it(`parse(${caip2})`, async function () { + expect(await this.caip2.$parse(caip2)).to.deep.equal([namespace, reference]); + }); + }); + + describe('CAIP-10', function () { + const { address: account } = ethers.Wallet.createRandom(); + + it(`local(${account})`, async function () { + const { caip10 } = await getLocalCAIP(account); + expect(await this.caip10.$local(ethers.Typed.address(account))).to.equal(caip10); + }); + + for (const { account, caip2, caip10 } of Object.values(CHAINS)) + it(`format(${caip2}, ${account})`, async function () { + expect(await this.caip10.$format(ethers.Typed.string(caip2), ethers.Typed.string(account))).to.equal(caip10); + }); + + for (const { account, caip2, caip10 } of Object.values(CHAINS)) + it(`parse(${caip10})`, async function () { + expect(await this.caip10.$parse(caip10)).to.deep.equal([caip2, account]); + }); + }); +}); From 3291252c866ad698f6a55ec660259e49a67eb3d0 Mon Sep 17 00:00:00 2001 From: cairo Date: Thu, 17 Oct 2024 04:33:22 -0700 Subject: [PATCH 68/81] Document risk of `SafeERC20` and `ERC-7674` (#5262) --- .changeset/yellow-tables-sell.md | 5 +++++ contracts/token/ERC20/utils/SafeERC20.sol | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .changeset/yellow-tables-sell.md diff --git a/.changeset/yellow-tables-sell.md b/.changeset/yellow-tables-sell.md new file mode 100644 index 00000000000..f8cdc8d306b --- /dev/null +++ b/.changeset/yellow-tables-sell.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index ed41fb042c9..19c2f3e19e8 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -46,6 +46,11 @@ library SafeERC20 { /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. + * + * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" + * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using + * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract + * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); @@ -55,6 +60,11 @@ library SafeERC20 { /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. + * + * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" + * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using + * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract + * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { @@ -70,6 +80,10 @@ library SafeERC20 { * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. + * + * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function + * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being + * set here. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); From 0034c302241c4b1a1685272d4df42ca5d64b8c34 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:41:15 +0200 Subject: [PATCH 69/81] Merge release-v5.1 branch (#5266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hadrien Croubois Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Hadrien Croubois Co-authored-by: cairo Co-authored-by: Ernesto García Co-authored-by: sudo rm -rf --no-preserve-root / Co-authored-by: danilo neves cruz Co-authored-by: omahs <73983677+omahs@users.noreply.github.com> Co-authored-by: skyge <1506186404li@gmail.com> Co-authored-by: PurrProof <149718167+PurrProof@users.noreply.github.com> Co-authored-by: Eric Lau Co-authored-by: plooten Co-authored-by: github-actions[bot] Co-authored-by: Md Zartaj Afser <91191500+Zartaj0@users.noreply.github.com> --- .changeset/chilled-walls-develop.md | 5 -- .changeset/chilly-humans-warn.md | 5 -- .changeset/cold-cheetahs-check.md | 5 -- .changeset/cool-mangos-compare.md | 5 -- .changeset/curvy-crabs-repeat.md | 5 -- .changeset/dirty-cobras-smile.md | 5 -- .changeset/eight-eyes-burn.md | 5 -- .changeset/eleven-planets-relax.md | 5 -- .changeset/flat-turtles-repeat.md | 5 -- .changeset/fluffy-buses-jump.md | 5 -- .changeset/fluffy-steaks-exist.md | 5 -- .changeset/forty-dodos-visit.md | 5 -- .changeset/friendly-nails-push.md | 5 -- .changeset/gentle-bulldogs-turn.md | 5 -- .changeset/gorgeous-badgers-vanish.md | 5 -- .changeset/great-pianos-work.md | 5 -- .changeset/heavy-baboons-give.md | 5 -- .changeset/kind-planets-cough.md | 5 -- .changeset/light-news-listen.md | 5 -- .changeset/lucky-crews-eat.md | 5 -- .changeset/nervous-eyes-teach.md | 5 -- .changeset/nervous-pans-grow.md | 5 -- .changeset/nice-paws-pull.md | 5 -- .changeset/odd-files-protect.md | 5 -- .changeset/odd-lobsters-wash.md | 5 -- .changeset/poor-chefs-cheat.md | 5 -- .changeset/serious-carrots-provide.md | 5 -- .changeset/shiny-poets-whisper.md | 5 -- .changeset/silver-swans-promise.md | 5 -- .changeset/smart-bugs-switch.md | 5 -- .changeset/spotty-falcons-explain.md | 5 -- .changeset/spotty-queens-own.md | 5 -- .changeset/strong-singers-talk.md | 5 -- .changeset/thick-pumpkins-report.md | 5 -- .changeset/thin-walls-drop.md | 5 -- .changeset/twenty-feet-grin.md | 5 -- .changeset/violet-moons-tell.md | 5 -- .changeset/warm-sheep-cover.md | 5 -- .changeset/wise-bobcats-speak.md | 5 -- .changeset/witty-chicken-smile.md | 5 -- .changeset/yellow-deers-walk.md | 5 -- .changeset/yellow-moles-hammer.md | 5 -- CHANGELOG.md | 81 ++++++++++++++++++- contracts/access/IAccessControl.sol | 2 +- contracts/access/Ownable2Step.sol | 2 +- .../extensions/AccessControlEnumerable.sol | 2 +- .../IAccessControlDefaultAdminRules.sol | 2 +- .../extensions/IAccessControlEnumerable.sol | 2 +- contracts/access/manager/AccessManaged.sol | 2 +- contracts/access/manager/AccessManager.sol | 2 +- contracts/access/manager/IAccessManager.sol | 2 +- contracts/finance/VestingWallet.sol | 2 +- contracts/finance/VestingWalletCliff.sol | 1 + contracts/governance/Governor.sol | 2 +- contracts/governance/IGovernor.sol | 2 +- .../extensions/GovernorCountingFractional.sol | 1 + .../extensions/GovernorCountingSimple.sol | 2 +- .../governance/extensions/GovernorStorage.sol | 2 +- .../extensions/GovernorTimelockAccess.sol | 2 +- .../extensions/GovernorTimelockCompound.sol | 2 +- .../extensions/GovernorTimelockControl.sol | 2 +- .../governance/extensions/GovernorVotes.sol | 2 +- contracts/governance/utils/Votes.sol | 2 +- contracts/interfaces/IERC1271.sol | 2 +- contracts/interfaces/IERC1363.sol | 2 +- contracts/interfaces/IERC1363Receiver.sol | 2 +- contracts/interfaces/IERC1363Spender.sol | 2 +- contracts/interfaces/IERC1820Implementer.sol | 2 +- contracts/interfaces/IERC1820Registry.sol | 2 +- contracts/interfaces/IERC2981.sol | 2 +- .../interfaces/IERC3156FlashBorrower.sol | 2 +- contracts/interfaces/IERC3156FlashLender.sol | 2 +- contracts/interfaces/IERC4626.sol | 2 +- contracts/interfaces/IERC4906.sol | 2 +- contracts/interfaces/IERC777.sol | 2 +- contracts/interfaces/IERC777Recipient.sol | 2 +- contracts/interfaces/IERC777Sender.sol | 2 +- contracts/interfaces/draft-IERC1822.sol | 2 +- contracts/interfaces/draft-IERC6093.sol | 2 +- contracts/interfaces/draft-IERC7674.sol | 1 + contracts/metatx/ERC2771Context.sol | 2 +- contracts/metatx/ERC2771Forwarder.sol | 2 +- contracts/package.json | 2 +- contracts/proxy/Clones.sol | 2 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 2 +- contracts/proxy/ERC1967/ERC1967Utils.sol | 2 +- contracts/proxy/beacon/BeaconProxy.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- .../TransparentUpgradeableProxy.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- contracts/token/ERC1155/ERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155.sol | 2 +- contracts/token/ERC1155/IERC1155Receiver.sol | 2 +- .../ERC1155/extensions/ERC1155Pausable.sol | 2 +- .../ERC1155/extensions/ERC1155Supply.sol | 2 +- .../ERC1155/extensions/ERC1155URIStorage.sol | 2 +- .../extensions/IERC1155MetadataURI.sol | 2 +- .../token/ERC1155/utils/ERC1155Holder.sol | 2 +- .../token/ERC1155/utils/ERC1155Utils.sol | 1 + contracts/token/ERC20/ERC20.sol | 2 +- contracts/token/ERC20/IERC20.sol | 2 +- contracts/token/ERC20/extensions/ERC1363.sol | 1 + .../token/ERC20/extensions/ERC20FlashMint.sol | 2 +- .../token/ERC20/extensions/ERC20Pausable.sol | 2 +- .../token/ERC20/extensions/ERC20Permit.sol | 2 +- .../token/ERC20/extensions/ERC20Votes.sol | 2 +- .../token/ERC20/extensions/ERC20Wrapper.sol | 2 +- contracts/token/ERC20/extensions/ERC4626.sol | 2 +- .../token/ERC20/extensions/IERC20Metadata.sol | 2 +- .../token/ERC20/extensions/IERC20Permit.sol | 2 +- .../draft-ERC20TemporaryApproval.sol | 1 + contracts/token/ERC20/utils/ERC1363Utils.sol | 1 + contracts/token/ERC20/utils/SafeERC20.sol | 2 +- contracts/token/ERC721/ERC721.sol | 2 +- contracts/token/ERC721/IERC721.sol | 2 +- contracts/token/ERC721/IERC721Receiver.sol | 2 +- .../ERC721/extensions/ERC721Burnable.sol | 2 +- .../ERC721/extensions/ERC721Consecutive.sol | 2 +- .../ERC721/extensions/ERC721Enumerable.sol | 2 +- .../ERC721/extensions/ERC721Pausable.sol | 2 +- .../token/ERC721/extensions/ERC721Royalty.sol | 2 +- .../ERC721/extensions/ERC721URIStorage.sol | 2 +- .../token/ERC721/extensions/ERC721Votes.sol | 2 +- .../token/ERC721/extensions/ERC721Wrapper.sol | 2 +- contracts/token/ERC721/utils/ERC721Utils.sol | 1 + contracts/token/common/ERC2981.sol | 2 +- contracts/utils/Address.sol | 2 +- contracts/utils/Arrays.sol | 2 +- contracts/utils/Base64.sol | 2 +- contracts/utils/Comparators.sol | 1 + contracts/utils/Create2.sol | 2 +- contracts/utils/Errors.sol | 1 + contracts/utils/Packing.sol | 1 + contracts/utils/Panic.sol | 1 + contracts/utils/ReentrancyGuard.sol | 2 +- contracts/utils/ReentrancyGuardTransient.sol | 1 + contracts/utils/ShortStrings.sol | 2 +- contracts/utils/SlotDerivation.sol | 1 + contracts/utils/StorageSlot.sol | 2 +- contracts/utils/Strings.sol | 2 +- contracts/utils/TransientSlot.sol | 1 + contracts/utils/cryptography/ECDSA.sol | 2 +- contracts/utils/cryptography/EIP712.sol | 2 +- contracts/utils/cryptography/Hashes.sol | 1 + contracts/utils/cryptography/MerkleProof.sol | 2 +- .../utils/cryptography/MessageHashUtils.sol | 2 +- contracts/utils/cryptography/P256.sol | 1 + contracts/utils/cryptography/RSA.sol | 1 + .../utils/cryptography/SignatureChecker.sol | 2 +- contracts/utils/introspection/ERC165.sol | 2 +- .../utils/introspection/ERC165Checker.sol | 2 +- contracts/utils/introspection/IERC165.sol | 2 +- contracts/utils/math/Math.sol | 2 +- contracts/utils/math/SafeCast.sol | 2 +- contracts/utils/math/SignedMath.sol | 2 +- contracts/utils/structs/Checkpoints.sol | 2 +- contracts/utils/structs/CircularBuffer.sol | 1 + contracts/utils/structs/DoubleEndedQueue.sol | 2 +- contracts/utils/structs/EnumerableMap.sol | 2 +- contracts/utils/structs/EnumerableSet.sol | 2 +- contracts/utils/structs/Heap.sol | 1 + contracts/utils/structs/MerkleTree.sol | 1 + contracts/utils/types/Time.sol | 2 +- package.json | 2 +- 164 files changed, 201 insertions(+), 311 deletions(-) delete mode 100644 .changeset/chilled-walls-develop.md delete mode 100644 .changeset/chilly-humans-warn.md delete mode 100644 .changeset/cold-cheetahs-check.md delete mode 100644 .changeset/cool-mangos-compare.md delete mode 100644 .changeset/curvy-crabs-repeat.md delete mode 100644 .changeset/dirty-cobras-smile.md delete mode 100644 .changeset/eight-eyes-burn.md delete mode 100644 .changeset/eleven-planets-relax.md delete mode 100644 .changeset/flat-turtles-repeat.md delete mode 100644 .changeset/fluffy-buses-jump.md delete mode 100644 .changeset/fluffy-steaks-exist.md delete mode 100644 .changeset/forty-dodos-visit.md delete mode 100644 .changeset/friendly-nails-push.md delete mode 100644 .changeset/gentle-bulldogs-turn.md delete mode 100644 .changeset/gorgeous-badgers-vanish.md delete mode 100644 .changeset/great-pianos-work.md delete mode 100644 .changeset/heavy-baboons-give.md delete mode 100644 .changeset/kind-planets-cough.md delete mode 100644 .changeset/light-news-listen.md delete mode 100644 .changeset/lucky-crews-eat.md delete mode 100644 .changeset/nervous-eyes-teach.md delete mode 100644 .changeset/nervous-pans-grow.md delete mode 100644 .changeset/nice-paws-pull.md delete mode 100644 .changeset/odd-files-protect.md delete mode 100644 .changeset/odd-lobsters-wash.md delete mode 100644 .changeset/poor-chefs-cheat.md delete mode 100644 .changeset/serious-carrots-provide.md delete mode 100644 .changeset/shiny-poets-whisper.md delete mode 100644 .changeset/silver-swans-promise.md delete mode 100644 .changeset/smart-bugs-switch.md delete mode 100644 .changeset/spotty-falcons-explain.md delete mode 100644 .changeset/spotty-queens-own.md delete mode 100644 .changeset/strong-singers-talk.md delete mode 100644 .changeset/thick-pumpkins-report.md delete mode 100644 .changeset/thin-walls-drop.md delete mode 100644 .changeset/twenty-feet-grin.md delete mode 100644 .changeset/violet-moons-tell.md delete mode 100644 .changeset/warm-sheep-cover.md delete mode 100644 .changeset/wise-bobcats-speak.md delete mode 100644 .changeset/witty-chicken-smile.md delete mode 100644 .changeset/yellow-deers-walk.md delete mode 100644 .changeset/yellow-moles-hammer.md diff --git a/.changeset/chilled-walls-develop.md b/.changeset/chilled-walls-develop.md deleted file mode 100644 index 4108feb612d..00000000000 --- a/.changeset/chilled-walls-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Clones`: Add version of `clone` and `cloneDeterministic` that support sending value at creation. diff --git a/.changeset/chilly-humans-warn.md b/.changeset/chilly-humans-warn.md deleted file mode 100644 index 1301dfec416..00000000000 --- a/.changeset/chilly-humans-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`ProxyAdmin`: Fixed documentation for `UPGRADE_INTERFACE_VERSION` getter. diff --git a/.changeset/cold-cheetahs-check.md b/.changeset/cold-cheetahs-check.md deleted file mode 100644 index 0697dcdf7b4..00000000000 --- a/.changeset/cold-cheetahs-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`CircularBuffer`: Add a data structure that stores the last `N` values pushed to it. diff --git a/.changeset/cool-mangos-compare.md b/.changeset/cool-mangos-compare.md deleted file mode 100644 index 470ee089456..00000000000 --- a/.changeset/cool-mangos-compare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`: add an `invMod` function to get the modular multiplicative inverse of a number in Z/nZ. diff --git a/.changeset/curvy-crabs-repeat.md b/.changeset/curvy-crabs-repeat.md deleted file mode 100644 index db3ef275bb3..00000000000 --- a/.changeset/curvy-crabs-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`RSA`: Library to verify signatures according to RFC 8017 Signature Verification Operation diff --git a/.changeset/dirty-cobras-smile.md b/.changeset/dirty-cobras-smile.md deleted file mode 100644 index d71194cfc21..00000000000 --- a/.changeset/dirty-cobras-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Arrays`: add a `sort` functions for `address[]`, `bytes32[]` and `uint256[]` memory arrays. diff --git a/.changeset/eight-eyes-burn.md b/.changeset/eight-eyes-burn.md deleted file mode 100644 index 908c90c7bbf..00000000000 --- a/.changeset/eight-eyes-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`GovernorCountingFractional`: Add a governor counting module that allows distributing voting power amongst 3 options (For, Against, Abstain). diff --git a/.changeset/eleven-planets-relax.md b/.changeset/eleven-planets-relax.md deleted file mode 100644 index a1f1bbf1c4e..00000000000 --- a/.changeset/eleven-planets-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`TransparentUpgradeableProxy`: Make internal `_proxyAdmin()` getter have `view` visibility. diff --git a/.changeset/flat-turtles-repeat.md b/.changeset/flat-turtles-repeat.md deleted file mode 100644 index 6b627201ac9..00000000000 --- a/.changeset/flat-turtles-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Arrays`: deprecate `findUpperBound` in favor of the new `lowerBound`. diff --git a/.changeset/fluffy-buses-jump.md b/.changeset/fluffy-buses-jump.md deleted file mode 100644 index 0525a4d8e43..00000000000 --- a/.changeset/fluffy-buses-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Comparator`: A library of comparator functions, useful for customizing the behavior of the Heap structure. diff --git a/.changeset/fluffy-steaks-exist.md b/.changeset/fluffy-steaks-exist.md deleted file mode 100644 index b625e243481..00000000000 --- a/.changeset/fluffy-steaks-exist.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`Create2`, `Clones`: Mask `computeAddress` and `cloneDeterministic` outputs to produce a clean value for an `address` type (i.e. only use 20 bytes) diff --git a/.changeset/forty-dodos-visit.md b/.changeset/forty-dodos-visit.md deleted file mode 100644 index 7d5ae747335..00000000000 --- a/.changeset/forty-dodos-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Strings`: Added a utility function for converting an address to checksummed string. diff --git a/.changeset/friendly-nails-push.md b/.changeset/friendly-nails-push.md deleted file mode 100644 index 157bf05561a..00000000000 --- a/.changeset/friendly-nails-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals. diff --git a/.changeset/gentle-bulldogs-turn.md b/.changeset/gentle-bulldogs-turn.md deleted file mode 100644 index 12bc87a2dfb..00000000000 --- a/.changeset/gentle-bulldogs-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`DoubleEndedQueue`: Custom errors replaced with native panic codes. diff --git a/.changeset/gorgeous-badgers-vanish.md b/.changeset/gorgeous-badgers-vanish.md deleted file mode 100644 index ce75ed6ebae..00000000000 --- a/.changeset/gorgeous-badgers-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SlotDerivation`: Add a library of methods for derivating common storage slots. diff --git a/.changeset/great-pianos-work.md b/.changeset/great-pianos-work.md deleted file mode 100644 index da54483e47e..00000000000 --- a/.changeset/great-pianos-work.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Heap`: A data structure that implements a heap-based priority queue. diff --git a/.changeset/heavy-baboons-give.md b/.changeset/heavy-baboons-give.md deleted file mode 100644 index 5852748f81c..00000000000 --- a/.changeset/heavy-baboons-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Packing`: Added a new utility for packing, extracting and replacing bytesXX values. diff --git a/.changeset/kind-planets-cough.md b/.changeset/kind-planets-cough.md deleted file mode 100644 index 988e24c4ad2..00000000000 --- a/.changeset/kind-planets-cough.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`StorageSlot`: Add primitives for operating on the transient storage space using a typed-slot representation. diff --git a/.changeset/light-news-listen.md b/.changeset/light-news-listen.md deleted file mode 100644 index 1572d908139..00000000000 --- a/.changeset/light-news-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`AccessManager`: Allow the `onlyAuthorized` modifier to restrict functions added to the manager. diff --git a/.changeset/lucky-crews-eat.md b/.changeset/lucky-crews-eat.md deleted file mode 100644 index 48592b5eaf1..00000000000 --- a/.changeset/lucky-crews-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Votes`: Set `_moveDelegateVotes` visibility to internal instead of private. diff --git a/.changeset/nervous-eyes-teach.md b/.changeset/nervous-eyes-teach.md deleted file mode 100644 index f85bc66d8f7..00000000000 --- a/.changeset/nervous-eyes-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Create2`: Bubbles up returndata from a deployed contract that reverted during construction. diff --git a/.changeset/nervous-pans-grow.md b/.changeset/nervous-pans-grow.md deleted file mode 100644 index b86a075c678..00000000000 --- a/.changeset/nervous-pans-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': patch ---- - -`SafeCast`: Add `toUint(bool)` for operating on `bool` values as `uint256`. diff --git a/.changeset/nice-paws-pull.md b/.changeset/nice-paws-pull.md deleted file mode 100644 index 11f48d51f82..00000000000 --- a/.changeset/nice-paws-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs. diff --git a/.changeset/odd-files-protect.md b/.changeset/odd-files-protect.md deleted file mode 100644 index 8b334acfd11..00000000000 --- a/.changeset/odd-files-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Hashes`: A library with commonly used hash functions. diff --git a/.changeset/odd-lobsters-wash.md b/.changeset/odd-lobsters-wash.md deleted file mode 100644 index 578f7a42ea7..00000000000 --- a/.changeset/odd-lobsters-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`P256`: Library for verification and public key recovery of P256 (aka secp256r1) signatures. diff --git a/.changeset/poor-chefs-cheat.md b/.changeset/poor-chefs-cheat.md deleted file mode 100644 index 39db3d5139c..00000000000 --- a/.changeset/poor-chefs-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers. diff --git a/.changeset/serious-carrots-provide.md b/.changeset/serious-carrots-provide.md deleted file mode 100644 index 60a16580d8a..00000000000 --- a/.changeset/serious-carrots-provide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ERC20TemporaryApproval`: Add an ERC-20 extension that implements temporary approval using transient storage, based on ERC7674 (draft). diff --git a/.changeset/shiny-poets-whisper.md b/.changeset/shiny-poets-whisper.md deleted file mode 100644 index 92497033acf..00000000000 --- a/.changeset/shiny-poets-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`: Add `modExp` function that exposes the `EIP-198` precompile. Includes `uint256` and `bytes memory` versions. diff --git a/.changeset/silver-swans-promise.md b/.changeset/silver-swans-promise.md deleted file mode 100644 index 1d2ff2e9ef8..00000000000 --- a/.changeset/silver-swans-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Panic`: Add a library for reverting with panic codes. diff --git a/.changeset/smart-bugs-switch.md b/.changeset/smart-bugs-switch.md deleted file mode 100644 index 8a001ae58a1..00000000000 --- a/.changeset/smart-bugs-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`: Custom errors replaced with native panic codes. diff --git a/.changeset/spotty-falcons-explain.md b/.changeset/spotty-falcons-explain.md deleted file mode 100644 index 28cb95190c1..00000000000 --- a/.changeset/spotty-falcons-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost. diff --git a/.changeset/spotty-queens-own.md b/.changeset/spotty-queens-own.md deleted file mode 100644 index 98fb2fbc0ed..00000000000 --- a/.changeset/spotty-queens-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`MerkleProof`: Add variations of `verify`, `processProof`, `multiProofVerify` and `processMultiProof` (and equivalent calldata version) with support for custom hashing functions. diff --git a/.changeset/strong-singers-talk.md b/.changeset/strong-singers-talk.md deleted file mode 100644 index 7897980cbae..00000000000 --- a/.changeset/strong-singers-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Errors`: New library of common custom errors. diff --git a/.changeset/thick-pumpkins-report.md b/.changeset/thick-pumpkins-report.md deleted file mode 100644 index f17a208950c..00000000000 --- a/.changeset/thick-pumpkins-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Arrays`: add new functions `lowerBound`, `upperBound`, `lowerBoundMemory` and `upperBoundMemory` for lookups in sorted arrays with potential duplicates. diff --git a/.changeset/thin-walls-drop.md b/.changeset/thin-walls-drop.md deleted file mode 100644 index 80260020256..00000000000 --- a/.changeset/thin-walls-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`AccessManager`, `VestingWallet`, `TimelockController` and `ERC2771Forwarder`: Added a public `initializer` function in their corresponding upgradeable variants. diff --git a/.changeset/twenty-feet-grin.md b/.changeset/twenty-feet-grin.md deleted file mode 100644 index 69b4fe63b2e..00000000000 --- a/.changeset/twenty-feet-grin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Base64`: Add `encodeURL` following section 5 of RFC4648 for URL encoding diff --git a/.changeset/violet-moons-tell.md b/.changeset/violet-moons-tell.md deleted file mode 100644 index be215e1934b..00000000000 --- a/.changeset/violet-moons-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`AccessControlEnumerable`: Add a `getRoleMembers` method to return all accounts that have `role`. diff --git a/.changeset/warm-sheep-cover.md b/.changeset/warm-sheep-cover.md deleted file mode 100644 index f0a2ebaa256..00000000000 --- a/.changeset/warm-sheep-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash. diff --git a/.changeset/wise-bobcats-speak.md b/.changeset/wise-bobcats-speak.md deleted file mode 100644 index 6ecd9695723..00000000000 --- a/.changeset/wise-bobcats-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`VestingWalletCliff`: Add an extension of the `VestingWallet` contract with an added cliff. diff --git a/.changeset/witty-chicken-smile.md b/.changeset/witty-chicken-smile.md deleted file mode 100644 index 6fae3e744ad..00000000000 --- a/.changeset/witty-chicken-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`ReentrancyGuardTransient`: Added a variant of `ReentrancyGuard` that uses transient storage. diff --git a/.changeset/yellow-deers-walk.md b/.changeset/yellow-deers-walk.md deleted file mode 100644 index ad370b36e58..00000000000 --- a/.changeset/yellow-deers-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`EnumerableMap`: add `UintToBytes32Map`, `AddressToAddressMap`, `AddressToBytes32Map` and `Bytes32ToAddressMap`. diff --git a/.changeset/yellow-moles-hammer.md b/.changeset/yellow-moles-hammer.md deleted file mode 100644 index b13971a28a9..00000000000 --- a/.changeset/yellow-moles-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SignatureChecker`: refactor `isValidSignatureNow` to avoid validating ECDSA signatures if there is code deployed at the signer's address. diff --git a/CHANGELOG.md b/CHANGELOG.md index b64fabab220..cc52db338ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ # Changelog + +## 5.1.0 (2024-10-17) + ### Breaking changes - `ERC1967Utils`: Removed duplicate declaration of the `Upgraded`, `AdminChanged` and `BeaconUpgraded` events. These events are still available through the `IERC1967` interface located under the `contracts/interfaces/` directory. Minimum pragma version is now 0.8.21. - `Governor`, `GovernorCountingSimple`: The `_countVote` virtual function now returns an `uint256` with the total votes casted. This change allows for more flexibility for partial and fractional voting. Upgrading users may get a compilation error that can be fixed by adding a return statement to the `_countVote` function. -### Custom error changes +#### Custom error changes This version comes with changes to the custom error identifiers. Contracts previously depending on the following errors should be replaced accordingly: @@ -18,6 +21,82 @@ This version comes with changes to the custom error identifiers. Contracts previ - `SafeERC20`: Replace generic `Error(string)` with `SafeERC20FailedOperation` if the returned data can't be decoded as `bool`. - `SafeERC20`: Replace generic `SafeERC20FailedOperation` with the revert message from the contract call if it fails. +### Changes by category + +#### General + +- `AccessManager`, `VestingWallet`, `TimelockController` and `ERC2771Forwarder`: Added a public `initializer` function in their corresponding upgradeable variants. ([#5008](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5008)) + +#### Access + +- `AccessControlEnumerable`: Add a `getRoleMembers` method to return all accounts that have `role`. ([#4546](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4546)) +- `AccessManager`: Allow the `onlyAuthorized` modifier to restrict functions added to the manager. ([#5014](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5014)) + +#### Finance + +- `VestingWalletCliff`: Add an extension of the `VestingWallet` contract with an added cliff. ([#4870](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4870)) + +#### Governance + +- `GovernorCountingFractional`: Add a governor counting module that allows distributing voting power amongst 3 options (For, Against, Abstain). ([#5045](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5045)) +- `Votes`: Set `_moveDelegateVotes` visibility to internal instead of private. ([#5007](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5007)) + +#### Proxy + +- `Clones`: Add version of `clone` and `cloneDeterministic` that support sending value at creation. ([#4936](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4936)) +- `TransparentUpgradeableProxy`: Make internal `_proxyAdmin()` getter have `view` visibility. ([#4688](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4688)) +- `ProxyAdmin`: Fixed documentation for `UPGRADE_INTERFACE_VERSION` getter. ([#5031](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5031)) + +#### Tokens + +- `ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) +- `ERC20TemporaryApproval`: Add an ERC-20 extension that implements temporary approval using transient storage, based on ERC7674 (draft). ([#5071](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5071)) +- `SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) +- `SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. ([#5262](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5262)) +- `ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers. ([#4845](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4845)) +- `ERC1363Utils`: Add helper similar to the existing ERC721Utils and ERC1155Utils. ([#5133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5133)) + +#### Utils + +- `Arrays`: add a `sort` functions for `address[]`, `bytes32[]` and `uint256[]` memory arrays. ([#4846](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4846)) +- `Arrays`: add new functions `lowerBound`, `upperBound`, `lowerBoundMemory` and `upperBoundMemory` for lookups in sorted arrays with potential duplicates. ([#4842](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4842)) +- `Arrays`: deprecate `findUpperBound` in favor of the new `lowerBound`. ([#4842](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4842)) +- `Base64`: Add `encodeURL` following section 5 of RFC4648 for URL encoding ([#4822](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4822)) +- `Comparator`: A library of comparator functions, useful for customizing the behavior of the Heap structure. ([#5084](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5084)) +- `Create2`: Bubbles up returndata from a deployed contract that reverted during construction. ([#5052](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5052)) +- `Create2`, `Clones`: Mask `computeAddress` and `cloneDeterministic` outputs to produce a clean value for an `address` type (i.e. only use 20 bytes) ([#4941](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4941)) +- `Errors`: New library of common custom errors. ([#4936](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4936)) +- `Hashes`: A library with commonly used hash functions. ([#3617](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3617)) +- `Packing`: Added a new utility for packing, extracting and replacing bytesXX values. ([#4992](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4992)) +- `Panic`: Add a library for reverting with panic codes. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `ReentrancyGuardTransient`: Added a variant of `ReentrancyGuard` that uses transient storage. ([#4988](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4988)) +- `Strings`: Added a utility function for converting an address to checksummed string. ([#5067](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5067)) +- `SlotDerivation`: Add a library of methods for derivating common storage slots. ([#4975](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4975)) +- `TransientSlot`: Add primitives for operating on the transient storage space using a typed-slot representation. ([#4980](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4980)) + +##### Cryptography + +- `SignatureChecker`: refactor `isValidSignatureNow` to avoid validating ECDSA signatures if there is code deployed at the signer's address. ([#4951](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4951)) +- `MerkleProof`: Add variations of `verify`, `processProof`, `multiProofVerify` and `processMultiProof` (and equivalent calldata version) with support for custom hashing functions. ([#4887](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4887)) +- `P256`: Library for verification and public key recovery of P256 (aka secp256r1) signatures. ([#4881](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4881)) +- `RSA`: Library to verify signatures according to RFC 8017 Signature Verification Operation ([#4952](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4952)) + +#### Math + +- `Math`: add an `invMod` function to get the modular multiplicative inverse of a number in Z/nZ. ([#4839](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4839)) +- `Math`: Add `modExp` function that exposes the `EIP-198` precompile. Includes `uint256` and `bytes memory` versions. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `Math`: Custom errors replaced with native panic codes. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost. ([#4976](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4976)) +- `SafeCast`: Add `toUint(bool)` for operating on `bool` values as `uint256`. ([#4878](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4878)) + +#### Structures + +- `CircularBuffer`: Add a data structure that stores the last `N` values pushed to it. ([#4913](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4913)) +- `DoubleEndedQueue`: Custom errors replaced with native panic codes. ([#4872](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4872)) +- `EnumerableMap`: add `UintToBytes32Map`, `AddressToAddressMap`, `AddressToBytes32Map` and `Bytes32ToAddressMap`. ([#4843](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4843)) +- `Heap`: A data structure that implements a heap-based priority queue. ([#5084](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5084)) +- `MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash. ([#3617](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3617)) + ## 5.0.2 (2024-02-29) - `Base64`: Fix issue where dirty memory located just after the input buffer is affecting the result. ([#4926](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4926)) diff --git a/contracts/access/IAccessControl.sol b/contracts/access/IAccessControl.sol index 67ea537ed8d..4c16a6ef75c 100644 --- a/contracts/access/IAccessControl.sol +++ b/contracts/access/IAccessControl.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/IAccessControl.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/Ownable2Step.sol b/contracts/access/Ownable2Step.sol index 8050ff2b030..3a0747ce7cd 100644 --- a/contracts/access/Ownable2Step.sol +++ b/contracts/access/Ownable2Step.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/extensions/AccessControlEnumerable.sol b/contracts/access/extensions/AccessControlEnumerable.sol index 222490c9b30..b1980e364b0 100644 --- a/contracts/access/extensions/AccessControlEnumerable.sol +++ b/contracts/access/extensions/AccessControlEnumerable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/AccessControlEnumerable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/AccessControlEnumerable.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/extensions/IAccessControlDefaultAdminRules.sol b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol index b74355aad31..3740749c4b8 100644 --- a/contracts/access/extensions/IAccessControlDefaultAdminRules.sol +++ b/contracts/access/extensions/IAccessControlDefaultAdminRules.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlDefaultAdminRules.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/IAccessControlDefaultAdminRules.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/extensions/IAccessControlEnumerable.sol b/contracts/access/extensions/IAccessControlEnumerable.sol index 11429686355..bb9bac86ee1 100644 --- a/contracts/access/extensions/IAccessControlEnumerable.sol +++ b/contracts/access/extensions/IAccessControlEnumerable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/extensions/IAccessControlEnumerable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/extensions/IAccessControlEnumerable.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/manager/AccessManaged.sol b/contracts/access/manager/AccessManaged.sol index 41b21b0b79d..352a54def2e 100644 --- a/contracts/access/manager/AccessManaged.sol +++ b/contracts/access/manager/AccessManaged.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AccessManaged.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/manager/AccessManaged.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/manager/AccessManager.sol b/contracts/access/manager/AccessManager.sol index a6e2d7aed0e..051080554c6 100644 --- a/contracts/access/manager/AccessManager.sol +++ b/contracts/access/manager/AccessManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/AccessManager.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/manager/AccessManager.sol) pragma solidity ^0.8.20; diff --git a/contracts/access/manager/IAccessManager.sol b/contracts/access/manager/IAccessManager.sol index 2811281a319..ebcd1d614a1 100644 --- a/contracts/access/manager/IAccessManager.sol +++ b/contracts/access/manager/IAccessManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (access/manager/IAccessManager.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (access/manager/IAccessManager.sol) pragma solidity ^0.8.20; diff --git a/contracts/finance/VestingWallet.sol b/contracts/finance/VestingWallet.sol index 153b8fc63de..0e0321d0da2 100644 --- a/contracts/finance/VestingWallet.sol +++ b/contracts/finance/VestingWallet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (finance/VestingWallet.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (finance/VestingWallet.sol) pragma solidity ^0.8.20; import {IERC20} from "../token/ERC20/IERC20.sol"; diff --git a/contracts/finance/VestingWalletCliff.sol b/contracts/finance/VestingWalletCliff.sol index 83d82510427..dd1da6580bd 100644 --- a/contracts/finance/VestingWalletCliff.sol +++ b/contracts/finance/VestingWalletCliff.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (finance/VestingWalletCliff.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index f1851b30ee6..2add7c759fb 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/Governor.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/Governor.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/IGovernor.sol b/contracts/governance/IGovernor.sol index 67abfdbbae4..28f8aaac044 100644 --- a/contracts/governance/IGovernor.sol +++ b/contracts/governance/IGovernor.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/IGovernor.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/IGovernor.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorCountingFractional.sol b/contracts/governance/extensions/GovernorCountingFractional.sol index a3f40201fd1..d2231bb9529 100644 --- a/contracts/governance/extensions/GovernorCountingFractional.sol +++ b/contracts/governance/extensions/GovernorCountingFractional.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorCountingFractional.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorCountingSimple.sol b/contracts/governance/extensions/GovernorCountingSimple.sol index def29e34e61..0b89b24381d 100644 --- a/contracts/governance/extensions/GovernorCountingSimple.sol +++ b/contracts/governance/extensions/GovernorCountingSimple.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorCountingSimple.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorCountingSimple.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorStorage.sol b/contracts/governance/extensions/GovernorStorage.sol index 40f7b23d5ac..22db099924a 100644 --- a/contracts/governance/extensions/GovernorStorage.sol +++ b/contracts/governance/extensions/GovernorStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorStorage.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorStorage.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorTimelockAccess.sol b/contracts/governance/extensions/GovernorTimelockAccess.sol index 6e2c5b84e3d..5b8429b8320 100644 --- a/contracts/governance/extensions/GovernorTimelockAccess.sol +++ b/contracts/governance/extensions/GovernorTimelockAccess.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockAccess.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorTimelockAccess.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorTimelockCompound.sol b/contracts/governance/extensions/GovernorTimelockCompound.sol index 77cf369ff49..309f9a4fa76 100644 --- a/contracts/governance/extensions/GovernorTimelockCompound.sol +++ b/contracts/governance/extensions/GovernorTimelockCompound.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockCompound.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorTimelockCompound.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorTimelockControl.sol b/contracts/governance/extensions/GovernorTimelockControl.sol index 52d4b429598..ba0953d1608 100644 --- a/contracts/governance/extensions/GovernorTimelockControl.sol +++ b/contracts/governance/extensions/GovernorTimelockControl.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorTimelockControl.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorTimelockControl.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/extensions/GovernorVotes.sol b/contracts/governance/extensions/GovernorVotes.sol index 16cd934355e..9aeaf1214a1 100644 --- a/contracts/governance/extensions/GovernorVotes.sol +++ b/contracts/governance/extensions/GovernorVotes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/extensions/GovernorVotes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/extensions/GovernorVotes.sol) pragma solidity ^0.8.20; diff --git a/contracts/governance/utils/Votes.sol b/contracts/governance/utils/Votes.sol index 9c3262b9789..bbbc2264ff9 100644 --- a/contracts/governance/utils/Votes.sol +++ b/contracts/governance/utils/Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (governance/utils/Votes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (governance/utils/Votes.sol) pragma solidity ^0.8.20; import {IERC5805} from "../../interfaces/IERC5805.sol"; diff --git a/contracts/interfaces/IERC1271.sol b/contracts/interfaces/IERC1271.sol index 5f5b326336e..8c239942ac8 100644 --- a/contracts/interfaces/IERC1271.sol +++ b/contracts/interfaces/IERC1271.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1271.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1271.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1363.sol b/contracts/interfaces/IERC1363.sol index a5246da37ca..02de2285997 100644 --- a/contracts/interfaces/IERC1363.sol +++ b/contracts/interfaces/IERC1363.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1363Receiver.sol b/contracts/interfaces/IERC1363Receiver.sol index 279178849d6..02c065861cc 100644 --- a/contracts/interfaces/IERC1363Receiver.sol +++ b/contracts/interfaces/IERC1363Receiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363Receiver.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363Receiver.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1363Spender.sol b/contracts/interfaces/IERC1363Spender.sol index 4e9aba7d7f7..13af938f066 100644 --- a/contracts/interfaces/IERC1363Spender.sol +++ b/contracts/interfaces/IERC1363Spender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1363Spender.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363Spender.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1820Implementer.sol b/contracts/interfaces/IERC1820Implementer.sol index 9cf941a3343..95289c65cf4 100644 --- a/contracts/interfaces/IERC1820Implementer.sol +++ b/contracts/interfaces/IERC1820Implementer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1820Implementer.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1820Implementer.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC1820Registry.sol b/contracts/interfaces/IERC1820Registry.sol index b8f3d73998c..fa70466114d 100644 --- a/contracts/interfaces/IERC1820Registry.sol +++ b/contracts/interfaces/IERC1820Registry.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1820Registry.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1820Registry.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC2981.sol b/contracts/interfaces/IERC2981.sol index 22b951dd84e..db5eb5cd446 100644 --- a/contracts/interfaces/IERC2981.sol +++ b/contracts/interfaces/IERC2981.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2981.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC2981.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC3156FlashBorrower.sol b/contracts/interfaces/IERC3156FlashBorrower.sol index 4fd10e7cfea..daafb17ee05 100644 --- a/contracts/interfaces/IERC3156FlashBorrower.sol +++ b/contracts/interfaces/IERC3156FlashBorrower.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashBorrower.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC3156FlashBorrower.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC3156FlashLender.sol b/contracts/interfaces/IERC3156FlashLender.sol index 47208ac3112..7b1b071d4df 100644 --- a/contracts/interfaces/IERC3156FlashLender.sol +++ b/contracts/interfaces/IERC3156FlashLender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC3156FlashLender.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC3156FlashLender.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC4626.sol b/contracts/interfaces/IERC4626.sol index 9a24507a7fd..8ebadd72fcf 100644 --- a/contracts/interfaces/IERC4626.sol +++ b/contracts/interfaces/IERC4626.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4626.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC4906.sol b/contracts/interfaces/IERC4906.sol index cdcace31fcb..6ecd061347b 100644 --- a/contracts/interfaces/IERC4906.sol +++ b/contracts/interfaces/IERC4906.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4906.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC4906.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC777.sol b/contracts/interfaces/IERC777.sol index 31f05aa6421..1e672330a41 100644 --- a/contracts/interfaces/IERC777.sol +++ b/contracts/interfaces/IERC777.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC777.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC777Recipient.sol b/contracts/interfaces/IERC777Recipient.sol index 1619e112679..c377de971fc 100644 --- a/contracts/interfaces/IERC777Recipient.sol +++ b/contracts/interfaces/IERC777Recipient.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777Recipient.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC777Recipient.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/IERC777Sender.sol b/contracts/interfaces/IERC777Sender.sol index f47a7832399..0ec8c278484 100644 --- a/contracts/interfaces/IERC777Sender.sol +++ b/contracts/interfaces/IERC777Sender.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC777Sender.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC777Sender.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/draft-IERC1822.sol b/contracts/interfaces/draft-IERC1822.sol index ad047dae600..f846ea6bf0e 100644 --- a/contracts/interfaces/draft-IERC1822.sol +++ b/contracts/interfaces/draft-IERC1822.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC1822.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC1822.sol) pragma solidity ^0.8.20; diff --git a/contracts/interfaces/draft-IERC6093.sol b/contracts/interfaces/draft-IERC6093.sol index 75fd75643e6..3227fd624fc 100644 --- a/contracts/interfaces/draft-IERC6093.sol +++ b/contracts/interfaces/draft-IERC6093.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol) pragma solidity ^0.8.20; /** diff --git a/contracts/interfaces/draft-IERC7674.sol b/contracts/interfaces/draft-IERC7674.sol index 949ec806e84..be3c413ec36 100644 --- a/contracts/interfaces/draft-IERC7674.sol +++ b/contracts/interfaces/draft-IERC7674.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC7674.sol) pragma solidity ^0.8.20; diff --git a/contracts/metatx/ERC2771Context.sol b/contracts/metatx/ERC2771Context.sol index d448b24b128..794bfb3e5ef 100644 --- a/contracts/metatx/ERC2771Context.sol +++ b/contracts/metatx/ERC2771Context.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.1) (metatx/ERC2771Context.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (metatx/ERC2771Context.sol) pragma solidity ^0.8.20; diff --git a/contracts/metatx/ERC2771Forwarder.sol b/contracts/metatx/ERC2771Forwarder.sol index 0b0d185d87e..4a069874ecb 100644 --- a/contracts/metatx/ERC2771Forwarder.sol +++ b/contracts/metatx/ERC2771Forwarder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (metatx/ERC2771Forwarder.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (metatx/ERC2771Forwarder.sol) pragma solidity ^0.8.20; diff --git a/contracts/package.json b/contracts/package.json index 845e8c4035d..e0ed163d05a 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,7 +1,7 @@ { "name": "@openzeppelin/contracts", "description": "Secure Smart Contract library for Solidity", - "version": "5.0.2", + "version": "5.1.0", "files": [ "**/*.sol", "/build/contracts/*.json", diff --git a/contracts/proxy/Clones.sol b/contracts/proxy/Clones.sol index 454d7fb97d2..99e25ab46db 100644 --- a/contracts/proxy/Clones.sol +++ b/contracts/proxy/Clones.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/Clones.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 8f6b717a56f..4f51cd9578b 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Proxy.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index cf555098af9..1f320135277 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Utils.sol) pragma solidity ^0.8.21; diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 9b3f627b19a..2606f21db08 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/BeaconProxy.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/beacon/BeaconProxy.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 7fbdca6fd7e..31772350392 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/ProxyAdmin.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index 5304f4311bc..a35a725f2b3 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/TransparentUpgradeableProxy.sol) pragma solidity ^0.8.20; diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index 20eb1f72604..dc799962cb3 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/UUPSUpgradeable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index b0e7e547ab2..3e0a91f8bcc 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/ERC1155.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/ERC1155.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 5a1805fadee..0da320fbefb 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.1) (token/ERC1155/IERC1155.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/IERC1155Receiver.sol b/contracts/token/ERC1155/IERC1155Receiver.sol index 36ad4c75206..7d9bc239719 100644 --- a/contracts/token/ERC1155/IERC1155Receiver.sol +++ b/contracts/token/ERC1155/IERC1155Receiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155Receiver.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol index e99cf2aa95d..a0de999f0cf 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Pausable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155Pausable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155Pausable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/ERC1155Supply.sol b/contracts/token/ERC1155/extensions/ERC1155Supply.sol index 33c5b103a95..00dd082a383 100644 --- a/contracts/token/ERC1155/extensions/ERC1155Supply.sol +++ b/contracts/token/ERC1155/extensions/ERC1155Supply.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155Supply.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155Supply.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol index e8436e830d7..5abf319d327 100644 --- a/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +++ b/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/ERC1155URIStorage.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol index ea07897b9f2..b413f4304d3 100644 --- a/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +++ b/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/extensions/IERC1155MetadataURI.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/extensions/IERC1155MetadataURI.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/utils/ERC1155Holder.sol b/contracts/token/ERC1155/utils/ERC1155Holder.sol index 35be58c5238..7ad5943aceb 100644 --- a/contracts/token/ERC1155/utils/ERC1155Holder.sol +++ b/contracts/token/ERC1155/utils/ERC1155Holder.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Holder.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC1155/utils/ERC1155Utils.sol b/contracts/token/ERC1155/utils/ERC1155Utils.sol index 62accf6bc6e..371cd86ba46 100644 --- a/contracts/token/ERC1155/utils/ERC1155Utils.sol +++ b/contracts/token/ERC1155/utils/ERC1155Utils.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/utils/ERC1155Utils.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 9885eaac996..0b707604c0c 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/IERC20.sol b/contracts/token/ERC20/IERC20.sol index d8907216425..7d1019563f5 100644 --- a/contracts/token/ERC20/IERC20.sol +++ b/contracts/token/ERC20/IERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC1363.sol b/contracts/token/ERC20/extensions/ERC1363.sol index 4ae78f32764..acc841d78fc 100644 --- a/contracts/token/ERC20/extensions/ERC1363.sol +++ b/contracts/token/ERC20/extensions/ERC1363.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC1363.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20FlashMint.sol b/contracts/token/ERC20/extensions/ERC20FlashMint.sol index fd8ff0b3ece..4d3a31f6df2 100644 --- a/contracts/token/ERC20/extensions/ERC20FlashMint.sol +++ b/contracts/token/ERC20/extensions/ERC20FlashMint.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20FlashMint.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20FlashMint.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Pausable.sol b/contracts/token/ERC20/extensions/ERC20Pausable.sol index 9b56f053566..2f6d86c4a53 100644 --- a/contracts/token/ERC20/extensions/ERC20Pausable.sol +++ b/contracts/token/ERC20/extensions/ERC20Pausable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Pausable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Pausable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Permit.sol b/contracts/token/ERC20/extensions/ERC20Permit.sol index 22b19dc0bb7..3d36561a85f 100644 --- a/contracts/token/ERC20/extensions/ERC20Permit.sol +++ b/contracts/token/ERC20/extensions/ERC20Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Permit.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Votes.sol b/contracts/token/ERC20/extensions/ERC20Votes.sol index 7680293a7cb..c15e7f56885 100644 --- a/contracts/token/ERC20/extensions/ERC20Votes.sol +++ b/contracts/token/ERC20/extensions/ERC20Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Votes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Votes.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC20Wrapper.sol b/contracts/token/ERC20/extensions/ERC20Wrapper.sol index eecd6ab7a63..9cc5aaf5f68 100644 --- a/contracts/token/ERC20/extensions/ERC20Wrapper.sol +++ b/contracts/token/ERC20/extensions/ERC20Wrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Wrapper.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Wrapper.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 73778b530ca..ec9a255076c 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC4626.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC4626.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/IERC20Metadata.sol b/contracts/token/ERC20/extensions/IERC20Metadata.sol index e8c020b1fcd..3c067ef4012 100644 --- a/contracts/token/ERC20/extensions/IERC20Metadata.sol +++ b/contracts/token/ERC20/extensions/IERC20Metadata.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/IERC20Permit.sol b/contracts/token/ERC20/extensions/IERC20Permit.sol index a8ad26ede5a..fc374368fd2 100644 --- a/contracts/token/ERC20/extensions/IERC20Permit.sol +++ b/contracts/token/ERC20/extensions/IERC20Permit.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol index c5a29685c7f..d30521b49bb 100644 --- a/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol +++ b/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/draft-ERC20TemporaryApproval.sol) pragma solidity ^0.8.24; diff --git a/contracts/token/ERC20/utils/ERC1363Utils.sol b/contracts/token/ERC20/utils/ERC1363Utils.sol index f5c931361f7..6ba26901eb0 100644 --- a/contracts/token/ERC20/utils/ERC1363Utils.sol +++ b/contracts/token/ERC20/utils/ERC1363Utils.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/ERC1363Utils.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC20/utils/SafeERC20.sol b/contracts/token/ERC20/utils/SafeERC20.sol index 19c2f3e19e8..eb2f903fbed 100644 --- a/contracts/token/ERC20/utils/SafeERC20.sol +++ b/contracts/token/ERC20/utils/SafeERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol index df7ff50575d..6aebc37309f 100644 --- a/contracts/token/ERC721/ERC721.sol +++ b/contracts/token/ERC721/ERC721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/ERC721.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/ERC721.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/IERC721.sol b/contracts/token/ERC721/IERC721.sol index d6ab6a47d2a..da393014753 100644 --- a/contracts/token/ERC721/IERC721.sol +++ b/contracts/token/ERC721/IERC721.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/IERC721Receiver.sol b/contracts/token/ERC721/IERC721Receiver.sol index a53d0777479..d472eec338a 100644 --- a/contracts/token/ERC721/IERC721Receiver.sol +++ b/contracts/token/ERC721/IERC721Receiver.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721Receiver.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Burnable.sol b/contracts/token/ERC721/extensions/ERC721Burnable.sol index 65cfc744cba..c6d22455761 100644 --- a/contracts/token/ERC721/extensions/ERC721Burnable.sol +++ b/contracts/token/ERC721/extensions/ERC721Burnable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Burnable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Burnable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Consecutive.sol b/contracts/token/ERC721/extensions/ERC721Consecutive.sol index 8508a79f4fc..6b849d77438 100644 --- a/contracts/token/ERC721/extensions/ERC721Consecutive.sol +++ b/contracts/token/ERC721/extensions/ERC721Consecutive.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Consecutive.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Consecutive.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Enumerable.sol b/contracts/token/ERC721/extensions/ERC721Enumerable.sol index 1bd4375290d..43aa81e6e09 100644 --- a/contracts/token/ERC721/extensions/ERC721Enumerable.sol +++ b/contracts/token/ERC721/extensions/ERC721Enumerable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Enumerable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Enumerable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Pausable.sol b/contracts/token/ERC721/extensions/ERC721Pausable.sol index 81619c7f5dc..9a75623c62a 100644 --- a/contracts/token/ERC721/extensions/ERC721Pausable.sol +++ b/contracts/token/ERC721/extensions/ERC721Pausable.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Pausable.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Pausable.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Royalty.sol b/contracts/token/ERC721/extensions/ERC721Royalty.sol index 1e0b25af43b..cfce1786c7a 100644 --- a/contracts/token/ERC721/extensions/ERC721Royalty.sol +++ b/contracts/token/ERC721/extensions/ERC721Royalty.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Royalty.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Royalty.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721URIStorage.sol b/contracts/token/ERC721/extensions/ERC721URIStorage.sol index 562f815c0b8..d8b4d8d1f91 100644 --- a/contracts/token/ERC721/extensions/ERC721URIStorage.sol +++ b/contracts/token/ERC721/extensions/ERC721URIStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721URIStorage.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721URIStorage.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Votes.sol b/contracts/token/ERC721/extensions/ERC721Votes.sol index 4962cb00c19..f71195ce7c8 100644 --- a/contracts/token/ERC721/extensions/ERC721Votes.sol +++ b/contracts/token/ERC721/extensions/ERC721Votes.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Votes.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Votes.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/extensions/ERC721Wrapper.sol b/contracts/token/ERC721/extensions/ERC721Wrapper.sol index 0a8acacb87b..111136bbe77 100644 --- a/contracts/token/ERC721/extensions/ERC721Wrapper.sol +++ b/contracts/token/ERC721/extensions/ERC721Wrapper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Wrapper.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/extensions/ERC721Wrapper.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/ERC721/utils/ERC721Utils.sol b/contracts/token/ERC721/utils/ERC721Utils.sol index 0e18f5cc0a8..2fd091afd67 100644 --- a/contracts/token/ERC721/utils/ERC721Utils.sol +++ b/contracts/token/ERC721/utils/ERC721Utils.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/utils/ERC721Utils.sol) pragma solidity ^0.8.20; diff --git a/contracts/token/common/ERC2981.sol b/contracts/token/common/ERC2981.sol index 7f56b275ee3..8335e56efab 100644 --- a/contracts/token/common/ERC2981.sol +++ b/contracts/token/common/ERC2981.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (token/common/ERC2981.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (token/common/ERC2981.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol index 40f01a93dca..a1c8af296ce 100644 --- a/contracts/utils/Address.sol +++ b/contracts/utils/Address.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Arrays.sol b/contracts/utils/Arrays.sol index 432c8602800..bd3401703d4 100644 --- a/contracts/utils/Arrays.sol +++ b/contracts/utils/Arrays.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Arrays.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Arrays.sol) // This file was procedurally generated from scripts/generate/templates/Arrays.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/Base64.sol b/contracts/utils/Base64.sol index ef9431b5578..8b7c5c5ed06 100644 --- a/contracts/utils/Base64.sol +++ b/contracts/utils/Base64.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.2) (utils/Base64.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Base64.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Comparators.sol b/contracts/utils/Comparators.sol index c10734712bc..a8c5e73dfea 100644 --- a/contracts/utils/Comparators.sol +++ b/contracts/utils/Comparators.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Comparators.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Create2.sol b/contracts/utils/Create2.sol index 121eb0c742a..ffd39d9a46c 100644 --- a/contracts/utils/Create2.sol +++ b/contracts/utils/Create2.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Create2.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Errors.sol b/contracts/utils/Errors.sol index 715db953620..442fc18920f 100644 --- a/contracts/utils/Errors.sol +++ b/contracts/utils/Errors.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 4da311cf3c0..069153bef4a 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Packing.sol) // This file was procedurally generated from scripts/generate/templates/Packing.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/Panic.sol b/contracts/utils/Panic.sol index 0e3c38c3e36..e168824d34b 100644 --- a/contracts/utils/Panic.sol +++ b/contracts/utils/Panic.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/ReentrancyGuard.sol b/contracts/utils/ReentrancyGuard.sol index 081851170e2..a95fb512f31 100644 --- a/contracts/utils/ReentrancyGuard.sol +++ b/contracts/utils/ReentrancyGuard.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/ReentrancyGuardTransient.sol b/contracts/utils/ReentrancyGuardTransient.sol index ba8cbb65b1f..1a62e29dafa 100644 --- a/contracts/utils/ReentrancyGuardTransient.sol +++ b/contracts/utils/ReentrancyGuardTransient.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuardTransient.sol) pragma solidity ^0.8.24; diff --git a/contracts/utils/ShortStrings.sol b/contracts/utils/ShortStrings.sol index 78fc5cd963d..fb8bde51668 100644 --- a/contracts/utils/ShortStrings.sol +++ b/contracts/utils/ShortStrings.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/ShortStrings.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/ShortStrings.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/SlotDerivation.sol b/contracts/utils/SlotDerivation.sol index 83b9d5639c0..62d1545c650 100644 --- a/contracts/utils/SlotDerivation.sol +++ b/contracts/utils/SlotDerivation.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/SlotDerivation.sol) // This file was procedurally generated from scripts/generate/templates/SlotDerivation.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/StorageSlot.sol b/contracts/utils/StorageSlot.sol index 2df32cb9278..aebb10524a2 100644 --- a/contracts/utils/StorageSlot.sol +++ b/contracts/utils/StorageSlot.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol index b72588646f7..c353212927e 100644 --- a/contracts/utils/Strings.sol +++ b/contracts/utils/Strings.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Strings.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/TransientSlot.sol b/contracts/utils/TransientSlot.sol index f1f67bdceb0..25c57dd3f96 100644 --- a/contracts/utils/TransientSlot.sol +++ b/contracts/utils/TransientSlot.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/TransientSlot.sol) // This file was procedurally generated from scripts/generate/templates/TransientSlot.js. pragma solidity ^0.8.24; diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index dc6534a8aa0..6493f56338a 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol index 77c4c8990fe..f15a67bd9b5 100644 --- a/contracts/utils/cryptography/EIP712.sol +++ b/contracts/utils/cryptography/EIP712.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/EIP712.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/Hashes.sol b/contracts/utils/cryptography/Hashes.sol index 0ed89b308c0..893883164fb 100644 --- a/contracts/utils/cryptography/Hashes.sol +++ b/contracts/utils/cryptography/Hashes.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/Hashes.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/MerkleProof.sol b/contracts/utils/cryptography/MerkleProof.sol index a8658013592..19b09e2af65 100644 --- a/contracts/utils/cryptography/MerkleProof.sol +++ b/contracts/utils/cryptography/MerkleProof.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol) // This file was procedurally generated from scripts/generate/templates/MerkleProof.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/MessageHashUtils.sol b/contracts/utils/cryptography/MessageHashUtils.sol index 35746ce72ce..e1cbccb65ec 100644 --- a/contracts/utils/cryptography/MessageHashUtils.sol +++ b/contracts/utils/cryptography/MessageHashUtils.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MessageHashUtils.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/cryptography/P256.sol b/contracts/utils/cryptography/P256.sol index 3028505ba75..510eb55e700 100644 --- a/contracts/utils/cryptography/P256.sol +++ b/contracts/utils/cryptography/P256.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/P256.sol) pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/contracts/utils/cryptography/RSA.sol b/contracts/utils/cryptography/RSA.sol index 70c38fd15bb..4e04ce5cc56 100644 --- a/contracts/utils/cryptography/RSA.sol +++ b/contracts/utils/cryptography/RSA.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/RSA.sol) pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/contracts/utils/cryptography/SignatureChecker.sol b/contracts/utils/cryptography/SignatureChecker.sol index 9aaa2e0716c..554f00d89d1 100644 --- a/contracts/utils/cryptography/SignatureChecker.sol +++ b/contracts/utils/cryptography/SignatureChecker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/SignatureChecker.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/SignatureChecker.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/introspection/ERC165.sol b/contracts/utils/introspection/ERC165.sol index 664b39fc19d..9fbce0447e7 100644 --- a/contracts/utils/introspection/ERC165.sol +++ b/contracts/utils/introspection/ERC165.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/introspection/ERC165Checker.sol b/contracts/utils/introspection/ERC165Checker.sol index a0ac72c21bf..8650f5503cc 100644 --- a/contracts/utils/introspection/ERC165Checker.sol +++ b/contracts/utils/introspection/ERC165Checker.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165Checker.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165Checker.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/introspection/IERC165.sol b/contracts/utils/introspection/IERC165.sol index bfbdf5dda39..719ec358659 100644 --- a/contracts/utils/introspection/IERC165.sol +++ b/contracts/utils/introspection/IERC165.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/math/Math.sol b/contracts/utils/math/Math.sol index 9e277f3f50d..85a420b1a7b 100644 --- a/contracts/utils/math/Math.sol +++ b/contracts/utils/math/Math.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/math/SafeCast.sol b/contracts/utils/math/SafeCast.sol index 36832006ebf..b345ede1e69 100644 --- a/contracts/utils/math/SafeCast.sol +++ b/contracts/utils/math/SafeCast.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/math/SignedMath.sol b/contracts/utils/math/SignedMath.sol index 502f4ecb03f..7c97aa4c22d 100644 --- a/contracts/utils/math/SignedMath.sol +++ b/contracts/utils/math/SignedMath.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/Checkpoints.sol b/contracts/utils/structs/Checkpoints.sol index 39e18a1fe82..8d8f130319d 100644 --- a/contracts/utils/structs/Checkpoints.sol +++ b/contracts/utils/structs/Checkpoints.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/Checkpoints.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/Checkpoints.sol) // This file was procedurally generated from scripts/generate/templates/Checkpoints.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/CircularBuffer.sol b/contracts/utils/structs/CircularBuffer.sol index cf1afbbd6ea..ae0a4a88f68 100644 --- a/contracts/utils/structs/CircularBuffer.sol +++ b/contracts/utils/structs/CircularBuffer.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/CircularBuffer.sol) pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; diff --git a/contracts/utils/structs/DoubleEndedQueue.sol b/contracts/utils/structs/DoubleEndedQueue.sol index 48f0d68c1eb..f243243bbd7 100644 --- a/contracts/utils/structs/DoubleEndedQueue.sol +++ b/contracts/utils/structs/DoubleEndedQueue.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/DoubleEndedQueue.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/DoubleEndedQueue.sol) pragma solidity ^0.8.20; import {Panic} from "../Panic.sol"; diff --git a/contracts/utils/structs/EnumerableMap.sol b/contracts/utils/structs/EnumerableMap.sol index d86e57f69d5..4e12acec142 100644 --- a/contracts/utils/structs/EnumerableMap.sol +++ b/contracts/utils/structs/EnumerableMap.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableMap.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/EnumerableSet.sol b/contracts/utils/structs/EnumerableSet.sol index 90fcfa9d77b..065202e8204 100644 --- a/contracts/utils/structs/EnumerableSet.sol +++ b/contracts/utils/structs/EnumerableSet.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/Heap.sol b/contracts/utils/structs/Heap.sol index d33dfbec8b2..c97bb432a33 100644 --- a/contracts/utils/structs/Heap.sol +++ b/contracts/utils/structs/Heap.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/Heap.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/structs/MerkleTree.sol b/contracts/utils/structs/MerkleTree.sol index 6a78c4b3c97..56f5bc67237 100644 --- a/contracts/utils/structs/MerkleTree.sol +++ b/contracts/utils/structs/MerkleTree.sol @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/MerkleTree.sol) pragma solidity ^0.8.20; diff --git a/contracts/utils/types/Time.sol b/contracts/utils/types/Time.sol index 1f729515425..a495932de39 100644 --- a/contracts/utils/types/Time.sol +++ b/contracts/utils/types/Time.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/types/Time.sol) +// OpenZeppelin Contracts (last updated v5.1.0) (utils/types/Time.sol) pragma solidity ^0.8.20; diff --git a/package.json b/package.json index f7c0f519737..f9b88273362 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openzeppelin-solidity", "description": "Secure Smart Contract library for Solidity", - "version": "5.0.2", + "version": "5.1.0", "private": true, "files": [ "/contracts/**/*.sol", From 378914ceabe26d149b0f413e7c6b226704d5c1ad Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 18 Oct 2024 14:17:20 +0200 Subject: [PATCH 70/81] Delegate override vote (#5192) Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- .changeset/great-lions-hear.md | 5 + .changeset/pink-wasps-hammer.md | 5 + contracts/governance/Governor.sol | 11 +- contracts/governance/README.adoc | 6 + .../GovernorCountingOverridable.sol | 212 +++++++++++ .../extensions/GovernorPreventLateQuorum.sol | 16 +- contracts/governance/utils/VotesExtended.sol | 70 ++++ .../mocks/VotesAdditionalCheckpointsMock.sol | 42 +++ .../GovernorCountingOverridableMock.sol | 18 + .../GovernorPreventLateQuorumMock.sol | 10 +- .../ERC20VotesAdditionalCheckpointsMock.sol | 31 ++ .../GovernorCountingOverridable.test.js | 344 ++++++++++++++++++ .../utils/VotesAdditionalCheckpoints.test.js | 152 ++++++++ test/helpers/eip712-types.js | 7 + test/helpers/governance.js | 17 + 15 files changed, 924 insertions(+), 22 deletions(-) create mode 100644 .changeset/great-lions-hear.md create mode 100644 .changeset/pink-wasps-hammer.md create mode 100644 contracts/governance/extensions/GovernorCountingOverridable.sol create mode 100644 contracts/governance/utils/VotesExtended.sol create mode 100644 contracts/mocks/VotesAdditionalCheckpointsMock.sol create mode 100644 contracts/mocks/governance/GovernorCountingOverridableMock.sol create mode 100644 contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol create mode 100644 test/governance/extensions/GovernorCountingOverridable.test.js create mode 100644 test/governance/utils/VotesAdditionalCheckpoints.test.js diff --git a/.changeset/great-lions-hear.md b/.changeset/great-lions-hear.md new file mode 100644 index 00000000000..2be5de253a8 --- /dev/null +++ b/.changeset/great-lions-hear.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`VotesExtended`: Create an extension of `Votes` which checkpoints balances and delegates. diff --git a/.changeset/pink-wasps-hammer.md b/.changeset/pink-wasps-hammer.md new file mode 100644 index 00000000000..69f63d3ca19 --- /dev/null +++ b/.changeset/pink-wasps-hammer.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`GovernorCountingOverridable`: Add a governor counting module that enables token holders to override the vote of their delegate. diff --git a/contracts/governance/Governor.sol b/contracts/governance/Governor.sol index 2add7c759fb..390411556b0 100644 --- a/contracts/governance/Governor.sol +++ b/contracts/governance/Governor.sol @@ -260,6 +260,13 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 bytes memory params ) internal virtual returns (uint256); + /** + * @dev Hook that should be called every time the tally for a proposal is updated. + * + * Note: This function must run successfully. Reverts will result in the bricking of governance + */ + function _tallyUpdated(uint256 proposalId) internal virtual {} + /** * @dev Default additional encoded parameters used by castVote methods that don't include them * @@ -649,6 +656,8 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 emit VoteCastWithParams(account, proposalId, support, votedWeight, reason, params); } + _tallyUpdated(proposalId); + return votedWeight; } @@ -732,7 +741,7 @@ abstract contract Governor is Context, ERC165, EIP712, Nonces, IGovernor, IERC72 * * If requirements are not met, reverts with a {GovernorUnexpectedProposalState} error. */ - function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) private view returns (ProposalState) { + function _validateStateBitmap(uint256 proposalId, bytes32 allowedStates) internal view returns (ProposalState) { ProposalState currentState = state(proposalId); if (_encodeStateBitmap(currentState) & allowedStates == bytes32(0)) { revert GovernorUnexpectedProposalState(proposalId, currentState, allowedStates); diff --git a/contracts/governance/README.adoc b/contracts/governance/README.adoc index 0f556b90d69..d390d1be82d 100644 --- a/contracts/governance/README.adoc +++ b/contracts/governance/README.adoc @@ -30,6 +30,8 @@ Counting modules determine valid voting options. * {GovernorCountingFractional}: A more modular voting system that allows a user to vote with only part of its voting power, and to split that weight arbitrarily between the 3 different options (Against, For and Abstain). +* {GovernorCountingOverridable}: An extended version of `GovernorCountingSimple` which allows delegatees to override their delegates while the vote is live. + Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed. * {GovernorTimelockAccess}: Connects with an instance of an {AccessManager}. This allows restrictions (and delays) enforced by the manager to be considered by the Governor and integrated into the AccessManager's "schedule + execute" workflow. @@ -66,6 +68,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{GovernorCountingFractional}} +{{GovernorCountingOverride}} + {{GovernorVotes}} {{GovernorVotesQuorumFraction}} @@ -88,6 +92,8 @@ NOTE: Functions of the `Governor` contract do not include access control. If you {{Votes}} +{{VotesExtended}} + == Timelock In a governance system, the {TimelockController} contract is in charge of introducing a delay between a proposal and its execution. It can be used with or without a {Governor}. diff --git a/contracts/governance/extensions/GovernorCountingOverridable.sol b/contracts/governance/extensions/GovernorCountingOverridable.sol new file mode 100644 index 00000000000..9b46903e35d --- /dev/null +++ b/contracts/governance/extensions/GovernorCountingOverridable.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {SignatureChecker} from "../../utils/cryptography/SignatureChecker.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; +import {VotesExtended} from "../utils/VotesExtended.sol"; +import {GovernorVotes} from "./GovernorVotes.sol"; + +/** + * @dev Extension of {Governor} which enables delegatees to override the vote of their delegates. This module requires a + * token token that inherits `VotesExtended`. + */ +abstract contract GovernorCountingOverridable is GovernorVotes { + bytes32 public constant OVERRIDE_BALLOT_TYPEHASH = + keccak256("OverrideBallot(uint256 proposalId,uint8 support,address voter,uint256 nonce,string reason)"); + + /** + * @dev Supported vote types. Matches Governor Bravo ordering. + */ + enum VoteType { + Against, + For, + Abstain + } + + struct VoteReceipt { + uint8 casted; // 0 if vote was not casted. Otherwise: support + 1 + bool hasOverriden; + uint208 overridenWeight; + } + + struct ProposalVote { + uint256[3] votes; + mapping(address voter => VoteReceipt) voteReceipt; + } + + event VoteReduced(address indexed voter, uint256 proposalId, uint8 support, uint256 weight); + event OverrideVoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); + + error GovernorAlreadyOverridenVote(address account); + + mapping(uint256 proposalId => ProposalVote) private _proposalVotes; + + /** + * @dev See {IGovernor-COUNTING_MODE}. + */ + // solhint-disable-next-line func-name-mixedcase + function COUNTING_MODE() public pure virtual override returns (string memory) { + return "support=bravo,override&quorum=for,abstain&overridable=true"; + } + + /** + * @dev See {IGovernor-hasVoted}. + */ + function hasVoted(uint256 proposalId, address account) public view virtual override returns (bool) { + return _proposalVotes[proposalId].voteReceipt[account].casted != 0; + } + + /** + * @dev Check if an `account` has overridden their delegate for a proposal. + */ + function hasVotedOverride(uint256 proposalId, address account) public view virtual returns (bool) { + return _proposalVotes[proposalId].voteReceipt[account].hasOverriden; + } + + /** + * @dev Accessor to the internal vote counts. + */ + function proposalVotes( + uint256 proposalId + ) public view virtual returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return (votes[uint8(VoteType.Against)], votes[uint8(VoteType.For)], votes[uint8(VoteType.Abstain)]); + } + + /** + * @dev See {Governor-_quorumReached}. + */ + function _quorumReached(uint256 proposalId) internal view virtual override returns (bool) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return quorum(proposalSnapshot(proposalId)) <= votes[uint8(VoteType.For)] + votes[uint8(VoteType.Abstain)]; + } + + /** + * @dev See {Governor-_voteSucceeded}. In this module, the forVotes must be strictly over the againstVotes. + */ + function _voteSucceeded(uint256 proposalId) internal view virtual override returns (bool) { + uint256[3] storage votes = _proposalVotes[proposalId].votes; + return votes[uint8(VoteType.For)] > votes[uint8(VoteType.Against)]; + } + + /** + * @dev See {Governor-_countVote}. In this module, the support follows the `VoteType` enum (from Governor Bravo). + * + * NOTE: called by {Governor-_castVote} which emits the {IGovernor-VoteCast} (or {IGovernor-VoteCastWithParams}) + * event. + */ + function _countVote( + uint256 proposalId, + address account, + uint8 support, + uint256 totalWeight, + bytes memory /*params*/ + ) internal virtual override returns (uint256) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (support > uint8(VoteType.Abstain)) { + revert GovernorInvalidVoteType(); + } + + if (proposalVote.voteReceipt[account].casted != 0) { + revert GovernorAlreadyCastVote(account); + } + + totalWeight -= proposalVote.voteReceipt[account].overridenWeight; + proposalVote.votes[support] += totalWeight; + proposalVote.voteReceipt[account].casted = support + 1; + + return totalWeight; + } + + /// @dev Variant of {Governor-_countVote} that deals with vote overrides. + function _countOverride(uint256 proposalId, address account, uint8 support) internal virtual returns (uint256) { + ProposalVote storage proposalVote = _proposalVotes[proposalId]; + + if (support > uint8(VoteType.Abstain)) { + revert GovernorInvalidVoteType(); + } + + if (proposalVote.voteReceipt[account].hasOverriden) { + revert GovernorAlreadyOverridenVote(account); + } + + uint256 proposalSnapshot = proposalSnapshot(proposalId); + uint256 overridenWeight = VotesExtended(address(token())).getPastBalanceOf(account, proposalSnapshot); + address delegate = VotesExtended(address(token())).getPastDelegate(account, proposalSnapshot); + uint8 delegateCasted = proposalVote.voteReceipt[delegate].casted; + + proposalVote.voteReceipt[account].hasOverriden = true; + proposalVote.votes[support] += overridenWeight; + if (delegateCasted == 0) { + proposalVote.voteReceipt[delegate].overridenWeight += SafeCast.toUint208(overridenWeight); + } else { + uint8 delegateSupport = delegateCasted - 1; + proposalVote.votes[delegateSupport] -= overridenWeight; + emit VoteReduced(delegate, proposalId, delegateSupport, overridenWeight); + } + + return overridenWeight; + } + + /// @dev variant of {Governor-_castVote} that deals with vote overrides. + function _castOverride( + uint256 proposalId, + address account, + uint8 support, + string calldata reason + ) internal virtual returns (uint256) { + _validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Active)); + + uint256 overridenWeight = _countOverride(proposalId, account, support); + + emit OverrideVoteCast(account, proposalId, support, overridenWeight, reason); + + _tallyUpdated(proposalId); + + return overridenWeight; + } + + /// @dev Public function for casting an override vote + function castOverrideVote( + uint256 proposalId, + uint8 support, + string calldata reason + ) public virtual returns (uint256) { + address voter = _msgSender(); + return _castOverride(proposalId, voter, support, reason); + } + + /// @dev Public function for casting an override vote using a voter's signature + function castOverrideVoteBySig( + uint256 proposalId, + uint8 support, + address voter, + string calldata reason, + bytes calldata signature + ) public virtual returns (uint256) { + bool valid = SignatureChecker.isValidSignatureNow( + voter, + _hashTypedDataV4( + keccak256( + abi.encode( + OVERRIDE_BALLOT_TYPEHASH, + proposalId, + support, + voter, + _useNonce(voter), + keccak256(bytes(reason)) + ) + ) + ), + signature + ); + + if (!valid) { + revert GovernorInvalidSignature(voter); + } + + return _castOverride(proposalId, voter, support, reason); + } +} diff --git a/contracts/governance/extensions/GovernorPreventLateQuorum.sol b/contracts/governance/extensions/GovernorPreventLateQuorum.sol index ff80af648db..eb93add8099 100644 --- a/contracts/governance/extensions/GovernorPreventLateQuorum.sol +++ b/contracts/governance/extensions/GovernorPreventLateQuorum.sol @@ -44,20 +44,12 @@ abstract contract GovernorPreventLateQuorum is Governor { } /** - * @dev Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See - * {Governor-_castVote}. + * @dev Vote tally updated and detects if it caused quorum to be reached, potentially extending the voting period. * * May emit a {ProposalExtended} event. */ - function _castVote( - uint256 proposalId, - address account, - uint8 support, - string memory reason, - bytes memory params - ) internal virtual override returns (uint256) { - uint256 result = super._castVote(proposalId, account, support, reason, params); - + function _tallyUpdated(uint256 proposalId) internal virtual override { + super._tallyUpdated(proposalId); if (_extendedDeadlines[proposalId] == 0 && _quorumReached(proposalId)) { uint48 extendedDeadline = clock() + lateQuorumVoteExtension(); @@ -67,8 +59,6 @@ abstract contract GovernorPreventLateQuorum is Governor { _extendedDeadlines[proposalId] = extendedDeadline; } - - return result; } /** diff --git a/contracts/governance/utils/VotesExtended.sol b/contracts/governance/utils/VotesExtended.sol new file mode 100644 index 00000000000..62ddd5f7a2e --- /dev/null +++ b/contracts/governance/utils/VotesExtended.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Checkpoints} from "../../utils/structs/Checkpoints.sol"; +import {Votes} from "./Votes.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +/** + * @dev Extension of {Votes} that adds exposes checkpoints for delegations and balances. + */ +abstract contract VotesExtended is Votes { + using SafeCast for uint256; + using Checkpoints for Checkpoints.Trace160; + using Checkpoints for Checkpoints.Trace208; + + mapping(address delegatee => Checkpoints.Trace160) private _delegateCheckpoints; + mapping(address account => Checkpoints.Trace208) private _balanceOfCheckpoints; + + /** + * @dev Returns the delegate of an `account` at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + * + * Requirements: + * + * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + */ + function getPastDelegate(address account, uint256 timepoint) public view virtual returns (address) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return address(_delegateCheckpoints[account].upperLookupRecent(timepoint.toUint48())); + } + + /** + * @dev Returns the `balanceOf` of an `account` at a specific moment in the past. If the `clock()` is + * configured to use block numbers, this will return the value at the end of the corresponding block. + * + * Requirements: + * + * - `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + */ + function getPastBalanceOf(address account, uint256 timepoint) public view virtual returns (uint256) { + uint48 currentTimepoint = clock(); + if (timepoint >= currentTimepoint) { + revert ERC5805FutureLookup(timepoint, currentTimepoint); + } + return _balanceOfCheckpoints[account].upperLookupRecent(timepoint.toUint48()); + } + + /// @inheritdoc Votes + function _delegate(address account, address delegatee) internal virtual override { + super._delegate(account, delegatee); + + _delegateCheckpoints[account].push(clock(), uint160(delegatee)); + } + + /// @inheritdoc Votes + function _transferVotingUnits(address from, address to, uint256 amount) internal virtual override { + super._transferVotingUnits(from, to, amount); + if (from != to) { + if (from != address(0)) { + _balanceOfCheckpoints[from].push(clock(), _getVotingUnits(from).toUint208()); + } + if (to != address(0)) { + _balanceOfCheckpoints[to].push(clock(), _getVotingUnits(to).toUint208()); + } + } + } +} diff --git a/contracts/mocks/VotesAdditionalCheckpointsMock.sol b/contracts/mocks/VotesAdditionalCheckpointsMock.sol new file mode 100644 index 00000000000..9c456190e97 --- /dev/null +++ b/contracts/mocks/VotesAdditionalCheckpointsMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {VotesExtended} from "../governance/utils/VotesExtended.sol"; + +abstract contract VotesExtendedMock is VotesExtended { + mapping(address voter => uint256) private _votingUnits; + + function getTotalSupply() public view returns (uint256) { + return _getTotalSupply(); + } + + function delegate(address account, address newDelegation) public { + return _delegate(account, newDelegation); + } + + function _getVotingUnits(address account) internal view override returns (uint256) { + return _votingUnits[account]; + } + + function _mint(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(address(0), account, votes); + } + + function _burn(address account, uint256 votes) internal { + _votingUnits[account] += votes; + _transferVotingUnits(account, address(0), votes); + } +} + +abstract contract VotesExtendedTimestampMock is VotesExtendedMock { + function clock() public view override returns (uint48) { + return uint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/contracts/mocks/governance/GovernorCountingOverridableMock.sol b/contracts/mocks/governance/GovernorCountingOverridableMock.sol new file mode 100644 index 00000000000..bae09d933ad --- /dev/null +++ b/contracts/mocks/governance/GovernorCountingOverridableMock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Governor} from "../../governance/Governor.sol"; +import {GovernorSettings} from "../../governance/extensions/GovernorSettings.sol"; +import {GovernorVotesQuorumFraction} from "../../governance/extensions/GovernorVotesQuorumFraction.sol"; +import {GovernorCountingOverridable, VotesExtended} from "../../governance/extensions/GovernorCountingOverridable.sol"; + +abstract contract GovernorCountingOverridableMock is + GovernorSettings, + GovernorVotesQuorumFraction, + GovernorCountingOverridable +{ + function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) { + return super.proposalThreshold(); + } +} diff --git a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol index fde0863ce86..176976f9199 100644 --- a/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol +++ b/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol @@ -34,13 +34,7 @@ abstract contract GovernorPreventLateQuorumMock is return super.proposalThreshold(); } - function _castVote( - uint256 proposalId, - address account, - uint8 support, - string memory reason, - bytes memory params - ) internal override(Governor, GovernorPreventLateQuorum) returns (uint256) { - return super._castVote(proposalId, account, support, reason, params); + function _tallyUpdated(uint256 proposalId) internal override(Governor, GovernorPreventLateQuorum) { + super._tallyUpdated(proposalId); } } diff --git a/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol b/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol new file mode 100644 index 00000000000..39b3c654b17 --- /dev/null +++ b/contracts/mocks/token/ERC20VotesAdditionalCheckpointsMock.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20Votes} from "../../token/ERC20/extensions/ERC20Votes.sol"; +import {VotesExtended, Votes} from "../../governance/utils/VotesExtended.sol"; +import {SafeCast} from "../../utils/math/SafeCast.sol"; + +abstract contract ERC20VotesExtendedMock is ERC20Votes, VotesExtended { + function _delegate(address account, address delegatee) internal virtual override(Votes, VotesExtended) { + return super._delegate(account, delegatee); + } + + function _transferVotingUnits( + address from, + address to, + uint256 amount + ) internal virtual override(Votes, VotesExtended) { + return super._transferVotingUnits(from, to, amount); + } +} + +abstract contract ERC20VotesExtendedTimestampMock is ERC20VotesExtendedMock { + function clock() public view virtual override returns (uint48) { + return SafeCast.toUint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public view virtual override returns (string memory) { + return "mode=timestamp"; + } +} diff --git a/test/governance/extensions/GovernorCountingOverridable.test.js b/test/governance/extensions/GovernorCountingOverridable.test.js new file mode 100644 index 00000000000..6a54009df41 --- /dev/null +++ b/test/governance/extensions/GovernorCountingOverridable.test.js @@ -0,0 +1,344 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); + +const { GovernorHelper } = require('../../helpers/governance'); +const { getDomain, OverrideBallot } = require('../../helpers/eip712'); +const { VoteType } = require('../../helpers/enums'); + +const TOKENS = [ + { Token: '$ERC20VotesExtendedMock', mode: 'blocknumber' }, + // { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' }, +]; + +const name = 'Override Governor'; +const version = '1'; +const tokenName = 'MockToken'; +const tokenSymbol = 'MTKN'; +const tokenSupply = ethers.parseEther('100'); +const votingDelay = 4n; +const votingPeriod = 16n; +const value = ethers.parseEther('1'); + +const signBallot = account => (contract, message) => + getDomain(contract).then(domain => account.signTypedData(domain, { OverrideBallot }, message)); + +describe('GovernorCountingOverridable', function () { + for (const { Token, mode } of TOKENS) { + const fixture = async () => { + const [owner, proposer, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); + const receiver = await ethers.deployContract('CallReceiverMock'); + + const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, tokenName, version]); + const mock = await ethers.deployContract('$GovernorCountingOverridableMock', [ + name, // name + votingDelay, // initialVotingDelay + votingPeriod, // initialVotingPeriod + 0n, // initialProposalThreshold + token, // tokenAddress + 10n, // quorumNumeratorValue + ]); + + await owner.sendTransaction({ to: mock, value }); + await token.$_mint(owner, tokenSupply); + + const helper = new GovernorHelper(mock, mode); + await helper.connect(owner).delegate({ token, to: voter1, value: ethers.parseEther('10') }); + await helper.connect(owner).delegate({ token, to: voter2, value: ethers.parseEther('7') }); + await helper.connect(owner).delegate({ token, to: voter3, value: ethers.parseEther('5') }); + await helper.connect(owner).delegate({ token, to: voter4, value: ethers.parseEther('2') }); + + return { owner, proposer, voter1, voter2, voter3, voter4, other, receiver, token, mock, helper }; + }; + + describe(`using ${Token}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + + // default proposal + this.proposal = this.helper.setProposal( + [ + { + target: this.receiver.target, + value, + data: this.receiver.interface.encodeFunctionData('mockFunction'), + }, + ], + '', + ); + }); + + it('deployment check', async function () { + expect(await this.mock.name()).to.equal(name); + expect(await this.mock.token()).to.equal(this.token); + expect(await this.mock.votingDelay()).to.equal(votingDelay); + expect(await this.mock.votingPeriod()).to.equal(votingPeriod); + expect(await this.mock.COUNTING_MODE()).to.equal('support=bravo,override&quorum=for,abstain&overridable=true'); + }); + + it('nominal is unaffected', async function () { + await this.helper.connect(this.proposer).propose(); + await this.helper.waitForSnapshot(); + await this.helper.connect(this.voter1).vote({ support: VoteType.For, reason: 'This is nice' }); + await this.helper.connect(this.voter2).vote({ support: VoteType.For }); + await this.helper.connect(this.voter3).vote({ support: VoteType.Against }); + await this.helper.connect(this.voter4).vote({ support: VoteType.Abstain }); + await this.helper.waitForDeadline(); + await this.helper.execute(); + + expect(await this.mock.hasVoted(this.proposal.id, this.owner)).to.be.false; + expect(await this.mock.hasVoted(this.proposal.id, this.voter1)).to.be.true; + expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.true; + expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); + expect(await ethers.provider.getBalance(this.receiver)).to.equal(value); + }); + + describe('cast override vote', async function () { + beforeEach(async function () { + // user 1 -(delegate 10 tokens)-> user 2 + // user 2 -(delegate 7 tokens)-> user 2 + // user 3 -(delegate 5 tokens)-> user 1 + // user 4 -(delegate 2 tokens)-> user 2 + await this.token.connect(this.voter1).delegate(this.voter2); + await this.token.connect(this.voter3).delegate(this.voter1); + await this.token.connect(this.voter4).delegate(this.voter2); + await mine(); + + await this.helper.connect(this.proposer).propose(); + await this.helper.waitForSnapshot(); + }); + + it('override after delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('19'), ''); // 10 + 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [0, 19, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + + // user 1 overrides after user 2 votes + + const reason = "disagree with user 2's decision"; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.emit(this.mock, 'VoteReduced') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('10')); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + }); + + it('override before delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 1 overrides before user 2 votes + + const reason = 'voter 2 is not voting'; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.not.emit(this.mock, 'VoteReduced'); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + }); + + it('override before and after delegate vote', async function () { + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter3)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.false; + + // user 1 overrides before user 2 votes + + const reason = 'voter 2 is not voting'; + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, reason)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), reason) + .to.not.emit(this.mock, 'VoteReduced'); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + + // user 2 votes + + await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('9'), ''); // 7 + 2 + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 9, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter2)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter2)).to.be.false; + + // User 4 overrides after user 2 votes + + const reason2 = "disagree with user 2's decision"; + await expect(this.mock.connect(this.voter4).castOverrideVote(this.helper.id, VoteType.Abstain, reason2)) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter4, this.helper.id, VoteType.Abstain, ethers.parseEther('2'), reason2) + .to.emit(this.mock, 'VoteReduced') + .withArgs(this.voter2, this.helper.id, VoteType.For, ethers.parseEther('2')); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 7, 2].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter4)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter4)).to.be.true; + }); + + it('vote (with delegated balance) and override (with self balance) are independent', async function () { + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [0, 0, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.false; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.false; + + // user 1 votes with delegated weight from user 3 + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.For)) + .to.emit(this.mock, 'VoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('5'), ''); + + // user 1 cast an override vote with its own balance (delegated to user 2) + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, '')) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), ''); + + expect(await this.mock.proposalVotes(this.helper.id)).to.deep.eq( + [10, 5, 0].map(x => ethers.parseEther(x.toString())), + ); + expect(await this.mock.hasVoted(this.helper.id, this.voter1)).to.be.true; + expect(await this.mock.hasVotedOverride(this.helper.id, this.voter1)).to.be.true; + }); + + it('can not override vote twice', async function () { + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Against, '')) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.Against, ethers.parseEther('10'), ''); + await expect(this.mock.connect(this.voter1).castOverrideVote(this.helper.id, VoteType.Abstain, '')) + .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyOverridenVote') + .withArgs(this.voter1.address); + }); + + it('can not vote twice', async function () { + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Against)); + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, VoteType.Abstain)) + .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyCastVote') + .withArgs(this.voter1.address); + }); + + describe('invalid vote type', function () { + it('override vote', async function () { + await expect( + this.mock.connect(this.voter1).castOverrideVote(this.helper.id, 3, ''), + ).to.be.revertedWithCustomError(this.mock, 'GovernorInvalidVoteType'); + }); + + it('traditional vote', async function () { + await expect(this.mock.connect(this.voter1).castVote(this.helper.id, 3)).to.be.revertedWithCustomError( + this.mock, + 'GovernorInvalidVoteType', + ); + }); + }); + + describe('by signature', function () { + it('EOA signature', async function () { + const nonce = await this.mock.nonces(this.voter1); + + await expect( + this.helper.overrideVote({ + support: VoteType.For, + voter: this.voter1.address, + nonce, + signature: signBallot(this.voter1), + }), + ) + .to.emit(this.mock, 'OverrideVoteCast') + .withArgs(this.voter1, this.helper.id, VoteType.For, ethers.parseEther('10'), ''); + + expect(await this.mock.hasVotedOverride(this.proposal.id, this.voter1)).to.be.true; + }); + + it('revert if signature does not match signer', async function () { + const nonce = await this.mock.nonces(this.voter1); + + const voteParams = { + support: VoteType.For, + voter: this.voter2.address, + nonce, + signature: signBallot(this.voter1), + }; + + await expect(this.helper.overrideVote(voteParams)) + .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') + .withArgs(voteParams.voter); + }); + + it('revert if vote nonce is incorrect', async function () { + const nonce = await this.mock.nonces(this.voter1); + + const voteParams = { + support: VoteType.For, + voter: this.voter1.address, + nonce: nonce + 1n, + signature: signBallot(this.voter1), + }; + + await expect(this.helper.overrideVote(voteParams)) + .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') + .withArgs(voteParams.voter); + }); + }); + }); + }); + } +}); diff --git a/test/governance/utils/VotesAdditionalCheckpoints.test.js b/test/governance/utils/VotesAdditionalCheckpoints.test.js new file mode 100644 index 00000000000..4a66ef2748c --- /dev/null +++ b/test/governance/utils/VotesAdditionalCheckpoints.test.js @@ -0,0 +1,152 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); + +const { sum } = require('../../helpers/math'); +const { zip } = require('../../helpers/iterate'); +const time = require('../../helpers/time'); + +const { shouldBehaveLikeVotes } = require('./Votes.behavior'); + +const MODES = { + blocknumber: '$VotesExtendedMock', + timestamp: '$VotesExtendedTimestampMock', +}; + +const AMOUNTS = [ethers.parseEther('10000000'), 10n, 20n]; + +describe('VotesExtended', function () { + for (const [mode, artifact] of Object.entries(MODES)) { + const fixture = async () => { + const accounts = await ethers.getSigners(); + + const amounts = Object.fromEntries( + zip( + accounts.slice(0, AMOUNTS.length).map(({ address }) => address), + AMOUNTS, + ), + ); + + const name = 'Override Votes'; + const version = '1'; + const votes = await ethers.deployContract(artifact, [name, version]); + + return { accounts, amounts, votes, name, version }; + }; + + describe(`vote with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeVotes(AMOUNTS, { mode, fungible: true }); + + it('starts with zero votes', async function () { + expect(await this.votes.getTotalSupply()).to.equal(0n); + }); + + describe('performs voting operations', function () { + beforeEach(async function () { + this.txs = []; + for (const [account, amount] of Object.entries(this.amounts)) { + this.txs.push(await this.votes.$_mint(account, amount)); + } + }); + + it('reverts if block number >= current block', async function () { + const lastTxTimepoint = await time.clockFromReceipt[mode](this.txs.at(-1)); + const clock = await this.votes.clock(); + await expect(this.votes.getPastTotalSupply(lastTxTimepoint)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(lastTxTimepoint, clock); + }); + + it('delegates', async function () { + expect(await this.votes.getVotes(this.accounts[0])).to.equal(0n); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(ethers.ZeroAddress); + expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + + await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[0].address]); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); + expect(await this.votes.delegates(this.accounts[1])).to.equal(ethers.ZeroAddress); + + await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal( + this.amounts[this.accounts[0].address] + this.amounts[this.accounts[1].address], + ); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(0n); + expect(await this.votes.delegates(this.accounts[0])).to.equal(this.accounts[0]); + expect(await this.votes.delegates(this.accounts[1])).to.equal(this.accounts[0]); + }); + + it('cross delegates', async function () { + await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + await this.votes.delegate(this.accounts[1], ethers.Typed.address(this.accounts[0])); + + expect(await this.votes.getVotes(this.accounts[0])).to.equal(this.amounts[this.accounts[1].address]); + expect(await this.votes.getVotes(this.accounts[1])).to.equal(this.amounts[this.accounts[0].address]); + }); + + it('returns total amount of votes', async function () { + const totalSupply = sum(...Object.values(this.amounts)); + expect(await this.votes.getTotalSupply()).to.equal(totalSupply); + }); + }); + }); + + describe(`checkpoint delegates with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('checkpoint delegates', async function () { + const tx = await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + const timepoint = await time.clockFromReceipt[mode](tx); + await mine(2); + + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint - 1n)).to.equal(ethers.ZeroAddress); + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint)).to.equal(this.accounts[1].address); + expect(await this.votes.getPastDelegate(this.accounts[0], timepoint + 1n)).to.equal(this.accounts[1].address); + }); + + it('reverts if current timepoint <= timepoint', async function () { + const tx = await this.votes.delegate(this.accounts[0], ethers.Typed.address(this.accounts[1])); + const timepoint = await time.clockFromReceipt[mode](tx); + + await expect(this.votes.getPastDelegate(this.accounts[0], timepoint + 1n)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(timepoint + 1n, timepoint); + }); + }); + + describe(`checkpoint balances with ${mode}`, function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + it('checkpoint balances', async function () { + const tx = await this.votes.$_mint(this.accounts[0].address, 100n); + const timepoint = await time.clockFromReceipt[mode](tx); + await mine(2); + + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint - 1n)).to.equal(0n); + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint)).to.equal(100n); + expect(await this.votes.getPastBalanceOf(this.accounts[0].address, timepoint + 1n)).to.equal(100n); + }); + + it('reverts if current timepoint <= timepoint', async function () { + const tx = await this.votes.$_mint(this.accounts[0].address, 100n); + const timepoint = await time.clockFromReceipt[mode](tx); + + await expect(this.votes.getPastBalanceOf(this.accounts[0], timepoint + 1n)) + .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') + .withArgs(timepoint + 1n, timepoint); + }); + }); + } +}); diff --git a/test/helpers/eip712-types.js b/test/helpers/eip712-types.js index b2b6ccf837b..d04969e442b 100644 --- a/test/helpers/eip712-types.js +++ b/test/helpers/eip712-types.js @@ -32,6 +32,13 @@ module.exports = mapValues( reason: 'string', params: 'bytes', }, + OverrideBallot: { + proposalId: 'uint256', + support: 'uint8', + voter: 'address', + nonce: 'uint256', + reason: 'string', + }, Delegation: { delegatee: 'address', nonce: 'uint256', diff --git a/test/helpers/governance.js b/test/helpers/governance.js index dce5927b730..540967af49d 100644 --- a/test/helpers/governance.js +++ b/test/helpers/governance.js @@ -128,6 +128,23 @@ class GovernorHelper { return await this.governor[method](...args); } + async overrideVote(vote = {}) { + let method = 'castOverrideVote'; + let args = [this.id, vote.support]; + + vote.reason = vote.reason ?? ''; + + if (vote.signature) { + let message = this.forgeMessage(vote); + message.reason = message.reason ?? ''; + const sign = await vote.signature(this.governor, message); + method = 'castOverrideVoteBySig'; + args.push(vote.voter, vote.reason ?? '', sign); + } + + return await this.governor[method](...args); + } + /// Clock helpers async waitForSnapshot(offset = 0n) { const timepoint = await this.governor.proposalSnapshot(this.id); From 2a83a217afff9acb874478be9eb83cb58d7eafbe Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 18 Oct 2024 14:45:36 +0200 Subject: [PATCH 71/81] GovernorCountingOverridable testing with timestamp (#5269) --- ...VotesAdditionalCheckpointsMock.sol => VotesExtendedMock.sol} | 0 test/governance/extensions/GovernorCountingOverridable.test.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename contracts/mocks/{VotesAdditionalCheckpointsMock.sol => VotesExtendedMock.sol} (100%) diff --git a/contracts/mocks/VotesAdditionalCheckpointsMock.sol b/contracts/mocks/VotesExtendedMock.sol similarity index 100% rename from contracts/mocks/VotesAdditionalCheckpointsMock.sol rename to contracts/mocks/VotesExtendedMock.sol diff --git a/test/governance/extensions/GovernorCountingOverridable.test.js b/test/governance/extensions/GovernorCountingOverridable.test.js index 6a54009df41..92e86f45019 100644 --- a/test/governance/extensions/GovernorCountingOverridable.test.js +++ b/test/governance/extensions/GovernorCountingOverridable.test.js @@ -8,7 +8,7 @@ const { VoteType } = require('../../helpers/enums'); const TOKENS = [ { Token: '$ERC20VotesExtendedMock', mode: 'blocknumber' }, - // { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' }, + { Token: '$ERC20VotesExtendedTimestampMock', mode: 'timestamp' }, ]; const name = 'Override Governor'; From f989fff93168606c726bc5e831ef50dd6e543f45 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Fri, 18 Oct 2024 15:07:34 +0200 Subject: [PATCH 72/81] Remove missed changeset from 5.1-rc.1 (#5270) --- .changeset/yellow-tables-sell.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/yellow-tables-sell.md diff --git a/.changeset/yellow-tables-sell.md b/.changeset/yellow-tables-sell.md deleted file mode 100644 index f8cdc8d306b..00000000000 --- a/.changeset/yellow-tables-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. From 5bb3f3e788c6b2c806d562ef083b438354f969d7 Mon Sep 17 00:00:00 2001 From: Abhishek Sharma <34743587+Shaykoo@users.noreply.github.com> Date: Sat, 19 Oct 2024 19:16:09 +0700 Subject: [PATCH 73/81] Simplify content in security documentation (#5233) Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- docs/modules/ROOT/pages/extending-contracts.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/extending-contracts.adoc b/docs/modules/ROOT/pages/extending-contracts.adoc index 1cdc0d7814f..8ff4101f220 100644 --- a/docs/modules/ROOT/pages/extending-contracts.adoc +++ b/docs/modules/ROOT/pages/extending-contracts.adoc @@ -46,6 +46,6 @@ NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessCo The maintainers of OpenZeppelin Contracts are mainly concerned with the correctness and security of the code as published in the library, and the combinations of base contracts with the official extensions from the library. -Custom overrides, and those of hooks in particular, may break some important assumptions and introduce vulnerabilities in otherwise secure code. While we try to ensure the contracts remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. While we try to document all important assumptions, this should not be relied upon. Custom overrides should be carefully reviewed and checked against the source code of the contract they are customizing so as to fully understand their impact and guarantee their security. +Custom overrides, especially to hooks, can disrupt important assumptions and may introduce security risks in the code that was previously secure. While we try to ensure the contracts remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. While we try to document all important assumptions, this should not be relied upon. Custom overrides should be carefully reviewed and checked against the source code of the contract they are customizing to fully understand their impact and guarantee their security. The way functions interact internally should not be assumed to stay stable across releases of the library. For example, a function that is used in one context in a particular release may not be used in the same context in the next release. Contracts that override functions should revalidate their assumptions when updating the version of OpenZeppelin Contracts they are built on. From a4dc928a93a887ba6baf69f821e5aee764516bec Mon Sep 17 00:00:00 2001 From: Maxim Tiron Date: Mon, 21 Oct 2024 14:39:21 +0300 Subject: [PATCH 74/81] ERC20: optimized gas costs in `_spendAllowance` (#5271) --- contracts/token/ERC20/ERC20.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC20/ERC20.sol b/contracts/token/ERC20/ERC20.sol index 0b707604c0c..6a9865e6aef 100644 --- a/contracts/token/ERC20/ERC20.sol +++ b/contracts/token/ERC20/ERC20.sol @@ -300,7 +300,7 @@ abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors { */ function _spendAllowance(address owner, address spender, uint256 value) internal virtual { uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { + if (currentAllowance < type(uint256).max) { if (currentAllowance < value) { revert ERC20InsufficientAllowance(spender, currentAllowance, value); } From c12cf86e0d85e089a8d3b2b9e733c2194ff89d45 Mon Sep 17 00:00:00 2001 From: cairo Date: Mon, 21 Oct 2024 13:44:22 +0200 Subject: [PATCH 75/81] Fuzz tampered tests for `ERC2771Forwarder` (#5258) Co-authored-by: Hadrien Croubois --- test/metatx/ERC2771Forwarder.t.sol | 270 +++++++++++++++++++-------- test/metatx/ERC2771Forwarder.test.js | 77 -------- 2 files changed, 192 insertions(+), 155 deletions(-) diff --git a/test/metatx/ERC2771Forwarder.t.sol b/test/metatx/ERC2771Forwarder.t.sol index d69b4750a39..e6baac6f030 100644 --- a/test/metatx/ERC2771Forwarder.t.sol +++ b/test/metatx/ERC2771Forwarder.t.sol @@ -5,21 +5,24 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {ERC2771Forwarder} from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; import {CallReceiverMockTrustingForwarder, CallReceiverMock} from "@openzeppelin/contracts/mocks/CallReceiverMock.sol"; - -struct ForwardRequest { - address from; - address to; - uint256 value; - uint256 gas; - uint256 nonce; - uint48 deadline; - bytes data; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +enum TamperType { + FROM, + TO, + VALUE, + DATA, + SIGNATURE } contract ERC2771ForwarderMock is ERC2771Forwarder { constructor(string memory name) ERC2771Forwarder(name) {} - function structHash(ForwardRequest calldata request) external view returns (bytes32) { + function forwardRequestStructHash( + ERC2771Forwarder.ForwardRequestData calldata request, + uint256 nonce + ) external view returns (bytes32) { return _hashTypedDataV4( keccak256( @@ -29,7 +32,7 @@ contract ERC2771ForwarderMock is ERC2771Forwarder { request.to, request.value, request.gas, - request.nonce, + nonce, request.deadline, keccak256(request.data) ) @@ -39,127 +42,238 @@ contract ERC2771ForwarderMock is ERC2771Forwarder { } contract ERC2771ForwarderTest is Test { + using ECDSA for bytes32; + ERC2771ForwarderMock internal _erc2771Forwarder; CallReceiverMockTrustingForwarder internal _receiver; - uint256 internal _signerPrivateKey; - uint256 internal _relayerPrivateKey; - - address internal _signer; - address internal _relayer; + uint256 internal _signerPrivateKey = 0xA11CE; + address internal _signer = vm.addr(_signerPrivateKey); uint256 internal constant _MAX_ETHER = 10_000_000; // To avoid overflow function setUp() public { _erc2771Forwarder = new ERC2771ForwarderMock("ERC2771Forwarder"); _receiver = new CallReceiverMockTrustingForwarder(address(_erc2771Forwarder)); + } - _signerPrivateKey = 0xA11CE; - _relayerPrivateKey = 0xB0B; - - _signer = vm.addr(_signerPrivateKey); - _relayer = vm.addr(_relayerPrivateKey); + // Forge a new ForwardRequestData + function _forgeRequestData() private view returns (ERC2771Forwarder.ForwardRequestData memory) { + return + _forgeRequestData({ + value: 0, + deadline: uint48(block.timestamp + 1), + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); } function _forgeRequestData( uint256 value, - uint256 nonce, uint48 deadline, bytes memory data ) private view returns (ERC2771Forwarder.ForwardRequestData memory) { - ForwardRequest memory request = ForwardRequest({ - from: _signer, - to: address(_receiver), - value: value, - gas: 30000, - nonce: nonce, - deadline: deadline, - data: data - }); - - bytes32 digest = _erc2771Forwarder.structHash(request); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, digest); - bytes memory signature = abi.encodePacked(r, s, v); - return ERC2771Forwarder.ForwardRequestData({ - from: request.from, - to: request.to, - value: request.value, - gas: request.gas, - deadline: request.deadline, - data: request.data, - signature: signature + from: _signer, + to: address(_receiver), + value: value, + gas: 30000, + deadline: deadline, + data: data, + signature: "" }); } - function testExecuteAvoidsETHStuck(uint256 initialBalance, uint256 value, bool targetReverts) public { - initialBalance = bound(initialBalance, 0, _MAX_ETHER); - value = bound(value, 0, _MAX_ETHER); + // Sign a ForwardRequestData (in place) for a given nonce. Also returns it for convenience. + function _signRequestData( + ERC2771Forwarder.ForwardRequestData memory request, + uint256 nonce + ) private view returns (ERC2771Forwarder.ForwardRequestData memory) { + bytes32 digest = _erc2771Forwarder.forwardRequestStructHash(request, nonce); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signerPrivateKey, digest); + request.signature = abi.encodePacked(r, s, v); + return request; + } - vm.deal(address(_erc2771Forwarder), initialBalance); + // Tamper a ForwardRequestData (in place). Also returns it for convenience. + function _tamperRequestData( + ERC2771Forwarder.ForwardRequestData memory request, + TamperType tamper + ) private returns (ERC2771Forwarder.ForwardRequestData memory) { + if (tamper == TamperType.FROM) request.from = vm.randomAddress(); + else if (tamper == TamperType.TO) request.to = vm.randomAddress(); + else if (tamper == TamperType.VALUE) request.value = vm.randomUint(); + else if (tamper == TamperType.DATA) request.data = vm.randomBytes(4); + else if (tamper == TamperType.SIGNATURE) request.signature = vm.randomBytes(65); + + return request; + } - uint256 nonce = _erc2771Forwarder.nonces(_signer); + // Predict the revert error for a tampered request, and expect it is emitted. + function _tamperedExpectRevert( + ERC2771Forwarder.ForwardRequestData memory request, + TamperType tamper, + uint256 nonce + ) private returns (ERC2771Forwarder.ForwardRequestData memory) { + if (tamper == TamperType.FROM) nonce = _erc2771Forwarder.nonces(request.from); + + // predict revert + if (tamper == TamperType.TO) { + vm.expectRevert( + abi.encodeWithSelector( + ERC2771Forwarder.ERC2771UntrustfulTarget.selector, + request.to, + address(_erc2771Forwarder) + ) + ); + } else { + (address recovered, , ) = _erc2771Forwarder.forwardRequestStructHash(request, nonce).tryRecover( + request.signature + ); + vm.expectRevert( + abi.encodeWithSelector(ERC2771Forwarder.ERC2771ForwarderInvalidSigner.selector, recovered, request.from) + ); + } + return request; + } - vm.deal(address(this), value); + function testExecuteAvoidsETHStuck(uint256 initialBalance, uint256 value, bool targetReverts) public { + initialBalance = bound(initialBalance, 0, _MAX_ETHER); + value = bound(value, 0, _MAX_ETHER); - ERC2771Forwarder.ForwardRequestData memory requestData = _forgeRequestData({ + // create and sign request + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData({ value: value, - nonce: nonce, deadline: uint48(block.timestamp + 1), data: targetReverts ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) : abi.encodeCall(CallReceiverMock.mockFunction, ()) }); + _signRequestData(request, _erc2771Forwarder.nonces(_signer)); - if (targetReverts) { - vm.expectRevert(); - } + vm.deal(address(_erc2771Forwarder), initialBalance); + vm.deal(address(this), request.value); + + if (targetReverts) vm.expectRevert(); + _erc2771Forwarder.execute{value: value}(request); - _erc2771Forwarder.execute{value: value}(requestData); assertEq(address(_erc2771Forwarder).balance, initialBalance); } function testExecuteBatchAvoidsETHStuck(uint256 initialBalance, uint256 batchSize, uint256 value) public { + uint256 seed = uint256(keccak256(abi.encodePacked(initialBalance, batchSize, value))); + batchSize = bound(batchSize, 1, 10); initialBalance = bound(initialBalance, 0, _MAX_ETHER); value = bound(value, 0, _MAX_ETHER); + address refundReceiver = address(0xebe); + uint256 refundExpected = 0; + uint256 nonce = _erc2771Forwarder.nonces(_signer); + + // create an sign array or requests (that may fail) + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](batchSize); + for (uint256 i = 0; i < batchSize; ++i) { + bool failure = (seed >> i) & 0x1 == 0x1; + + requests[i] = _forgeRequestData({ + value: value, + deadline: uint48(block.timestamp + 1), + data: failure + ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) + : abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); + _signRequestData(requests[i], nonce + i); + + refundExpected += SafeCast.toUint(failure) * value; + } + + // distribute ether vm.deal(address(_erc2771Forwarder), initialBalance); + vm.deal(address(this), value * batchSize); + + // execute batch + _erc2771Forwarder.executeBatch{value: value * batchSize}(requests, payable(refundReceiver)); + + // check balances + assertEq(address(_erc2771Forwarder).balance, initialBalance); + assertEq(refundReceiver.balance, refundExpected); + } + + function testVerifyTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + + // create request, sign, tamper + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData(); + _signRequestData(request, 0); + _tamperRequestData(request, tamper); + + // should not pass verification + assertFalse(_erc2771Forwarder.verify(request)); + } + + function testExecuteTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + + // create request, sign, tamper, expect execution revert + ERC2771Forwarder.ForwardRequestData memory request = _forgeRequestData(); + _signRequestData(request, 0); + _tamperRequestData(request, tamper); + _tamperedExpectRevert(request, tamper, 0); + + vm.deal(address(this), request.value); + _erc2771Forwarder.execute{value: request.value}(request); + } + + function testExecuteBatchTamperedValuesZeroReceiver(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); uint256 nonce = _erc2771Forwarder.nonces(_signer); - ERC2771Forwarder.ForwardRequestData[] memory batchRequestDatas = new ERC2771Forwarder.ForwardRequestData[]( - batchSize - ); + // create an sign array or requests + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](3); + for (uint256 i = 0; i < requests.length; ++i) { + requests[i] = _forgeRequestData({ + value: 0, + deadline: uint48(block.timestamp + 1), + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) + }); + _signRequestData(requests[i], nonce + i); + } - uint256 expectedRefund; + // tamper with request[1] and expect execution revert + _tamperRequestData(requests[1], tamper); + _tamperedExpectRevert(requests[1], tamper, nonce + 1); - for (uint256 i = 0; i < batchSize; ++i) { - bytes memory data; - bool succeed = uint256(keccak256(abi.encodePacked(initialBalance, i))) % 2 == 0; + vm.deal(address(this), requests[1].value); + _erc2771Forwarder.executeBatch{value: requests[1].value}(requests, payable(address(0))); + } - if (succeed) { - data = abi.encodeCall(CallReceiverMock.mockFunction, ()); - } else { - expectedRefund += value; - data = abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()); - } + function testExecuteBatchTamperedValues(uint8 _tamper) public { + TamperType tamper = _asTamper(_tamper); + uint256 nonce = _erc2771Forwarder.nonces(_signer); - batchRequestDatas[i] = _forgeRequestData({ - value: value, - nonce: nonce + i, + // create an sign array or requests + ERC2771Forwarder.ForwardRequestData[] memory requests = new ERC2771Forwarder.ForwardRequestData[](3); + for (uint256 i = 0; i < requests.length; ++i) { + requests[i] = _forgeRequestData({ + value: 0, deadline: uint48(block.timestamp + 1), - data: data + data: abi.encodeCall(CallReceiverMock.mockFunction, ()) }); + _signRequestData(requests[i], nonce + i); } - address payable refundReceiver = payable(address(0xebe)); - uint256 totalValue = value * batchSize; + // tamper with request[1] + _tamperRequestData(requests[1], tamper); - vm.deal(address(this), totalValue); - _erc2771Forwarder.executeBatch{value: totalValue}(batchRequestDatas, refundReceiver); + // should not revert + vm.expectCall(address(_receiver), abi.encodeCall(CallReceiverMock.mockFunction, ()), 1); - assertEq(address(_erc2771Forwarder).balance, initialBalance); - assertEq(refundReceiver.balance, expectedRefund); + vm.deal(address(this), requests[1].value); + _erc2771Forwarder.executeBatch{value: requests[1].value}(requests, payable(address(0xebe))); + } + + function _asTamper(uint8 _tamper) private pure returns (TamperType) { + return TamperType(bound(_tamper, uint8(TamperType.FROM), uint8(TamperType.SIGNATURE))); } } diff --git a/test/metatx/ERC2771Forwarder.test.js b/test/metatx/ERC2771Forwarder.test.js index 8653ad708cb..bf6cfd10c4b 100644 --- a/test/metatx/ERC2771Forwarder.test.js +++ b/test/metatx/ERC2771Forwarder.test.js @@ -52,19 +52,6 @@ async function fixture() { }; } -// values or function to tamper with a signed request. -const tamperedValues = { - from: ethers.Wallet.createRandom().address, - to: ethers.Wallet.createRandom().address, - value: ethers.parseEther('0.5'), - data: '0x1742', - signature: s => { - const t = ethers.toBeArray(s); - t[42] ^= 0xff; - return ethers.hexlify(t); - }, -}; - describe('ERC2771Forwarder', function () { beforeEach(async function () { Object.assign(this, await loadFixture(fixture)); @@ -81,15 +68,6 @@ describe('ERC2771Forwarder', function () { }); describe('with tampered values', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`returns false with tampered ${key}`, async function () { - const request = await this.forgeRequest(); - request[key] = typeof value == 'function' ? value(request[key]) : value; - - expect(await this.forwarder.verify(request)).to.be.false; - }); - } - it('returns false with valid signature for non-current nonce', async function () { const request = await this.forgeRequest({ nonce: 1337n }); expect(await this.forwarder.verify(request)).to.be.false; @@ -127,24 +105,6 @@ describe('ERC2771Forwarder', function () { }); describe('with tampered request', function () { - for (const [key, value] of Object.entries(tamperedValues)) { - it(`reverts with tampered ${key}`, async function () { - const request = await this.forgeRequest(); - request[key] = typeof value == 'function' ? value(request[key]) : value; - - const promise = this.forwarder.execute(request, { value: key == 'value' ? value : 0 }); - if (key != 'to') { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs(ethers.verifyTypedData(this.domain, this.types, request, request.signature), request.from); - } else { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771UntrustfulTarget') - .withArgs(request.to, this.forwarder); - } - }); - } - it('reverts with valid signature for non-current nonce', async function () { const request = await this.forgeRequest(); @@ -284,26 +244,6 @@ describe('ERC2771Forwarder', function () { this.refundReceiver = ethers.ZeroAddress; }); - for (const [key, value] of Object.entries(tamperedValues)) { - it(`reverts with at least one tampered request ${key}`, async function () { - this.requests[idx][key] = typeof value == 'function' ? value(this.requests[idx][key]) : value; - - const promise = this.forwarder.executeBatch(this.requests, this.refundReceiver, { value: this.value }); - if (key != 'to') { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs( - ethers.verifyTypedData(this.domain, this.types, this.requests[idx], this.requests[idx].signature), - this.requests[idx].from, - ); - } else { - await expect(promise) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771UntrustfulTarget') - .withArgs(this.requests[idx].to, this.forwarder); - } - }); - } - it('reverts with at least one valid signature for non-current nonce', async function () { // Execute first a request await this.forwarder.execute(this.requests[idx], { value: this.requests[idx].value }); @@ -340,23 +280,6 @@ describe('ERC2771Forwarder', function () { this.initialTamperedRequestNonce = await this.forwarder.nonces(this.requests[idx].from); }); - for (const [key, value] of Object.entries(tamperedValues)) { - it(`ignores a request with tampered ${key} and refunds its value`, async function () { - this.requests[idx][key] = typeof value == 'function' ? value(this.requests[idx][key]) : value; - - const events = await this.forwarder - .executeBatch(this.requests, this.refundReceiver, { value: requestsValue(this.requests) }) - .then(tx => tx.wait()) - .then(receipt => - receipt.logs.filter( - log => log?.fragment?.type == 'event' && log?.fragment?.name == 'ExecutedForwardRequest', - ), - ); - - expect(events).to.have.lengthOf(this.requests.length - 1); - }); - } - it('ignores a request with a valid signature for non-current nonce', async function () { // Execute first a request await this.forwarder.execute(this.requests[idx], { value: this.requests[idx].value }); From bc1df46eac3b897bc343952fccac3fad8a3095bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:31:20 +0200 Subject: [PATCH 76/81] Bump secp256k1 from 4.0.3 to 4.0.4 (#5275) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc147499814..da0e2f1042d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openzeppelin-solidity", - "version": "5.0.2", + "version": "5.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openzeppelin-solidity", - "version": "5.0.2", + "version": "5.1.0", "license": "MIT", "devDependencies": { "@changesets/changelog-github": "^0.5.0", @@ -8935,20 +8935,41 @@ "dev": true }, "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", "dev": true, "hasInstallScript": true, "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/secp256k1/node_modules/elliptic": { + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/secp256k1/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", From c343ee3768cc61a76a14d6f8402b1b19be1497fc Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 16:57:10 +0200 Subject: [PATCH 77/81] Bump pragma to 0.8.22 for all contract that depend on ERC1967Utils (#5273) --- .changeset/seven-donkeys-tap.md | 5 ++ .github/workflows/checks.yml | 4 ++ contracts/mocks/DummyImplementation.sol | 2 +- contracts/mocks/MerkleTreeMock.sol | 2 +- contracts/mocks/Stateless.sol | 2 +- .../mocks/governance/GovernorStorageMock.sol | 2 +- contracts/mocks/proxy/UUPSUpgradeableMock.sol | 2 +- contracts/proxy/ERC1967/ERC1967Proxy.sol | 2 +- contracts/proxy/ERC1967/ERC1967Utils.sol | 2 +- contracts/proxy/beacon/BeaconProxy.sol | 2 +- contracts/proxy/transparent/ProxyAdmin.sol | 2 +- .../TransparentUpgradeableProxy.sol | 2 +- contracts/proxy/utils/UUPSUpgradeable.sol | 2 +- package.json | 3 +- scripts/checks/inheritance-ordering.js | 9 ++-- scripts/checks/pragma-consistency.js | 49 +++++++++++++++++++ 16 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 .changeset/seven-donkeys-tap.md create mode 100755 scripts/checks/pragma-consistency.js diff --git a/.changeset/seven-donkeys-tap.md b/.changeset/seven-donkeys-tap.md new file mode 100644 index 00000000000..25d2305b9b8 --- /dev/null +++ b/.changeset/seven-donkeys-tap.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +Update some pragma directives to ensure that all file requirements match that of the files they import. diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9d338bb642f..a4d08c1da51 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -41,6 +41,8 @@ jobs: run: npm run test - name: Check linearisation of the inheritance graph run: npm run test:inheritance + - name: Check pragma consistency between files + run: npm run test:pragma - name: Check proceduraly generated contracts are up-to-date run: npm run test:generation - name: Compare gas costs @@ -68,6 +70,8 @@ jobs: run: npm run test - name: Check linearisation of the inheritance graph run: npm run test:inheritance + - name: Check pragma consistency between files + run: npm run test:pragma - name: Check storage layout uses: ./.github/actions/storage-layout continue-on-error: ${{ contains(github.event.pull_request.labels.*.name, 'breaking change') }} diff --git a/contracts/mocks/DummyImplementation.sol b/contracts/mocks/DummyImplementation.sol index 4925c89df4b..0f1147407f5 100644 --- a/contracts/mocks/DummyImplementation.sol +++ b/contracts/mocks/DummyImplementation.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol"; import {StorageSlot} from "../utils/StorageSlot.sol"; diff --git a/contracts/mocks/MerkleTreeMock.sol b/contracts/mocks/MerkleTreeMock.sol index 2454efa2f91..dcde6b65884 100644 --- a/contracts/mocks/MerkleTreeMock.sol +++ b/contracts/mocks/MerkleTreeMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import {MerkleTree} from "../utils/structs/MerkleTree.sol"; diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 98e7eaf7443..9e43232d587 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; // We keep these imports and a dummy contract just to we can run the test suite after transpilation. diff --git a/contracts/mocks/governance/GovernorStorageMock.sol b/contracts/mocks/governance/GovernorStorageMock.sol index 88c6bf906bf..26e0e10b57a 100644 --- a/contracts/mocks/governance/GovernorStorageMock.sol +++ b/contracts/mocks/governance/GovernorStorageMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {IGovernor, Governor} from "../../governance/Governor.sol"; import {GovernorTimelockControl} from "../../governance/extensions/GovernorTimelockControl.sol"; diff --git a/contracts/mocks/proxy/UUPSUpgradeableMock.sol b/contracts/mocks/proxy/UUPSUpgradeableMock.sol index a5f2d4a25d8..8c5641e6ca3 100644 --- a/contracts/mocks/proxy/UUPSUpgradeableMock.sol +++ b/contracts/mocks/proxy/UUPSUpgradeableMock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {UUPSUpgradeable} from "../../proxy/utils/UUPSUpgradeable.sol"; import {ERC1967Utils} from "../../proxy/ERC1967/ERC1967Utils.sol"; diff --git a/contracts/proxy/ERC1967/ERC1967Proxy.sol b/contracts/proxy/ERC1967/ERC1967Proxy.sol index 4f51cd9578b..cad9eb5ab7e 100644 --- a/contracts/proxy/ERC1967/ERC1967Proxy.sol +++ b/contracts/proxy/ERC1967/ERC1967Proxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Proxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {Proxy} from "../Proxy.sol"; import {ERC1967Utils} from "./ERC1967Utils.sol"; diff --git a/contracts/proxy/ERC1967/ERC1967Utils.sol b/contracts/proxy/ERC1967/ERC1967Utils.sol index 1f320135277..287bb6beee2 100644 --- a/contracts/proxy/ERC1967/ERC1967Utils.sol +++ b/contracts/proxy/ERC1967/ERC1967Utils.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/ERC1967/ERC1967Utils.sol) -pragma solidity ^0.8.21; +pragma solidity ^0.8.22; import {IBeacon} from "../beacon/IBeacon.sol"; import {IERC1967} from "../../interfaces/IERC1967.sol"; diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol index 2606f21db08..e38b9d891cf 100644 --- a/contracts/proxy/beacon/BeaconProxy.sol +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/beacon/BeaconProxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {IBeacon} from "./IBeacon.sol"; import {Proxy} from "../Proxy.sol"; diff --git a/contracts/proxy/transparent/ProxyAdmin.sol b/contracts/proxy/transparent/ProxyAdmin.sol index 31772350392..2a60edfe987 100644 --- a/contracts/proxy/transparent/ProxyAdmin.sol +++ b/contracts/proxy/transparent/ProxyAdmin.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/ProxyAdmin.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; import {Ownable} from "../../access/Ownable.sol"; diff --git a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol index a35a725f2b3..7342d9f8f0a 100644 --- a/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +++ b/contracts/proxy/transparent/TransparentUpgradeableProxy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/transparent/TransparentUpgradeableProxy.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol"; diff --git a/contracts/proxy/utils/UUPSUpgradeable.sol b/contracts/proxy/utils/UUPSUpgradeable.sol index dc799962cb3..745c56fa5d2 100644 --- a/contracts/proxy/utils/UUPSUpgradeable.sol +++ b/contracts/proxy/utils/UUPSUpgradeable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol) -pragma solidity ^0.8.20; +pragma solidity ^0.8.22; import {IERC1822Proxiable} from "../../interfaces/draft-IERC1822.sol"; import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol"; diff --git a/package.json b/package.json index f9b88273362..618a7efb868 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,9 @@ "generate": "scripts/generate/run.js", "version": "scripts/release/version.sh", "test": "hardhat test", - "test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*", "test:generation": "scripts/checks/generation.sh", + "test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*", + "test:pragma": "scripts/checks/pragma-consistency.js artifacts/build-info/*", "gas-report": "env ENABLE_GAS_REPORT=true npm run test", "slither": "npm run clean && slither ." }, diff --git a/scripts/checks/inheritance-ordering.js b/scripts/checks/inheritance-ordering.js index 72aa37ef7b3..4ed2deec454 100755 --- a/scripts/checks/inheritance-ordering.js +++ b/scripts/checks/inheritance-ordering.js @@ -2,9 +2,13 @@ const path = require('path'); const graphlib = require('graphlib'); +const match = require('micromatch'); const { findAll } = require('solidity-ast/utils'); const { _: artifacts } = require('yargs').argv; +// files to skip +const skipPatterns = ['contracts-exposed/**', 'contracts/mocks/**']; + for (const artifact of artifacts) { const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); @@ -13,10 +17,7 @@ for (const artifact of artifacts) { const linearized = []; for (const source in solcOutput.contracts) { - if (['contracts-exposed/', 'contracts/mocks/'].some(pattern => source.startsWith(pattern))) { - continue; - } - + if (match.any(source, skipPatterns)) continue; for (const contractDef of findAll('ContractDefinition', solcOutput.sources[source].ast)) { names[contractDef.id] = contractDef.name; linearized.push(contractDef.linearizedBaseContracts); diff --git a/scripts/checks/pragma-consistency.js b/scripts/checks/pragma-consistency.js new file mode 100755 index 00000000000..f2f3c548f59 --- /dev/null +++ b/scripts/checks/pragma-consistency.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node + +const path = require('path'); +const semver = require('semver'); +const match = require('micromatch'); +const { findAll } = require('solidity-ast/utils'); +const { _: artifacts } = require('yargs').argv; + +// files to skip +const skipPatterns = ['contracts-exposed/**', 'contracts/mocks/WithInit.sol']; + +for (const artifact of artifacts) { + const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); + + const pragma = {}; + + // Extract pragma directive for all files + for (const source in solcOutput.contracts) { + if (match.any(source, skipPatterns)) continue; + for (const { literals } of findAll('PragmaDirective', solcOutput.sources[source].ast)) { + // There should only be one. + const [first, ...rest] = literals; + if (first === 'solidity') pragma[source] = rest.join(''); + } + } + + // Compare the pragma directive of the file, to that of the files it imports + for (const source in solcOutput.contracts) { + if (match.any(source, skipPatterns)) continue; + // minimum version of the compiler that matches source's pragma + const minVersion = semver.minVersion(pragma[source]); + // loop over all imports in source + for (const { absolutePath } of findAll('ImportDirective', solcOutput.sources[source].ast)) { + // So files that only import without declaring anything cause issues, because they don't shop in in "pragma" + if (!pragma[absolutePath]) continue; + // Check that the minVersion for source satisfies the requirements of the imported files + if (!semver.satisfies(minVersion, pragma[absolutePath])) { + console.log( + `- ${source} uses ${pragma[source]} but depends on ${absolutePath} that requires ${pragma[absolutePath]}`, + ); + process.exitCode = 1; + } + } + } +} + +if (!process.exitCode) { + console.log('Pragma directives are consistent.'); +} From 29f40597739707296e27d01ff368e0833029fc67 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Tue, 22 Oct 2024 23:01:26 +0200 Subject: [PATCH 78/81] Bump pragma of files that depend on mcopy (#5276) --- contracts/mocks/Stateless.sol | 2 +- contracts/utils/Bytes.sol | 2 +- contracts/utils/CAIP10.sol | 2 +- contracts/utils/CAIP2.sol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 9e43232d587..4ec36418466 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.22; +pragma solidity ^0.8.24; // We keep these imports and a dummy contract just to we can run the test suite after transpilation. diff --git a/contracts/utils/Bytes.sol b/contracts/utils/Bytes.sol index 84e5a3ed51f..6fe49fd8d14 100644 --- a/contracts/utils/Bytes.sol +++ b/contracts/utils/Bytes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {Math} from "./math/Math.sol"; diff --git a/contracts/utils/CAIP10.sol b/contracts/utils/CAIP10.sol index e9ed17305b6..95aa2a97737 100644 --- a/contracts/utils/CAIP10.sol +++ b/contracts/utils/CAIP10.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {SafeCast} from "./math/SafeCast.sol"; import {Bytes} from "./Bytes.sol"; diff --git a/contracts/utils/CAIP2.sol b/contracts/utils/CAIP2.sol index 13a98f58a46..cfad67e00d0 100644 --- a/contracts/utils/CAIP2.sol +++ b/contracts/utils/CAIP2.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.24; import {SafeCast} from "./math/SafeCast.sol"; import {Bytes} from "./Bytes.sol"; From 205f59e9b6b9960d2dc61620457a4cfb05684a4a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:31:39 +0200 Subject: [PATCH 79/81] Update dependency eslint to v9 (#4996) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Hadrien Croubois Co-authored-by: cairo --- .eslintrc | 20 - eslint.config.mjs | 26 ++ package-lock.json | 423 +++++++++++------- package.json | 8 +- scripts/generate/run.js | 2 +- scripts/generate/templates/Checkpoints.js | 1 - scripts/generate/templates/Checkpoints.t.js | 1 - scripts/generate/templates/EnumerableMap.js | 2 - scripts/generate/templates/EnumerableSet.js | 2 - scripts/generate/templates/MerkleProof.js | 2 - scripts/generate/templates/SafeCast.js | 2 - scripts/release/workflow/state.js | 4 +- scripts/update-docs-branch.js | 2 +- test/helpers/storage.js | 2 +- .../TransparentUpgradeableProxy.behaviour.js | 4 +- .../extensions/ERC721Consecutive.test.js | 16 +- test/utils/Address.test.js | 5 +- test/utils/Create2.test.js | 4 +- test/utils/cryptography/ECDSA.test.js | 8 +- .../SupportsInterface.behavior.js | 4 +- 20 files changed, 310 insertions(+), 228 deletions(-) delete mode 100644 .eslintrc create mode 100644 eslint.config.mjs diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a5418c5e4e1..00000000000 --- a/.eslintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "root": true, - "extends" : [ - "eslint:recommended", - "prettier", - ], - "env": { - "es2022": true, - "browser": true, - "node": true, - "mocha": true, - }, - "globals" : { - "artifacts": "readonly", - "contract": "readonly", - "web3": "readonly", - "extendEnvironment": "readonly", - "expect": "readonly", - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..00fcc95bb4d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,26 @@ +import js from '@eslint/js'; +import { includeIgnoreFile } from '@eslint/compat'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; +import path from 'path'; + +export default [ + js.configs.recommended, + prettier, + { + languageOptions: { + ecmaVersion: 2022, + globals: { + ...globals.browser, + ...globals.mocha, + ...globals.node, + artifacts: 'readonly', + contract: 'readonly', + web3: 'readonly', + extendEnvironment: 'readonly', + expect: 'readonly', + }, + }, + }, + includeIgnoreFile(path.resolve(import.meta.dirname, '.gitignore')), +]; diff --git a/package-lock.json b/package-lock.json index da0e2f1042d..6066b617fda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@changesets/cli": "^2.26.0", "@changesets/pre": "^2.0.0", "@changesets/read": "^0.6.0", + "@eslint/compat": "^1.2.1", "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", @@ -21,10 +22,11 @@ "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", - "eslint": "^8.30.0", + "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", "glob": "^11.0.0", + "globals": "^15.3.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", "hardhat-exposed": "^0.3.15", @@ -571,24 +573,69 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.1.tgz", + "integrity": "sha512-JbHG2TWuCeNzh87fXo+/46Z1LEo9DBA9T188d0fZgGxAD+cNyS6sx9fdiyxjGPBMyQVRlCutTByZ6a5+YMkF7g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", + "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -596,7 +643,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -608,6 +655,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -621,12 +680,36 @@ } }, "node_modules/@eslint/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", + "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", + "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@ethereumjs/rlp": { @@ -1111,18 +1194,28 @@ "pnpm": "7.5.1" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1138,11 +1231,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2450,6 +2551,13 @@ "@types/chai": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -2475,6 +2583,13 @@ "ci-info": "^3.1.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2560,10 +2675,11 @@ } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2576,6 +2692,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -3970,18 +4087,6 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", @@ -4277,57 +4382,64 @@ } }, "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", + "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.49.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.7.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.13.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -4343,16 +4455,17 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4375,6 +4488,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -4385,17 +4499,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4412,6 +4521,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -4423,13 +4533,15 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4437,11 +4549,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -4458,6 +4584,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -4470,27 +4597,17 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -4506,6 +4623,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -4521,6 +4639,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -4529,17 +4648,31 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.1.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4575,6 +4708,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -4881,15 +5015,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -4937,59 +5072,25 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", - "dev": true, - "dependencies": { - "flatted": "^3.2.7", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.6", @@ -5326,15 +5427,13 @@ } }, "node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "15.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", + "integrity": "sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5424,12 +5523,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/graphlib": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", @@ -6643,15 +6736,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -6917,10 +7001,11 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } diff --git a/package.json b/package.json index 618a7efb868..19c54c92a05 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "prepare-docs": "scripts/prepare-docs.sh", "lint": "npm run lint:js && npm run lint:sol", "lint:fix": "npm run lint:js:fix && npm run lint:sol:fix", - "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint --ignore-path .gitignore .", - "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint --ignore-path .gitignore . --fix", + "lint:js": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --check && eslint .", + "lint:js:fix": "prettier --log-level warn --ignore-path .gitignore '**/*.{js,ts}' --write && eslint . --fix", "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", @@ -55,6 +55,7 @@ "@changesets/cli": "^2.26.0", "@changesets/pre": "^2.0.0", "@changesets/read": "^0.6.0", + "@eslint/compat": "^1.2.1", "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-network-helpers": "^1.0.3", @@ -63,9 +64,10 @@ "@openzeppelin/upgrade-safe-transpiler": "^0.3.32", "@openzeppelin/upgrades-core": "^1.20.6", "chai": "^4.2.0", - "eslint": "^8.30.0", + "eslint": "^9.0.0", "eslint-config-prettier": "^9.0.0", "ethers": "^6.7.1", + "globals": "^15.3.0", "glob": "^11.0.0", "graphlib": "^2.1.8", "hardhat": "^2.22.2", diff --git a/scripts/generate/run.js b/scripts/generate/run.js index e4947eb12bd..6779c93f44b 100755 --- a/scripts/generate/run.js +++ b/scripts/generate/run.js @@ -8,7 +8,7 @@ const format = require('./format-lines'); function getVersion(path) { try { return fs.readFileSync(path, 'utf8').match(/\/\/ OpenZeppelin Contracts \(last updated v[^)]+\)/)[0]; - } catch (err) { + } catch { return null; } } diff --git a/scripts/generate/templates/Checkpoints.js b/scripts/generate/templates/Checkpoints.js index d418b1177d1..7ec4a72532a 100644 --- a/scripts/generate/templates/Checkpoints.js +++ b/scripts/generate/templates/Checkpoints.js @@ -227,7 +227,6 @@ function _unsafeAccess( } } `; -/* eslint-enable max-len */ // GENERATE module.exports = format( diff --git a/scripts/generate/templates/Checkpoints.t.js b/scripts/generate/templates/Checkpoints.t.js index dd564e59bdd..edd2e9f98aa 100644 --- a/scripts/generate/templates/Checkpoints.t.js +++ b/scripts/generate/templates/Checkpoints.t.js @@ -11,7 +11,6 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; `; -/* eslint-disable max-len */ const template = opts => `\ using Checkpoints for Checkpoints.${opts.historyTypeName}; diff --git a/scripts/generate/templates/EnumerableMap.js b/scripts/generate/templates/EnumerableMap.js index fc896f8fb9a..c9cad6c1bc8 100644 --- a/scripts/generate/templates/EnumerableMap.js +++ b/scripts/generate/templates/EnumerableMap.js @@ -2,7 +2,6 @@ const format = require('../format-lines'); const { fromBytes32, toBytes32 } = require('./conversion'); const { TYPES } = require('./EnumerableMap.opts'); -/* eslint-disable max-len */ const header = `\ pragma solidity ^0.8.20; @@ -52,7 +51,6 @@ import {EnumerableSet} from "./EnumerableSet.sol"; * ==== */ `; -/* eslint-enable max-len */ const defaultMap = `\ // To implement this library for multiple types with as little code repetition as possible, we write it in diff --git a/scripts/generate/templates/EnumerableSet.js b/scripts/generate/templates/EnumerableSet.js index 351466b1313..02eccd0df11 100644 --- a/scripts/generate/templates/EnumerableSet.js +++ b/scripts/generate/templates/EnumerableSet.js @@ -2,7 +2,6 @@ const format = require('../format-lines'); const { fromBytes32, toBytes32 } = require('./conversion'); const { TYPES } = require('./EnumerableSet.opts'); -/* eslint-disable max-len */ const header = `\ pragma solidity ^0.8.20; @@ -41,7 +40,6 @@ pragma solidity ^0.8.20; * ==== */ `; -/* eslint-enable max-len */ const defaultSet = `\ // To implement this library for multiple types with as little code diff --git a/scripts/generate/templates/MerkleProof.js b/scripts/generate/templates/MerkleProof.js index 6711a87e28c..890b2febac4 100644 --- a/scripts/generate/templates/MerkleProof.js +++ b/scripts/generate/templates/MerkleProof.js @@ -43,7 +43,6 @@ const errors = `\ error MerkleProofInvalidMultiproof(); `; -/* eslint-disable max-len */ const templateProof = ({ suffix, location, visibility, hash }) => `\ /** * @dev Returns true if a \`leaf\` can be proved to be a part of a Merkle tree @@ -172,7 +171,6 @@ function processMultiProof${suffix}(${formatArgsMultiline( } } `; -/* eslint-enable max-len */ // GENERATE module.exports = format( diff --git a/scripts/generate/templates/SafeCast.js b/scripts/generate/templates/SafeCast.js index a3b32e3f00a..21000cf4a62 100644 --- a/scripts/generate/templates/SafeCast.js +++ b/scripts/generate/templates/SafeCast.js @@ -61,7 +61,6 @@ function toUint${length}(uint256 value) internal pure returns (uint${length}) { } `; -/* eslint-disable max-len */ const toIntDownCast = length => `\ /** * @dev Returns the downcasted int${length} from int256, reverting on @@ -81,7 +80,6 @@ function toInt${length}(int256 value) internal pure returns (int${length} downca } } `; -/* eslint-enable max-len */ const toInt = length => `\ /** diff --git a/scripts/release/workflow/state.js b/scripts/release/workflow/state.js index 914e8de0222..002f7774d47 100644 --- a/scripts/release/workflow/state.js +++ b/scripts/release/workflow/state.js @@ -106,7 +106,7 @@ async function readChangesetState(cwd = process.cwd()) { }; } -async function isPublishedOnNpm(package, version) { - const res = await fetch(`https://registry.npmjs.com/${package}/${version}`); +async function isPublishedOnNpm(packageName, version) { + const res = await fetch(`https://registry.npmjs.com/${packageName}/${version}`); return res.ok; } diff --git a/scripts/update-docs-branch.js b/scripts/update-docs-branch.js index 324ba0c67ca..cf61daad835 100644 --- a/scripts/update-docs-branch.js +++ b/scripts/update-docs-branch.js @@ -6,7 +6,7 @@ const run = cmd => { const tryRead = cmd => { try { return read(cmd); - } catch (e) { + } catch { return undefined; } }; diff --git a/test/helpers/storage.js b/test/helpers/storage.js index a75a3060d4b..466cbb10c56 100644 --- a/test/helpers/storage.js +++ b/test/helpers/storage.js @@ -26,7 +26,7 @@ const upgradeableSlot = (contractName, offset) => { // Try to get the artifact paths, will throw if it doesn't exist artifacts._getArtifactPathSync(`${contractName}Upgradeable`); return offset + ethers.toBigInt(erc7201Slot(erc7201format(contractName))); - } catch (_) { + } catch { return offset; } }; diff --git a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js index d90bd56e2dd..8e1d62eaad3 100644 --- a/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ b/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js @@ -243,10 +243,10 @@ module.exports = function shouldBehaveLikeTransparentUpgradeableProxy() { }); it('proxy admin cannot call delegated functions', async function () { - const interface = await ethers.getContractFactory('TransparentUpgradeableProxy'); + const factory = await ethers.getContractFactory('TransparentUpgradeableProxy'); await expect(this.instance.connect(this.proxyAdminAsSigner).delegatedFunction()).to.be.revertedWithCustomError( - interface, + factory, 'ProxyDeniedAdminAccess', ); }); diff --git a/test/token/ERC721/extensions/ERC721Consecutive.test.js b/test/token/ERC721/extensions/ERC721Consecutive.test.js index d2eda944ccd..f62d6dc5b12 100644 --- a/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ b/test/token/ERC721/extensions/ERC721Consecutive.test.js @@ -202,35 +202,35 @@ describe('ERC721Consecutive', function () { const receiver = ethers.Wallet.createRandom(); it('cannot mint a batch larger than 5000', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveMock'); await expect(ethers.deployContract('$ERC721ConsecutiveMock', [name, symbol, 0, [], [receiver], [5001n]])) - .to.be.revertedWithCustomError({ interface }, 'ERC721ExceededMaxBatchMint') + .to.be.revertedWithCustomError(factory, 'ERC721ExceededMaxBatchMint') .withArgs(5001n, 5000n); }); it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); await expect( ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721ForbiddenMint'); }); it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); await expect( ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721ForbiddenMint'); }); it('consecutive mint not compatible with enumerability', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveEnumerableMock'); + const factory = await ethers.getContractFactory('$ERC721ConsecutiveEnumerableMock'); await expect( ethers.deployContract('$ERC721ConsecutiveEnumerableMock', [name, symbol, [receiver], [100n]]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721EnumerableForbiddenBatchMint'); + ).to.be.revertedWithCustomError(factory, 'ERC721EnumerableForbiddenBatchMint'); }); }); }); diff --git a/test/utils/Address.test.js b/test/utils/Address.test.js index 21775397ab2..8307a923e69 100644 --- a/test/utils/Address.test.js +++ b/test/utils/Address.test.js @@ -126,8 +126,9 @@ describe('Address', function () { }); it('reverts when function does not exist', async function () { - const interface = new ethers.Interface(['function mockFunctionDoesNotExist()']); - const call = interface.encodeFunctionData('mockFunctionDoesNotExist'); + const call = new ethers.Interface(['function mockFunctionDoesNotExist()']).encodeFunctionData( + 'mockFunctionDoesNotExist', + ); await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithCustomError(this.mock, 'FailedCall'); }); diff --git a/test/utils/Create2.test.js b/test/utils/Create2.test.js index 152fdbdf4c1..99c47a0e34a 100644 --- a/test/utils/Create2.test.js +++ b/test/utils/Create2.test.js @@ -14,13 +14,13 @@ async function fixture() { // We use a vesting wallet, with 3 constructor arguments. const constructorByteCode = await ethers .getContractFactory('VestingWallet') - .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([other.address, 0n, 0n])])); + .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([other.address, 0n, 0n])])); // Bytecode for deploying a contract that has no constructor log. // Here we use the Create2 helper factory. const constructorLessBytecode = await ethers .getContractFactory('$Create2') - .then(({ bytecode, interface }) => ethers.concat([bytecode, interface.encodeDeploy([])])); + .then(factory => ethers.concat([factory.bytecode, factory.interface.encodeDeploy([])])); const mockFactory = await ethers.getContractFactory('ConstructorMock'); diff --git a/test/utils/cryptography/ECDSA.test.js b/test/utils/cryptography/ECDSA.test.js index 6b24bdbceaa..0f2879a86c4 100644 --- a/test/utils/cryptography/ECDSA.test.js +++ b/test/utils/cryptography/ECDSA.test.js @@ -26,7 +26,6 @@ describe('ECDSA', function () { it('with long signature', async function () { await expect( - // eslint-disable-next-line max-len this.mock.$recover( TEST_MESSAGE, '0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', @@ -61,7 +60,6 @@ describe('ECDSA', function () { }); it('reverts with invalid signature', async function () { - // eslint-disable-next-line max-len const signature = '0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c'; await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError( @@ -73,7 +71,7 @@ describe('ECDSA', function () { describe('with v=27 signature', function () { const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c'; - // eslint-disable-next-line max-len + const signatureWithoutV = '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892'; @@ -133,7 +131,7 @@ describe('ECDSA', function () { describe('with v=28 signature', function () { const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E'; - // eslint-disable-next-line max-len + const signatureWithoutV = '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0'; @@ -193,7 +191,7 @@ describe('ECDSA', function () { it('reverts with high-s value signature', async function () { const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; - // eslint-disable-next-line max-len + const highSSignature = '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b'; diff --git a/test/utils/introspection/SupportsInterface.behavior.js b/test/utils/introspection/SupportsInterface.behavior.js index c2bd1a479b2..8a7bc4b5e2b 100644 --- a/test/utils/introspection/SupportsInterface.behavior.js +++ b/test/utils/introspection/SupportsInterface.behavior.js @@ -103,8 +103,8 @@ function shouldSupportInterfaces(interfaces = []) { describe('when the interfaceId is supported', function () { it('uses less than 30k gas', async function () { for (const k of interfaces) { - const interface = INTERFACE_IDS[k] ?? k; - expect(await this.contractUnderTest.supportsInterface.estimateGas(interface)).to.lte(30_000n); + const interfaceId = INTERFACE_IDS[k] ?? k; + expect(await this.contractUnderTest.supportsInterface.estimateGas(interfaceId)).to.lte(30_000n); } }); From 2fa4d103fe374335b15f9c54e7eec32e36ea89b4 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 23 Oct 2024 09:16:10 +0200 Subject: [PATCH 80/81] Add NoncesKeyed variant (#5272) --- .changeset/lovely-dodos-lay.md | 5 ++ contracts/mocks/Stateless.sol | 2 + contracts/utils/NoncesKeyed.sol | 60 +++++++++++++ contracts/utils/README.adoc | 3 + test/utils/Nonces.behavior.js | 152 ++++++++++++++++++++++++++++++++ test/utils/Nonces.test.js | 65 +------------- test/utils/NoncesKeyed.test.js | 17 ++++ 7 files changed, 242 insertions(+), 62 deletions(-) create mode 100644 .changeset/lovely-dodos-lay.md create mode 100644 contracts/utils/NoncesKeyed.sol create mode 100644 test/utils/Nonces.behavior.js create mode 100644 test/utils/NoncesKeyed.test.js diff --git a/.changeset/lovely-dodos-lay.md b/.changeset/lovely-dodos-lay.md new file mode 100644 index 00000000000..da225132630 --- /dev/null +++ b/.changeset/lovely-dodos-lay.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`NoncesKeyed`: Add a variant of `Nonces` that implements the ERC-4337 entrypoint nonce system. diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 4ec36418466..3040ddcdf8e 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -29,6 +29,8 @@ import {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol"; +import {Nonces} from "../utils/Nonces.sol"; +import {NoncesKeyed} from "../utils/NoncesKeyed.sol"; import {P256} from "../utils/cryptography/P256.sol"; import {Panic} from "../utils/Panic.sol"; import {Packing} from "../utils/Packing.sol"; diff --git a/contracts/utils/NoncesKeyed.sol b/contracts/utils/NoncesKeyed.sol new file mode 100644 index 00000000000..133b0c3fe4a --- /dev/null +++ b/contracts/utils/NoncesKeyed.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Nonces} from "./Nonces.sol"; + +/** + * @dev Alternative to {Nonces}, that support key-ed nonces. + * + * Follows the https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337's semi-abstracted nonce system]. + */ +abstract contract NoncesKeyed is Nonces { + mapping(address owner => mapping(uint192 key => uint64)) private _nonces; + + /// @dev Returns the next unused nonce for an address and key. Result contains the key prefix. + function nonces(address owner, uint192 key) public view virtual returns (uint256) { + return key == 0 ? nonces(owner) : ((uint256(key) << 64) | _nonces[owner][key]); + } + + /** + * @dev Consumes the next unused nonce for an address and key. + * + * Returns the current value without the key prefix. Consumed nonce is increased, so calling this functions twice + * with the same arguments will return different (sequential) results. + */ + function _useNonce(address owner, uint192 key) internal virtual returns (uint256) { + // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be + // decremented or reset. This guarantees that the nonce never overflows. + unchecked { + // It is important to do x++ and not ++x here. + return key == 0 ? _useNonce(owner) : _nonces[owner][key]++; + } + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + * + * This version takes the key and the nonce in a single uint256 parameter: + * - use the first 8 bytes for the key + * - use the last 24 bytes for the nonce + */ + function _useCheckedNonce(address owner, uint256 keyNonce) internal virtual override { + _useCheckedNonce(owner, uint192(keyNonce >> 64), uint64(keyNonce)); + } + + /** + * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`. + * + * This version takes the key and the nonce as two different parameters. + */ + function _useCheckedNonce(address owner, uint192 key, uint64 nonce) internal virtual { + if (key == 0) { + super._useCheckedNonce(owner, nonce); + } else { + uint256 current = _useNonce(owner, key); + if (nonce != current) { + revert InvalidAccountNonce(owner, current); + } + } + } +} diff --git a/contracts/utils/README.adoc b/contracts/utils/README.adoc index eeef84aae7c..432b806e3c4 100644 --- a/contracts/utils/README.adoc +++ b/contracts/utils/README.adoc @@ -18,6 +18,7 @@ Miscellaneous contracts and libraries containing utility functions you can use t * {ReentrancyGuardTransient}: Variant of {ReentrancyGuard} that uses transient storage (https://eips.ethereum.org/EIPS/eip-1153[EIP-1153]). * {Pausable}: A common emergency response mechanism that can pause functionality while a remediation is pending. * {Nonces}: Utility for tracking and verifying address nonces that only increment. + * {NoncesKeyed}: Alternative to {Nonces}, that support key-ed nonces following https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support[ERC-4337 speciciations]. * {ERC165}, {ERC165Checker}: Utilities for inspecting interfaces supported by contracts. * {BitMaps}: A simple library to manage boolean value mapped to a numerical index in an efficient way. * {EnumerableMap}: A type like Solidity's https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`], but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`). @@ -85,6 +86,8 @@ Because Solidity does not support generic types, {EnumerableMap} and {Enumerable {{Nonces}} +{{NoncesKeyed}} + == Introspection This set of interfaces and contracts deal with https://en.wikipedia.org/wiki/Type_introspection[type introspection] of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract's _interface_. diff --git a/test/utils/Nonces.behavior.js b/test/utils/Nonces.behavior.js new file mode 100644 index 00000000000..17073966427 --- /dev/null +++ b/test/utils/Nonces.behavior.js @@ -0,0 +1,152 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); + +function shouldBehaveLikeNonces() { + describe('should behave like Nonces', function () { + const sender = ethers.Wallet.createRandom(); + const other = ethers.Wallet.createRandom(); + + it('gets a nonce', async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + }); + + describe('_useNonce', function () { + it('increments a nonce', async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + + const eventName = ['return$_useNonce', 'return$_useNonce_address'].find(name => + this.mock.interface.getEvent(name), + ); + + await expect(this.mock.$_useNonce(sender)).to.emit(this.mock, eventName).withArgs(0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + }); + + it("increments only sender's nonce", async function () { + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + + await this.mock.$_useNonce(sender); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + }); + }); + + describe('_useCheckedNonce', function () { + it('increments a nonce', async function () { + // current nonce is 0n + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + + await this.mock.$_useCheckedNonce(sender, 0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + }); + + it("increments only sender's nonce", async function () { + // current nonce is 0n + expect(this.mock.nonces(sender)).to.eventually.equal(0n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + + await this.mock.$_useCheckedNonce(sender, 0n); + + expect(this.mock.nonces(sender)).to.eventually.equal(1n); + expect(this.mock.nonces(other)).to.eventually.equal(0n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender); + + await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 1n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, currentNonce); + }); + }); + }); +} + +function shouldBehaveLikeNoncesKeyed() { + describe('should support nonces with keys', function () { + const sender = ethers.Wallet.createRandom(); + + const keyOffset = key => key << 64n; + + it('gets a nonce', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + }); + + describe('_useNonce', function () { + it('default variant uses key 0', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + + await expect(this.mock.$_useNonce(sender)).to.emit(this.mock, 'return$_useNonce_address').withArgs(0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(0n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(1n); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 2n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + }); + + it('use nonce at another key', async function () { + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(0n); + + await expect(this.mock.$_useNonce(sender, ethers.Typed.uint192(17n))) + .to.emit(this.mock, 'return$_useNonce_address_uint192') + .withArgs(1n); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(keyOffset(0n) + 0n); + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(keyOffset(17n) + 2n); + }); + }); + + describe('_useCheckedNonce', function () { + it('default variant uses key 0', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(0n)); + + await this.mock.$_useCheckedNonce(sender, currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(0n))).to.eventually.equal(currentNonce + 1n); + }); + + it('use nonce at another key', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(17n)); + + await this.mock.$_useCheckedNonce(sender, currentNonce); + + expect(this.mock.nonces(sender, ethers.Typed.uint192(17n))).to.eventually.equal(currentNonce + 1n); + }); + + it('reverts when nonce is not the expected', async function () { + const currentNonce = await this.mock.nonces(sender, ethers.Typed.uint192(42n)); + + // use and increment + await this.mock.$_useCheckedNonce(sender, currentNonce); + + // reuse same nonce + await expect(this.mock.$_useCheckedNonce(sender, currentNonce)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, 1); + + // use "future" nonce too early + await expect(this.mock.$_useCheckedNonce(sender, currentNonce + 10n)) + .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') + .withArgs(sender, 1); + }); + }); + }); +} + +module.exports = { + shouldBehaveLikeNonces, + shouldBehaveLikeNoncesKeyed, +}; diff --git a/test/utils/Nonces.test.js b/test/utils/Nonces.test.js index 2cb4798dea6..85aa7358a00 100644 --- a/test/utils/Nonces.test.js +++ b/test/utils/Nonces.test.js @@ -1,13 +1,10 @@ const { ethers } = require('hardhat'); -const { expect } = require('chai'); const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { shouldBehaveLikeNonces } = require('./Nonces.behavior'); async function fixture() { - const [sender, other] = await ethers.getSigners(); - const mock = await ethers.deployContract('$Nonces'); - - return { sender, other, mock }; + return { mock }; } describe('Nonces', function () { @@ -15,61 +12,5 @@ describe('Nonces', function () { Object.assign(this, await loadFixture(fixture)); }); - it('gets a nonce', async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - }); - - describe('_useNonce', function () { - it('increments a nonce', async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - - await expect(await this.mock.$_useNonce(this.sender)) - .to.emit(this.mock, 'return$_useNonce') - .withArgs(0n); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - }); - - it("increments only sender's nonce", async function () { - expect(await this.mock.nonces(this.sender)).to.equal(0n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - - await this.mock.$_useNonce(this.sender); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - }); - }); - - describe('_useCheckedNonce', function () { - it('increments a nonce', async function () { - const currentNonce = await this.mock.nonces(this.sender); - - expect(currentNonce).to.equal(0n); - - await this.mock.$_useCheckedNonce(this.sender, currentNonce); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - }); - - it("increments only sender's nonce", async function () { - const currentNonce = await this.mock.nonces(this.sender); - - expect(currentNonce).to.equal(0n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - - await this.mock.$_useCheckedNonce(this.sender, currentNonce); - - expect(await this.mock.nonces(this.sender)).to.equal(1n); - expect(await this.mock.nonces(this.other)).to.equal(0n); - }); - - it('reverts when nonce is not the expected', async function () { - const currentNonce = await this.mock.nonces(this.sender); - - await expect(this.mock.$_useCheckedNonce(this.sender, currentNonce + 1n)) - .to.be.revertedWithCustomError(this.mock, 'InvalidAccountNonce') - .withArgs(this.sender, currentNonce); - }); - }); + shouldBehaveLikeNonces(); }); diff --git a/test/utils/NoncesKeyed.test.js b/test/utils/NoncesKeyed.test.js new file mode 100644 index 00000000000..c46948ee402 --- /dev/null +++ b/test/utils/NoncesKeyed.test.js @@ -0,0 +1,17 @@ +const { ethers } = require('hardhat'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { shouldBehaveLikeNonces, shouldBehaveLikeNoncesKeyed } = require('./Nonces.behavior'); + +async function fixture() { + const mock = await ethers.deployContract('$NoncesKeyed'); + return { mock }; +} + +describe('NoncesKeyed', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + shouldBehaveLikeNonces(); + shouldBehaveLikeNoncesKeyed(); +}); From 28aed34dc5e025e61ea0390c18cac875bfde1a78 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 23 Oct 2024 09:19:13 +0200 Subject: [PATCH 81/81] Merge account abstraction work into master (#5274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ernesto García Co-authored-by: Elias Rad <146735585+nnsW3@users.noreply.github.com> Co-authored-by: cairo Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- .changeset/hot-shrimps-wait.md | 5 + .changeset/small-seahorses-bathe.md | 5 + .changeset/weak-roses-bathe.md | 5 + .codecov.yml | 1 + .github/workflows/checks.yml | 2 +- contracts/account/README.adoc | 12 + .../account/utils/draft-ERC4337Utils.sol | 150 +++++ .../account/utils/draft-ERC7579Utils.sol | 242 +++++++++ contracts/interfaces/draft-IERC4337.sol | 215 ++++++++ contracts/interfaces/draft-IERC7579.sol | 196 +++++++ contracts/mocks/Stateless.sol | 2 + .../mocks/account/utils/ERC7579UtilsMock.sol | 23 + contracts/utils/Packing.sol | 513 ++++++++++++++++++ scripts/generate/templates/Packing.opts.js | 2 +- scripts/generate/templates/Packing.t.js | 4 +- slither.config.json | 2 +- test/account/utils/draft-ERC4337Utils.test.js | 211 +++++++ test/account/utils/draft-ERC7579Utils.test.js | 354 ++++++++++++ test/helpers/erc4337.js | 95 ++++ test/helpers/erc7579.js | 58 ++ test/utils/Packing.t.sol | 492 ++++++++++++++--- 21 files changed, 2494 insertions(+), 95 deletions(-) create mode 100644 .changeset/hot-shrimps-wait.md create mode 100644 .changeset/small-seahorses-bathe.md create mode 100644 .changeset/weak-roses-bathe.md create mode 100644 contracts/account/README.adoc create mode 100644 contracts/account/utils/draft-ERC4337Utils.sol create mode 100644 contracts/account/utils/draft-ERC7579Utils.sol create mode 100644 contracts/interfaces/draft-IERC4337.sol create mode 100644 contracts/interfaces/draft-IERC7579.sol create mode 100644 contracts/mocks/account/utils/ERC7579UtilsMock.sol create mode 100644 test/account/utils/draft-ERC4337Utils.test.js create mode 100644 test/account/utils/draft-ERC7579Utils.test.js create mode 100644 test/helpers/erc4337.js create mode 100644 test/helpers/erc7579.js diff --git a/.changeset/hot-shrimps-wait.md b/.changeset/hot-shrimps-wait.md new file mode 100644 index 00000000000..e4e96a981ad --- /dev/null +++ b/.changeset/hot-shrimps-wait.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`Packing`: Add variants for packing `bytes10` and `bytes22` diff --git a/.changeset/small-seahorses-bathe.md b/.changeset/small-seahorses-bathe.md new file mode 100644 index 00000000000..7b5ec794f38 --- /dev/null +++ b/.changeset/small-seahorses-bathe.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC7579Utils`: Add a reusable library to interact with ERC-7579 modular accounts diff --git a/.changeset/weak-roses-bathe.md b/.changeset/weak-roses-bathe.md new file mode 100644 index 00000000000..416b2e746d3 --- /dev/null +++ b/.changeset/weak-roses-bathe.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': minor +--- + +`ERC4337Utils`: Add a reusable library to manipulate user operations and interact with ERC-4337 contracts diff --git a/.codecov.yml b/.codecov.yml index 5bee9146ab9..4cec4ef7d5f 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -13,3 +13,4 @@ coverage: ignore: - "test" - "contracts/mocks" + - "contracts/vendor" diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a4d08c1da51..18a38b3c5c0 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -133,4 +133,4 @@ jobs: with: check_hidden: true check_filenames: true - skip: package-lock.json,*.pdf + skip: package-lock.json,*.pdf,vendor diff --git a/contracts/account/README.adoc b/contracts/account/README.adoc new file mode 100644 index 00000000000..d2eb9db5ee9 --- /dev/null +++ b/contracts/account/README.adoc @@ -0,0 +1,12 @@ += Account + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/account + +This directory includes contracts to build accounts for ERC-4337. + +== Utilities + +{{ERC4337Utils}} + +{{ERC7579Utils}} diff --git a/contracts/account/utils/draft-ERC4337Utils.sol b/contracts/account/utils/draft-ERC4337Utils.sol new file mode 100644 index 00000000000..bf559b86d6e --- /dev/null +++ b/contracts/account/utils/draft-ERC4337Utils.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IEntryPoint, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol"; +import {Math} from "../../utils/math/Math.sol"; +import {Packing} from "../../utils/Packing.sol"; + +/** + * @dev Library with common ERC-4337 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-4337[ERC-4337]. + */ +library ERC4337Utils { + using Packing for *; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success. + uint256 internal constant SIG_VALIDATION_SUCCESS = 0; + + /// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) must return this value in case of signature failure, instead of revert. + uint256 internal constant SIG_VALIDATION_FAILED = 1; + + /// @dev Parses the validation data into its components. See {packValidationData}. + function parseValidationData( + uint256 validationData + ) internal pure returns (address aggregator, uint48 validAfter, uint48 validUntil) { + validAfter = uint48(bytes32(validationData).extract_32_6(0x00)); + validUntil = uint48(bytes32(validationData).extract_32_6(0x06)); + aggregator = address(bytes32(validationData).extract_32_20(0x0c)); + if (validUntil == 0) validUntil = type(uint48).max; + } + + /// @dev Packs the validation data into a single uint256. See {parseValidationData}. + function packValidationData( + address aggregator, + uint48 validAfter, + uint48 validUntil + ) internal pure returns (uint256) { + return uint256(bytes6(validAfter).pack_6_6(bytes6(validUntil)).pack_12_20(bytes20(aggregator))); + } + + /// @dev Same as {packValidationData}, but with a boolean signature success flag. + function packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) internal pure returns (uint256) { + return + packValidationData( + address(uint160(Math.ternary(sigSuccess, SIG_VALIDATION_SUCCESS, SIG_VALIDATION_FAILED))), + validAfter, + validUntil + ); + } + + /** + * @dev Combines two validation data into a single one. + * + * The `aggregator` is set to {SIG_VALIDATION_SUCCESS} if both are successful, while + * the `validAfter` is the maximum and the `validUntil` is the minimum of both. + */ + function combineValidationData(uint256 validationData1, uint256 validationData2) internal pure returns (uint256) { + (address aggregator1, uint48 validAfter1, uint48 validUntil1) = parseValidationData(validationData1); + (address aggregator2, uint48 validAfter2, uint48 validUntil2) = parseValidationData(validationData2); + + bool success = aggregator1 == address(0) && aggregator2 == address(0); + uint48 validAfter = uint48(Math.max(validAfter1, validAfter2)); + uint48 validUntil = uint48(Math.min(validUntil1, validUntil2)); + return packValidationData(success, validAfter, validUntil); + } + + /// @dev Returns the aggregator of the `validationData` and whether it is out of time range. + function getValidationData(uint256 validationData) internal view returns (address aggregator, bool outOfTimeRange) { + (address aggregator_, uint48 validAfter, uint48 validUntil) = parseValidationData(validationData); + return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp); + } + + /// @dev Computes the hash of a user operation with the current entrypoint and chainid. + function hash(PackedUserOperation calldata self) internal view returns (bytes32) { + return hash(self, address(this), block.chainid); + } + + /// @dev Sames as {hash}, but with a custom entrypoint and chainid. + function hash( + PackedUserOperation calldata self, + address entrypoint, + uint256 chainid + ) internal pure returns (bytes32) { + bytes32 result = keccak256( + abi.encode( + keccak256( + abi.encode( + self.sender, + self.nonce, + keccak256(self.initCode), + keccak256(self.callData), + self.accountGasLimits, + self.preVerificationGas, + self.gasFees, + keccak256(self.paymasterAndData) + ) + ), + entrypoint, + chainid + ) + ); + return result; + } + + /// @dev Returns `verificationGasLimit` from the {PackedUserOperation}. + function verificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x00)); + } + + /// @dev Returns `accountGasLimits` from the {PackedUserOperation}. + function callGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.accountGasLimits.extract_32_16(0x10)); + } + + /// @dev Returns the first section of `gasFees` from the {PackedUserOperation}. + function maxPriorityFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x00)); + } + + /// @dev Returns the second section of `gasFees` from the {PackedUserOperation}. + function maxFeePerGas(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(self.gasFees.extract_32_16(0x10)); + } + + /// @dev Returns the total gas price for the {PackedUserOperation} (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). + function gasPrice(PackedUserOperation calldata self) internal view returns (uint256) { + unchecked { + // Following values are "per gas" + uint256 maxPriorityFee = maxPriorityFeePerGas(self); + uint256 maxFee = maxFeePerGas(self); + return Math.ternary(maxFee == maxPriorityFee, maxFee, Math.min(maxFee, maxPriorityFee + block.basefee)); + } + } + + /// @dev Returns the first section of `paymasterAndData` from the {PackedUserOperation}. + function paymaster(PackedUserOperation calldata self) internal pure returns (address) { + return address(bytes20(self.paymasterAndData[0:20])); + } + + /// @dev Returns the second section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterVerificationGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[20:36])); + } + + /// @dev Returns the third section of `paymasterAndData` from the {PackedUserOperation}. + function paymasterPostOpGasLimit(PackedUserOperation calldata self) internal pure returns (uint256) { + return uint128(bytes16(self.paymasterAndData[36:52])); + } +} diff --git a/contracts/account/utils/draft-ERC7579Utils.sol b/contracts/account/utils/draft-ERC7579Utils.sol new file mode 100644 index 00000000000..de6bb6509ec --- /dev/null +++ b/contracts/account/utils/draft-ERC7579Utils.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Execution} from "../../interfaces/draft-IERC7579.sol"; +import {Packing} from "../../utils/Packing.sol"; +import {Address} from "../../utils/Address.sol"; + +type Mode is bytes32; +type CallType is bytes1; +type ExecType is bytes1; +type ModeSelector is bytes4; +type ModePayload is bytes22; + +/** + * @dev Library with common ERC-7579 utility functions. + * + * See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579]. + */ +// slither-disable-next-line unused-state +library ERC7579Utils { + using Packing for *; + + /// @dev A single `call` execution. + CallType constant CALLTYPE_SINGLE = CallType.wrap(0x00); + + /// @dev A batch of `call` executions. + CallType constant CALLTYPE_BATCH = CallType.wrap(0x01); + + /// @dev A `delegatecall` execution. + CallType constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF); + + /// @dev Default execution type that reverts on failure. + ExecType constant EXECTYPE_DEFAULT = ExecType.wrap(0x00); + + /// @dev Execution type that does not revert on failure. + ExecType constant EXECTYPE_TRY = ExecType.wrap(0x01); + + /// @dev Emits when an {EXECTYPE_TRY} execution fails. + event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes result); + + /// @dev The provided {CallType} is not supported. + error ERC7579UnsupportedCallType(CallType callType); + + /// @dev The provided {ExecType} is not supported. + error ERC7579UnsupportedExecType(ExecType execType); + + /// @dev The provided module doesn't match the provided module type. + error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module); + + /// @dev The module is not installed. + error ERC7579UninstalledModule(uint256 moduleTypeId, address module); + + /// @dev The module is already installed. + error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module); + + /// @dev The module type is not supported. + error ERC7579UnsupportedModuleType(uint256 moduleTypeId); + + /// @dev Executes a single call. + function execSingle( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + (address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata); + returnData = new bytes[](1); + returnData[0] = _call(0, execType, target, value, callData); + } + + /// @dev Executes a batch of calls. + function execBatch( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + Execution[] calldata executionBatch = decodeBatch(executionCalldata); + returnData = new bytes[](executionBatch.length); + for (uint256 i = 0; i < executionBatch.length; ++i) { + returnData[i] = _call( + i, + execType, + executionBatch[i].target, + executionBatch[i].value, + executionBatch[i].callData + ); + } + } + + /// @dev Executes a delegate call. + function execDelegateCall( + ExecType execType, + bytes calldata executionCalldata + ) internal returns (bytes[] memory returnData) { + (address target, bytes calldata callData) = decodeDelegate(executionCalldata); + returnData = new bytes[](1); + returnData[0] = _delegatecall(0, execType, target, callData); + } + + /// @dev Encodes the mode with the provided parameters. See {decodeMode}. + function encodeMode( + CallType callType, + ExecType execType, + ModeSelector selector, + ModePayload payload + ) internal pure returns (Mode mode) { + return + Mode.wrap( + CallType + .unwrap(callType) + .pack_1_1(ExecType.unwrap(execType)) + .pack_2_4(bytes4(0)) + .pack_6_4(ModeSelector.unwrap(selector)) + .pack_10_22(ModePayload.unwrap(payload)) + ); + } + + /// @dev Decodes the mode into its parameters. See {encodeMode}. + function decodeMode( + Mode mode + ) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) { + return ( + CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0)), + ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 1)), + ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 6)), + ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 10)) + ); + } + + /// @dev Encodes a single call execution. See {decodeSingle}. + function encodeSingle( + address target, + uint256 value, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, value, callData); + } + + /// @dev Decodes a single call execution. See {encodeSingle}. + function decodeSingle( + bytes calldata executionCalldata + ) internal pure returns (address target, uint256 value, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + value = uint256(bytes32(executionCalldata[20:52])); + callData = executionCalldata[52:]; + } + + /// @dev Encodes a delegate call execution. See {decodeDelegate}. + function encodeDelegate( + address target, + bytes calldata callData + ) internal pure returns (bytes memory executionCalldata) { + return abi.encodePacked(target, callData); + } + + /// @dev Decodes a delegate call execution. See {encodeDelegate}. + function decodeDelegate( + bytes calldata executionCalldata + ) internal pure returns (address target, bytes calldata callData) { + target = address(bytes20(executionCalldata[0:20])); + callData = executionCalldata[20:]; + } + + /// @dev Encodes a batch of executions. See {decodeBatch}. + function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) { + return abi.encode(executionBatch); + } + + /// @dev Decodes a batch of executions. See {encodeBatch}. + function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) { + assembly ("memory-safe") { + let ptr := add(executionCalldata.offset, calldataload(executionCalldata.offset)) + // Extract the ERC7579 Executions + executionBatch.offset := add(ptr, 32) + executionBatch.length := calldataload(ptr) + } + } + + /// @dev Executes a `call` to the target with the provided {ExecType}. + function _call( + uint256 index, + ExecType execType, + address target, + uint256 value, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _validateExecutionMode(index, execType, success, returndata); + } + + /// @dev Executes a `delegatecall` to the target with the provided {ExecType}. + function _delegatecall( + uint256 index, + ExecType execType, + address target, + bytes calldata data + ) private returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return _validateExecutionMode(index, execType, success, returndata); + } + + /// @dev Validates the execution mode and returns the returndata. + function _validateExecutionMode( + uint256 index, + ExecType execType, + bool success, + bytes memory returndata + ) private returns (bytes memory) { + if (execType == ERC7579Utils.EXECTYPE_DEFAULT) { + Address.verifyCallResult(success, returndata); + } else if (execType == ERC7579Utils.EXECTYPE_TRY) { + if (!success) emit ERC7579TryExecuteFail(index, returndata); + } else { + revert ERC7579UnsupportedExecType(execType); + } + return returndata; + } +} + +// Operators +using {eqCallType as ==} for CallType global; +using {eqExecType as ==} for ExecType global; +using {eqModeSelector as ==} for ModeSelector global; +using {eqModePayload as ==} for ModePayload global; + +/// @dev Compares two `CallType` values for equality. +function eqCallType(CallType a, CallType b) pure returns (bool) { + return CallType.unwrap(a) == CallType.unwrap(b); +} + +/// @dev Compares two `ExecType` values for equality. +function eqExecType(ExecType a, ExecType b) pure returns (bool) { + return ExecType.unwrap(a) == ExecType.unwrap(b); +} + +/// @dev Compares two `ModeSelector` values for equality. +function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) { + return ModeSelector.unwrap(a) == ModeSelector.unwrap(b); +} + +/// @dev Compares two `ModePayload` values for equality. +function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) { + return ModePayload.unwrap(a) == ModePayload.unwrap(b); +} diff --git a/contracts/interfaces/draft-IERC4337.sol b/contracts/interfaces/draft-IERC4337.sol new file mode 100644 index 00000000000..9b1af56b6e5 --- /dev/null +++ b/contracts/interfaces/draft-IERC4337.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @dev A https://github.com/ethereum/ercs/blob/master/ERCS/erc-4337.md#useroperation[user operation] is composed of the following elements: + * - `sender` (`address`): The account making the operation + * - `nonce` (`uint256`): Anti-replay parameter (see “Semi-abstracted Nonce Support” ) + * - `factory` (`address`): account factory, only for new accounts + * - `factoryData` (`bytes`): data for account factory (only if account factory exists) + * - `callData` (`bytes`): The data to pass to the sender during the main execution call + * - `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call + * - `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step + * - `preVerificationGas` (`uint256`): Extra gas to pay the bunder + * - `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) + * - `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) + * - `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself) + * - `paymasterVerificationGasLimit` (`uint256`): The amount of gas to allocate for the paymaster validation code + * - `paymasterPostOpGasLimit` (`uint256`): The amount of gas to allocate for the paymaster post-operation code + * - `paymasterData` (`bytes`): Data for paymaster (only if paymaster exists) + * - `signature` (`bytes`): Data passed into the account to verify authorization + * + * When passed to on-chain contacts, the following packed version is used. + * - `sender` (`address`) + * - `nonce` (`uint256`) + * - `initCode` (`bytes`): concatenation of factory address and factoryData (or empty) + * - `callData` (`bytes`) + * - `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes) + * - `preVerificationGas` (`uint256`) + * - `gasFees` (`bytes32`): concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes) + * - `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty) + * - `signature` (`bytes`) + */ +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; // `abi.encodePacked(factory, factoryData)` + bytes callData; + bytes32 accountGasLimits; // `abi.encodePacked(verificationGasLimit, callGasLimit)` 16 bytes each + uint256 preVerificationGas; + bytes32 gasFees; // `abi.encodePacked(maxPriorityFee, maxFeePerGas)` 16 bytes each + bytes paymasterAndData; // `abi.encodePacked(paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData)` + bytes signature; +} + +/** + * @dev Aggregates and validates multiple signatures for a batch of user operations. + */ +interface IAggregator { + /** + * @dev Validates the signature for a user operation. + */ + function validateUserOpSignature( + PackedUserOperation calldata userOp + ) external view returns (bytes memory sigForUserOp); + + /** + * @dev Returns an aggregated signature for a batch of user operation's signatures. + */ + function aggregateSignatures( + PackedUserOperation[] calldata userOps + ) external view returns (bytes memory aggregatesSignature); + + /** + * @dev Validates that the aggregated signature is valid for the user operations. + * + * Requirements: + * + * - The aggregated signature MUST match the given list of operations. + */ + function validateSignatures(PackedUserOperation[] calldata userOps, bytes calldata signature) external view; +} + +/** + * @dev Handle nonce management for accounts. + */ +interface IEntryPointNonces { + /** + * @dev Returns the nonce for a `sender` account and a `key`. + * + * Nonces for a certain `key` are always increasing. + */ + function getNonce(address sender, uint192 key) external view returns (uint256 nonce); +} + +/** + * @dev Handle stake management for accounts. + */ +interface IEntryPointStake { + /** + * @dev Returns the balance of the account. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Deposits `msg.value` to the account. + */ + function depositTo(address account) external payable; + + /** + * @dev Withdraws `withdrawAmount` from the account to `withdrawAddress`. + */ + function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; + + /** + * @dev Adds stake to the account with an unstake delay of `unstakeDelaySec`. + */ + function addStake(uint32 unstakeDelaySec) external payable; + + /** + * @dev Unlocks the stake of the account. + */ + function unlockStake() external; + + /** + * @dev Withdraws the stake of the account to `withdrawAddress`. + */ + function withdrawStake(address payable withdrawAddress) external; +} + +/** + * @dev Entry point for user operations. + */ +interface IEntryPoint is IEntryPointNonces, IEntryPointStake { + /** + * @dev A user operation at `opIndex` failed with `reason`. + */ + error FailedOp(uint256 opIndex, string reason); + + /** + * @dev A user operation at `opIndex` failed with `reason` and `inner` returned data. + */ + error FailedOpWithRevert(uint256 opIndex, string reason, bytes inner); + + /** + * @dev Batch of aggregated user operations per aggregator. + */ + struct UserOpsPerAggregator { + PackedUserOperation[] userOps; + IAggregator aggregator; + bytes signature; + } + + /** + * @dev Executes a batch of user operations. + */ + function handleOps(PackedUserOperation[] calldata ops, address payable beneficiary) external; + + /** + * @dev Executes a batch of aggregated user operations per aggregator. + */ + function handleAggregatedOps( + UserOpsPerAggregator[] calldata opsPerAggregator, + address payable beneficiary + ) external; +} + +/** + * @dev Base interface for an account. + */ +interface IAccount { + /** + * @dev Validates a user operation. + */ + function validateUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 missingAccountFunds + ) external returns (uint256 validationData); +} + +/** + * @dev Support for executing user operations by prepending the {executeUserOp} function selector + * to the UserOperation's `callData`. + */ +interface IAccountExecute { + /** + * @dev Executes a user operation. + */ + function executeUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external; +} + +/** + * @dev Interface for a paymaster contract that agrees to pay for the gas costs of a user operation. + * + * NOTE: A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. + */ +interface IPaymaster { + enum PostOpMode { + opSucceeded, + opReverted, + postOpReverted + } + + /** + * @dev Validates whether the paymaster is willing to pay for the user operation. + * + * NOTE: Bundlers will reject this method if it modifies the state, unless it's whitelisted. + */ + function validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 maxCost + ) external returns (bytes memory context, uint256 validationData); + + /** + * @dev Verifies the sender is the entrypoint. + */ + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) external; +} diff --git a/contracts/interfaces/draft-IERC7579.sol b/contracts/interfaces/draft-IERC7579.sol new file mode 100644 index 00000000000..47f1627f682 --- /dev/null +++ b/contracts/interfaces/draft-IERC7579.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {PackedUserOperation} from "./draft-IERC4337.sol"; + +uint256 constant VALIDATION_SUCCESS = 0; +uint256 constant VALIDATION_FAILED = 1; +uint256 constant MODULE_TYPE_VALIDATOR = 1; +uint256 constant MODULE_TYPE_EXECUTOR = 2; +uint256 constant MODULE_TYPE_FALLBACK = 3; +uint256 constant MODULE_TYPE_HOOK = 4; + +interface IERC7579Module { + /** + * @dev This function is called by the smart account during installation of the module + * @param data arbitrary data that may be required on the module during `onInstall` initialization + * + * MUST revert on error (e.g. if module is already enabled) + */ + function onInstall(bytes calldata data) external; + + /** + * @dev This function is called by the smart account during uninstallation of the module + * @param data arbitrary data that may be required on the module during `onUninstall` de-initialization + * + * MUST revert on error + */ + function onUninstall(bytes calldata data) external; + + /** + * @dev Returns boolean value if module is a certain type + * @param moduleTypeId the module type ID according the ERC-7579 spec + * + * MUST return true if the module is of the given type and false otherwise + */ + function isModuleType(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579Validator is IERC7579Module { + /** + * @dev Validates a UserOperation + * @param userOp the ERC-4337 PackedUserOperation + * @param userOpHash the hash of the ERC-4337 PackedUserOperation + * + * MUST validate that the signature is a valid signature of the userOpHash + * SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch + */ + function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); + + /** + * @dev Validates a signature using ERC-1271 + * @param sender the address that sent the ERC-1271 request to the smart account + * @param hash the hash of the ERC-1271 request + * @param signature the signature of the ERC-1271 request + * + * MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid + * MUST NOT modify state + */ + function isValidSignatureWithSender( + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); +} + +interface IERC7579Hook is IERC7579Module { + /** + * @dev Called by the smart account before execution + * @param msgSender the address that called the smart account + * @param value the value that was sent to the smart account + * @param msgData the data that was sent to the smart account + * + * MAY return arbitrary data in the `hookData` return value + */ + function preCheck( + address msgSender, + uint256 value, + bytes calldata msgData + ) external returns (bytes memory hookData); + + /** + * @dev Called by the smart account after execution + * @param hookData the data that was returned by the `preCheck` function + * + * MAY validate the `hookData` to validate transaction context of the `preCheck` function + */ + function postCheck(bytes calldata hookData) external; +} + +struct Execution { + address target; + uint256 value; + bytes callData; +} + +interface IERC7579Execution { + /** + * @dev Executes a transaction on behalf of the account. + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: e.g. onlyEntryPointOrSelf if used with ERC-4337 + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function execute(bytes32 mode, bytes calldata executionCalldata) external; + + /** + * @dev Executes a transaction on behalf of the account. + * This function is intended to be called by Executor Modules + * @param mode The encoded execution mode of the transaction. See ModeLib.sol for details + * @param executionCalldata The encoded execution call data + * + * MUST ensure adequate authorization control: i.e. onlyExecutorModule + * If a mode is requested that is not supported by the Account, it MUST revert + */ + function executeFromExecutor( + bytes32 mode, + bytes calldata executionCalldata + ) external returns (bytes[] memory returnData); +} + +interface IERC7579AccountConfig { + /** + * @dev Returns the account id of the smart account + * @return accountImplementationId the account id of the smart account + * + * MUST return a non-empty string + * The accountId SHOULD be structured like so: + * "vendorname.accountname.semver" + * The id SHOULD be unique across all smart accounts + */ + function accountId() external view returns (string memory accountImplementationId); + + /** + * @dev Function to check if the account supports a certain execution mode (see above) + * @param encodedMode the encoded mode + * + * MUST return true if the account supports the mode and false otherwise + */ + function supportsExecutionMode(bytes32 encodedMode) external view returns (bool); + + /** + * @dev Function to check if the account supports a certain module typeId + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * + * MUST return true if the account supports the module type and false otherwise + */ + function supportsModule(uint256 moduleTypeId) external view returns (bool); +} + +interface IERC7579ModuleConfig { + event ModuleInstalled(uint256 moduleTypeId, address module); + event ModuleUninstalled(uint256 moduleTypeId, address module); + + /** + * @dev Installs a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according to the ERC-7579 spec + * @param module the module address + * @param initData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onInstall` on the module with the `initData` parameter if provided + * MUST emit ModuleInstalled event + * MUST revert if the module is already installed or the initialization on the module failed + */ + function installModule(uint256 moduleTypeId, address module, bytes calldata initData) external; + + /** + * @dev Uninstalls a Module of a certain type on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param deInitData arbitrary data that may be required on the module during `onInstall` + * initialization. + * + * MUST implement authorization control + * MUST call `onUninstall` on the module with the `deInitData` parameter if provided + * MUST emit ModuleUninstalled event + * MUST revert if the module is not installed or the deInitialization on the module failed + */ + function uninstallModule(uint256 moduleTypeId, address module, bytes calldata deInitData) external; + + /** + * @dev Returns whether a module is installed on the smart account + * @param moduleTypeId the module type ID according the ERC-7579 spec + * @param module the module address + * @param additionalContext arbitrary data that may be required to determine if the module is installed + * + * MUST return true if the module is installed and false otherwise + */ + function isModuleInstalled( + uint256 moduleTypeId, + address module, + bytes calldata additionalContext + ) external view returns (bool); +} diff --git a/contracts/mocks/Stateless.sol b/contracts/mocks/Stateless.sol index 3040ddcdf8e..0bc89e4ceb7 100644 --- a/contracts/mocks/Stateless.sol +++ b/contracts/mocks/Stateless.sol @@ -25,6 +25,8 @@ 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 {ERC4337Utils} from "../account/utils/draft-ERC4337Utils.sol"; +import {ERC7579Utils} from "../account/utils/draft-ERC7579Utils.sol"; import {Heap} from "../utils/structs/Heap.sol"; import {Math} from "../utils/math/Math.sol"; import {MerkleProof} from "../utils/cryptography/MerkleProof.sol"; diff --git a/contracts/mocks/account/utils/ERC7579UtilsMock.sol b/contracts/mocks/account/utils/ERC7579UtilsMock.sol new file mode 100644 index 00000000000..e0a1e1a50ea --- /dev/null +++ b/contracts/mocks/account/utils/ERC7579UtilsMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {CallType, ExecType, ModeSelector, ModePayload} from "../../../account/utils/draft-ERC7579Utils.sol"; + +contract ERC7579UtilsGlobalMock { + function eqCallTypeGlobal(CallType callType1, CallType callType2) internal pure returns (bool) { + return callType1 == callType2; + } + + function eqExecTypeGlobal(ExecType execType1, ExecType execType2) internal pure returns (bool) { + return execType1 == execType2; + } + + function eqModeSelectorGlobal(ModeSelector modeSelector1, ModeSelector modeSelector2) internal pure returns (bool) { + return modeSelector1 == modeSelector2; + } + + function eqModePayloadGlobal(ModePayload modePayload1, ModePayload modePayload2) internal pure returns (bool) { + return modePayload1 == modePayload2; + } +} diff --git a/contracts/utils/Packing.sol b/contracts/utils/Packing.sol index 069153bef4a..f38e64a3bf6 100644 --- a/contracts/utils/Packing.sol +++ b/contracts/utils/Packing.sol @@ -68,6 +68,38 @@ library Packing { } } + function pack_2_8(bytes2 left, bytes8 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(192, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_10(bytes2 left, bytes10 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_20(bytes2 left, bytes20 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(96, not(0))) + result := or(left, shr(16, right)) + } + } + + function pack_2_22(bytes2 left, bytes22 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(240, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(16, right)) + } + } + function pack_4_2(bytes4 left, bytes2 right) internal pure returns (bytes6 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -84,6 +116,14 @@ library Packing { } } + function pack_4_6(bytes4 left, bytes6 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(224, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(32, right)) + } + } + function pack_4_8(bytes4 left, bytes8 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(224, not(0))) @@ -140,6 +180,14 @@ library Packing { } } + function pack_6_4(bytes6 left, bytes4 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(224, not(0))) + result := or(left, shr(48, right)) + } + } + function pack_6_6(bytes6 left, bytes6 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(208, not(0))) @@ -148,6 +196,38 @@ library Packing { } } + function pack_6_10(bytes6 left, bytes10 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_16(bytes6 left, bytes16 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(128, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_6_22(bytes6 left, bytes22 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(208, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(48, right)) + } + } + + function pack_8_2(bytes8 left, bytes2 right) internal pure returns (bytes10 result) { + assembly ("memory-safe") { + left := and(left, shl(192, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(64, right)) + } + } + function pack_8_4(bytes8 left, bytes4 right) internal pure returns (bytes12 result) { assembly ("memory-safe") { left := and(left, shl(192, not(0))) @@ -196,6 +276,46 @@ library Packing { } } + function pack_10_2(bytes10 left, bytes2 right) internal pure returns (bytes12 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_6(bytes10 left, bytes6 right) internal pure returns (bytes16 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_10(bytes10 left, bytes10 right) internal pure returns (bytes20 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_12(bytes10 left, bytes12 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(160, not(0))) + result := or(left, shr(80, right)) + } + } + + function pack_10_22(bytes10 left, bytes22 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(176, not(0))) + right := and(right, shl(80, not(0))) + result := or(left, shr(80, right)) + } + } + function pack_12_4(bytes12 left, bytes4 right) internal pure returns (bytes16 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -212,6 +332,14 @@ library Packing { } } + function pack_12_10(bytes12 left, bytes10 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(160, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(96, right)) + } + } + function pack_12_12(bytes12 left, bytes12 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(160, not(0))) @@ -244,6 +372,14 @@ library Packing { } } + function pack_16_6(bytes16 left, bytes6 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(128, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(128, right)) + } + } + function pack_16_8(bytes16 left, bytes8 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(128, not(0))) @@ -268,6 +404,14 @@ library Packing { } } + function pack_20_2(bytes20 left, bytes2 right) internal pure returns (bytes22 result) { + assembly ("memory-safe") { + left := and(left, shl(96, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(160, right)) + } + } + function pack_20_4(bytes20 left, bytes4 right) internal pure returns (bytes24 result) { assembly ("memory-safe") { left := and(left, shl(96, not(0))) @@ -292,6 +436,30 @@ library Packing { } } + function pack_22_2(bytes22 left, bytes2 right) internal pure returns (bytes24 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(240, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_6(bytes22 left, bytes6 right) internal pure returns (bytes28 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(208, not(0))) + result := or(left, shr(176, right)) + } + } + + function pack_22_10(bytes22 left, bytes10 right) internal pure returns (bytes32 result) { + assembly ("memory-safe") { + left := and(left, shl(80, not(0))) + right := and(right, shl(176, not(0))) + result := or(left, shr(176, right)) + } + } + function pack_24_4(bytes24 left, bytes4 right) internal pure returns (bytes28 result) { assembly ("memory-safe") { left := and(left, shl(64, not(0))) @@ -466,6 +634,81 @@ library Packing { } } + function extract_10_1(bytes10 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 9) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_10_1(bytes10 self, bytes1 value, uint8 offset) internal pure returns (bytes10 result) { + bytes1 oldValue = extract_10_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_2(bytes10 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 8) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_10_2(bytes10 self, bytes2 value, uint8 offset) internal pure returns (bytes10 result) { + bytes2 oldValue = extract_10_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_4(bytes10 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_10_4(bytes10 self, bytes4 value, uint8 offset) internal pure returns (bytes10 result) { + bytes4 oldValue = extract_10_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_6(bytes10 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 4) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_10_6(bytes10 self, bytes6 value, uint8 offset) internal pure returns (bytes10 result) { + bytes6 oldValue = extract_10_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_10_8(bytes10 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_10_8(bytes10 self, bytes8 value, uint8 offset) internal pure returns (bytes10 result) { + bytes8 oldValue = extract_10_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_12_1(bytes12 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 11) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -541,6 +784,21 @@ library Packing { } } + function extract_12_10(bytes12 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_12_10(bytes12 self, bytes10 value, uint8 offset) internal pure returns (bytes12 result) { + bytes10 oldValue = extract_12_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_1(bytes16 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 15) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -616,6 +874,21 @@ library Packing { } } + function extract_16_10(bytes16 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_16_10(bytes16 self, bytes10 value, uint8 offset) internal pure returns (bytes16 result) { + bytes10 oldValue = extract_16_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_16_12(bytes16 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -706,6 +979,21 @@ library Packing { } } + function extract_20_10(bytes20 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_20_10(bytes20 self, bytes10 value, uint8 offset) internal pure returns (bytes20 result) { + bytes10 oldValue = extract_20_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_20_12(bytes20 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -736,6 +1024,141 @@ library Packing { } } + function extract_22_1(bytes22 self, uint8 offset) internal pure returns (bytes1 result) { + if (offset > 21) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(248, not(0))) + } + } + + function replace_22_1(bytes22 self, bytes1 value, uint8 offset) internal pure returns (bytes22 result) { + bytes1 oldValue = extract_22_1(self, offset); + assembly ("memory-safe") { + value := and(value, shl(248, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_2(bytes22 self, uint8 offset) internal pure returns (bytes2 result) { + if (offset > 20) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(240, not(0))) + } + } + + function replace_22_2(bytes22 self, bytes2 value, uint8 offset) internal pure returns (bytes22 result) { + bytes2 oldValue = extract_22_2(self, offset); + assembly ("memory-safe") { + value := and(value, shl(240, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_4(bytes22 self, uint8 offset) internal pure returns (bytes4 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(224, not(0))) + } + } + + function replace_22_4(bytes22 self, bytes4 value, uint8 offset) internal pure returns (bytes22 result) { + bytes4 oldValue = extract_22_4(self, offset); + assembly ("memory-safe") { + value := and(value, shl(224, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_6(bytes22 self, uint8 offset) internal pure returns (bytes6 result) { + if (offset > 16) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(208, not(0))) + } + } + + function replace_22_6(bytes22 self, bytes6 value, uint8 offset) internal pure returns (bytes22 result) { + bytes6 oldValue = extract_22_6(self, offset); + assembly ("memory-safe") { + value := and(value, shl(208, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_8(bytes22 self, uint8 offset) internal pure returns (bytes8 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(192, not(0))) + } + } + + function replace_22_8(bytes22 self, bytes8 value, uint8 offset) internal pure returns (bytes22 result) { + bytes8 oldValue = extract_22_8(self, offset); + assembly ("memory-safe") { + value := and(value, shl(192, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_10(bytes22 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 12) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_22_10(bytes22 self, bytes10 value, uint8 offset) internal pure returns (bytes22 result) { + bytes10 oldValue = extract_22_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_12(bytes22 self, uint8 offset) internal pure returns (bytes12 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(160, not(0))) + } + } + + function replace_22_12(bytes22 self, bytes12 value, uint8 offset) internal pure returns (bytes22 result) { + bytes12 oldValue = extract_22_12(self, offset); + assembly ("memory-safe") { + value := and(value, shl(160, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_16(bytes22 self, uint8 offset) internal pure returns (bytes16 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(128, not(0))) + } + } + + function replace_22_16(bytes22 self, bytes16 value, uint8 offset) internal pure returns (bytes22 result) { + bytes16 oldValue = extract_22_16(self, offset); + assembly ("memory-safe") { + value := and(value, shl(128, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + + function extract_22_20(bytes22 self, uint8 offset) internal pure returns (bytes20 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(96, not(0))) + } + } + + function replace_22_20(bytes22 self, bytes20 value, uint8 offset) internal pure returns (bytes22 result) { + bytes20 oldValue = extract_22_20(self, offset); + assembly ("memory-safe") { + value := and(value, shl(96, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_1(bytes24 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 23) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -811,6 +1234,21 @@ library Packing { } } + function extract_24_10(bytes24 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 14) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_24_10(bytes24 self, bytes10 value, uint8 offset) internal pure returns (bytes24 result) { + bytes10 oldValue = extract_24_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_24_12(bytes24 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 12) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -856,6 +1294,21 @@ library Packing { } } + function extract_24_22(bytes24 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 2) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_24_22(bytes24 self, bytes22 value, uint8 offset) internal pure returns (bytes24 result) { + bytes22 oldValue = extract_24_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_1(bytes28 self, uint8 offset) internal pure returns (bytes1 result) { if (offset > 27) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -931,6 +1384,21 @@ library Packing { } } + function extract_28_10(bytes28 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 18) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_28_10(bytes28 self, bytes10 value, uint8 offset) internal pure returns (bytes28 result) { + bytes10 oldValue = extract_28_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_12(bytes28 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 16) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -976,6 +1444,21 @@ library Packing { } } + function extract_28_22(bytes28 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 6) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_28_22(bytes28 self, bytes22 value, uint8 offset) internal pure returns (bytes28 result) { + bytes22 oldValue = extract_28_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_28_24(bytes28 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 4) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1066,6 +1549,21 @@ library Packing { } } + function extract_32_10(bytes32 self, uint8 offset) internal pure returns (bytes10 result) { + if (offset > 22) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(176, not(0))) + } + } + + function replace_32_10(bytes32 self, bytes10 value, uint8 offset) internal pure returns (bytes32 result) { + bytes10 oldValue = extract_32_10(self, offset); + assembly ("memory-safe") { + value := and(value, shl(176, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_12(bytes32 self, uint8 offset) internal pure returns (bytes12 result) { if (offset > 20) revert OutOfRangeAccess(); assembly ("memory-safe") { @@ -1111,6 +1609,21 @@ library Packing { } } + function extract_32_22(bytes32 self, uint8 offset) internal pure returns (bytes22 result) { + if (offset > 10) revert OutOfRangeAccess(); + assembly ("memory-safe") { + result := and(shl(mul(8, offset), self), shl(80, not(0))) + } + } + + function replace_32_22(bytes32 self, bytes22 value, uint8 offset) internal pure returns (bytes32 result) { + bytes22 oldValue = extract_32_22(self, offset); + assembly ("memory-safe") { + value := and(value, shl(80, not(0))) + result := xor(self, shr(mul(8, offset), xor(oldValue, value))) + } + } + function extract_32_24(bytes32 self, uint8 offset) internal pure returns (bytes24 result) { if (offset > 8) revert OutOfRangeAccess(); assembly ("memory-safe") { diff --git a/scripts/generate/templates/Packing.opts.js b/scripts/generate/templates/Packing.opts.js index de9ab77ff53..893ad6297cf 100644 --- a/scripts/generate/templates/Packing.opts.js +++ b/scripts/generate/templates/Packing.opts.js @@ -1,3 +1,3 @@ module.exports = { - SIZES: [1, 2, 4, 6, 8, 12, 16, 20, 24, 28, 32], + SIZES: [1, 2, 4, 6, 8, 10, 12, 16, 20, 22, 24, 28, 32], }; diff --git a/scripts/generate/templates/Packing.t.js b/scripts/generate/templates/Packing.t.js index 56e9c0cc7c4..1feec28f5a5 100644 --- a/scripts/generate/templates/Packing.t.js +++ b/scripts/generate/templates/Packing.t.js @@ -11,14 +11,14 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; `; const testPack = (left, right) => `\ -function testPack(bytes${left} left, bytes${right} right) external { +function testPack(bytes${left} left, bytes${right} right) external pure { assertEq(left, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${left}(0)); assertEq(right, Packing.pack_${left}_${right}(left, right).extract_${left + right}_${right}(${left})); } `; const testReplace = (outer, inner) => `\ -function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external { +function testReplace(bytes${outer} container, bytes${inner} newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, ${outer - inner})); bytes${inner} oldValue = container.extract_${outer}_${inner}(offset); diff --git a/slither.config.json b/slither.config.json index 069da1f3a21..fa52f4dd1dd 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,5 +1,5 @@ { "detectors_to_run": "arbitrary-send-erc20,array-by-reference,incorrect-shift,name-reused,rtlo,suicidal,uninitialized-state,uninitialized-storage,arbitrary-send-erc20-permit,controlled-array-length,controlled-delegatecall,delegatecall-loop,msg-value-loop,reentrancy-eth,unchecked-transfer,weak-prng,domain-separator-collision,erc20-interface,erc721-interface,locked-ether,mapping-deletion,shadowing-abstract,tautology,write-after-write,boolean-cst,reentrancy-no-eth,reused-constructor,tx-origin,unchecked-lowlevel,unchecked-send,variable-scope,void-cst,events-access,events-maths,incorrect-unary,boolean-equal,cyclomatic-complexity,deprecated-standards,erc20-indexed,function-init-state,pragma,unused-state,reentrancy-unlimited-gas,constable-states,immutable-states,var-read-using-this", - "filter_paths": "contracts/mocks,contracts-exposed", + "filter_paths": "contracts/mocks,contracts/vendor,contracts-exposed", "compile_force_framework": "hardhat" } diff --git a/test/account/utils/draft-ERC4337Utils.test.js b/test/account/utils/draft-ERC4337Utils.test.js new file mode 100644 index 00000000000..8374bc745c9 --- /dev/null +++ b/test/account/utils/draft-ERC4337Utils.test.js @@ -0,0 +1,211 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); + +const { packValidationData, packPaymasterData, UserOperation } = require('../../helpers/erc4337'); +const { MAX_UINT48 } = require('../../helpers/constants'); + +const fixture = async () => { + const [authorizer, sender, entrypoint, paymaster] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC4337Utils'); + return { utils, authorizer, sender, entrypoint, paymaster }; +}; + +describe('ERC4337Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('parseValidationData', function () { + it('parses the validation data', async function () { + const authorizer = this.authorizer; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + validUntil, + ]); + }); + + it('returns an type(uint48).max if until is 0', async function () { + const authorizer = this.authorizer; + const validAfter = 0x12345678n; + const validationData = packValidationData(validAfter, 0, authorizer); + + expect(this.utils.$parseValidationData(validationData)).to.eventually.deep.equal([ + authorizer.address, + validAfter, + MAX_UINT48, + ]); + }); + }); + + describe('packValidationData', function () { + it('packs the validation data', async function () { + const authorizer = this.authorizer; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, authorizer); + + expect( + this.utils.$packValidationData(ethers.Typed.address(authorizer), validAfter, validUntil), + ).to.eventually.equal(validationData); + }); + + it('packs the validation data (bool)', async function () { + const success = false; + const validUntil = 0x12345678n; + const validAfter = 0x9abcdef0n; + const validationData = packValidationData(validAfter, validUntil, false); + + expect(this.utils.$packValidationData(ethers.Typed.bool(success), validAfter, validUntil)).to.eventually.equal( + validationData, + ); + }); + }); + + describe('combineValidationData', function () { + const validUntil1 = 0x12345678n; + const validAfter1 = 0x9abcdef0n; + const validUntil2 = 0x87654321n; + const validAfter2 = 0xabcdef90n; + + it('combines the validation data', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, ethers.ZeroAddress); + const validationData2 = packValidationData(validAfter2, validUntil2, ethers.ZeroAddress); + const expected = packValidationData(validAfter2, validUntil1, true); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + + for (const [authorizer1, authorizer2] of [ + [ethers.ZeroAddress, '0xbf023313b891fd6000544b79e353323aa94a4f29'], + ['0xbf023313b891fd6000544b79e353323aa94a4f29', ethers.ZeroAddress], + ]) { + it('returns SIG_VALIDATION_FAILURE if one of the authorizers is not address(0)', async function () { + const validationData1 = packValidationData(validAfter1, validUntil1, authorizer1); + const validationData2 = packValidationData(validAfter2, validUntil2, authorizer2); + const expected = packValidationData(validAfter2, validUntil1, false); + + // check symmetry + expect(this.utils.$combineValidationData(validationData1, validationData2)).to.eventually.equal(expected); + expect(this.utils.$combineValidationData(validationData2, validationData1)).to.eventually.equal(expected); + }); + } + }); + + describe('getValidationData', function () { + it('returns the validation data with valid validity range', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, false]); + }); + + it('returns the validation data with invalid validity range (expired)', async function () { + const aggregator = this.authorizer; + const validAfter = 0; + const validUntil = 1; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns the validation data with invalid validity range (not yet valid)', async function () { + const aggregator = this.authorizer; + const validAfter = MAX_UINT48; + const validUntil = MAX_UINT48; + const validationData = packValidationData(validAfter, validUntil, aggregator); + + expect(this.utils.$getValidationData(validationData)).to.eventually.deep.equal([aggregator.address, true]); + }); + + it('returns address(0) and false for validationData = 0', function () { + expect(this.utils.$getValidationData(0n)).to.eventually.deep.equal([ethers.ZeroAddress, false]); + }); + }); + + describe('hash', function () { + it('returns the user operation hash', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); + const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId); + + expect(this.utils.$hash(userOp.packed)).to.eventually.equal(userOp.hash(this.utils.target, chainId)); + }); + + it('returns the operation hash with specified entrypoint and chainId', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1 }); + const chainId = 0xdeadbeef; + + expect(this.utils.$hash(userOp.packed, this.entrypoint, chainId)).to.eventually.equal( + userOp.hash(this.entrypoint, chainId), + ); + }); + }); + + describe('userOp values', function () { + it('returns verificationGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, verificationGas: 0x12345678n }); + expect(this.utils.$verificationGasLimit(userOp.packed)).to.eventually.equal(userOp.verificationGas); + }); + + it('returns callGasLimit', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, callGas: 0x12345678n }); + expect(this.utils.$callGasLimit(userOp.packed)).to.eventually.equal(userOp.callGas); + }); + + it('returns maxPriorityFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxPriorityFee: 0x12345678n }); + expect(this.utils.$maxPriorityFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + it('returns maxFeePerGas', async function () { + const userOp = new UserOperation({ sender: this.sender, nonce: 1, maxFeePerGas: 0x12345678n }); + expect(this.utils.$maxFeePerGas(userOp.packed)).to.eventually.equal(userOp.maxFeePerGas); + }); + + it('returns gasPrice', async function () { + const userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + maxPriorityFee: 0x12345678n, + maxFeePerGas: 0x87654321n, + }); + expect(this.utils.$gasPrice(userOp.packed)).to.eventually.equal(userOp.maxPriorityFee); + }); + + describe('paymasterAndData', function () { + beforeEach(async function () { + this.verificationGasLimit = 0x12345678n; + this.postOpGasLimit = 0x87654321n; + this.paymasterAndData = packPaymasterData(this.paymaster, this.verificationGasLimit, this.postOpGasLimit); + this.userOp = new UserOperation({ + sender: this.sender, + nonce: 1, + paymasterAndData: this.paymasterAndData, + }); + }); + + it('returns paymaster', async function () { + expect(this.utils.$paymaster(this.userOp.packed)).to.eventually.equal(this.paymaster); + }); + + it('returns verificationGasLimit', async function () { + expect(this.utils.$paymasterVerificationGasLimit(this.userOp.packed)).to.eventually.equal( + this.verificationGasLimit, + ); + }); + + it('returns postOpGasLimit', async function () { + expect(this.utils.$paymasterPostOpGasLimit(this.userOp.packed)).to.eventually.equal(this.postOpGasLimit); + }); + }); + }); +}); diff --git a/test/account/utils/draft-ERC7579Utils.test.js b/test/account/utils/draft-ERC7579Utils.test.js new file mode 100644 index 00000000000..cc3b70425dd --- /dev/null +++ b/test/account/utils/draft-ERC7579Utils.test.js @@ -0,0 +1,354 @@ +const { ethers } = require('hardhat'); +const { expect } = require('chai'); +const { loadFixture, setBalance } = require('@nomicfoundation/hardhat-network-helpers'); +const { + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + encodeSingle, + encodeBatch, + encodeDelegate, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + encodeMode, +} = require('../../helpers/erc7579'); +const { selector } = require('../../helpers/methods'); + +const coder = ethers.AbiCoder.defaultAbiCoder(); + +const fixture = async () => { + const [sender] = await ethers.getSigners(); + const utils = await ethers.deployContract('$ERC7579Utils'); + const utilsGlobal = await ethers.deployContract('$ERC7579UtilsGlobalMock'); + const target = await ethers.deployContract('CallReceiverMock'); + const anotherTarget = await ethers.deployContract('CallReceiverMock'); + await setBalance(utils.target, ethers.parseEther('1')); + return { utils, utilsGlobal, target, anotherTarget, sender }; +}; + +describe('ERC7579Utils', function () { + beforeEach(async function () { + Object.assign(this, await loadFixture(fixture)); + }); + + describe('execSingle', function () { + it('calls the target with value', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.emit(this.target, 'MockFunctionCalled'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); + }); + + it('calls the target with value and args', async function () { + const value = 0x432; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ); + + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .withArgs(42, '0x1234'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + + await expect(this.utils.$execSingle(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const value = 0x012; + const data = encodeSingle( + this.target, + value, + this.target.interface.encodeFunctionData('mockFunctionRevertsReason'), + ); + + await expect(this.utils.$execSingle(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + + it('reverts with an invalid exec type', async function () { + const value = 0x012; + const data = encodeSingle(this.target, value, this.target.interface.encodeFunctionData('mockFunction')); + + await expect(this.utils.$execSingle('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + describe('execBatch', function () { + it('calls the targets with value', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalled') + .to.emit(this.anotherTarget, 'MockFunctionCalled'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2); + }); + + it('calls the targets with value and args', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234'])], + [ + this.anotherTarget, + value2, + this.anotherTarget.interface.encodeFunctionData('mockFunctionWithArgs', [42, '0x1234']), + ], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)) + .to.emit(this.target, 'MockFunctionCalledWithArgs') + .to.emit(this.anotherTarget, 'MockFunctionCalledWithArgs'); + + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(value2); + }); + + it('reverts when any target reverts in default ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith('CallReceiverMock: reverting'); + }); + + it('emits ERC7579TryExecuteFail event when any target reverts in try ExecType', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunctionRevertsReason')], + ); + + await expect(this.utils.$execBatch(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_BATCH, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + + // Check balances + expect(ethers.provider.getBalance(this.target)).to.eventually.equal(value1); + expect(ethers.provider.getBalance(this.anotherTarget)).to.eventually.equal(0); + }); + + it('reverts with an invalid exec type', async function () { + const value1 = 0x012; + const value2 = 0x234; + const data = encodeBatch( + [this.target, value1, this.target.interface.encodeFunctionData('mockFunction')], + [this.anotherTarget, value2, this.anotherTarget.interface.encodeFunctionData('mockFunction')], + ); + + await expect(this.utils.$execBatch('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + describe('execDelegateCall', function () { + it('delegate calls the target', async function () { + const slot = ethers.hexlify(ethers.randomBytes(32)); + const value = ethers.hexlify(ethers.randomBytes(32)); + const data = encodeDelegate( + this.target, + this.target.interface.encodeFunctionData('mockFunctionWritesStorage', [slot, value]), + ); + + expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(ethers.ZeroHash); + await this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data); + expect(ethers.provider.getStorage(this.utils.target, slot)).to.eventually.equal(value); + }); + + it('reverts when target reverts in default ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(EXEC_TYPE_DEFAULT, data)).to.be.revertedWith( + 'CallReceiverMock: reverting', + ); + }); + + it('emits ERC7579TryExecuteFail event when target reverts in try ExecType', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunctionRevertsReason')); + await expect(this.utils.$execDelegateCall(EXEC_TYPE_TRY, data)) + .to.emit(this.utils, 'ERC7579TryExecuteFail') + .withArgs( + CALL_TYPE_CALL, + ethers.solidityPacked( + ['bytes4', 'bytes'], + [selector('Error(string)'), coder.encode(['string'], ['CallReceiverMock: reverting'])], + ), + ); + }); + + it('reverts with an invalid exec type', async function () { + const data = encodeDelegate(this.target, this.target.interface.encodeFunctionData('mockFunction')); + await expect(this.utils.$execDelegateCall('0x03', data)) + .to.be.revertedWithCustomError(this.utils, 'ERC7579UnsupportedExecType') + .withArgs('0x03'); + }); + }); + + it('encodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + expect(this.utils.$encodeMode(callType, execType, selector, payload)).to.eventually.equal( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ); + }); + + it('decodes Mode', async function () { + const callType = CALL_TYPE_BATCH; + const execType = EXEC_TYPE_TRY; + const selector = '0x12345678'; + const payload = ethers.toBeHex(0, 22); + + expect( + this.utils.$decodeMode( + encodeMode({ + callType, + execType, + selector, + payload, + }), + ), + ).to.eventually.deep.equal([callType, execType, selector, payload]); + }); + + it('encodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$encodeSingle(target, value, data)).to.eventually.equal(encodeSingle(target, value, data)); + }); + + it('decodes single', async function () { + const target = this.target; + const value = 0x123; + const data = '0x12345678'; + + expect(this.utils.$decodeSingle(encodeSingle(target, value, data))).to.eventually.deep.equal([ + target.target, + value, + data, + ]); + }); + + it('encodes batch', async function () { + const entries = [ + [this.target, 0x123, '0x12345678'], + [this.anotherTarget, 0x456, '0x12345678'], + ]; + + expect(this.utils.$encodeBatch(entries)).to.eventually.equal(encodeBatch(...entries)); + }); + + it('decodes batch', async function () { + const entries = [ + [this.target.target, 0x123, '0x12345678'], + [this.anotherTarget.target, 0x456, '0x12345678'], + ]; + + expect(this.utils.$decodeBatch(encodeBatch(...entries))).to.eventually.deep.equal(entries); + }); + + it('encodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$encodeDelegate(target, data)).to.eventually.equal(encodeDelegate(target, data)); + }); + + it('decodes delegate', async function () { + const target = this.target; + const data = '0x12345678'; + + expect(this.utils.$decodeDelegate(encodeDelegate(target, data))).to.eventually.deep.equal([target.target, data]); + }); + + describe('global', function () { + describe('eqCallTypeGlobal', function () { + it('returns true if both call types are equal', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_BATCH, CALL_TYPE_BATCH)).to.eventually.be.true; + }); + + it('returns false if both call types are different', async function () { + expect(this.utilsGlobal.$eqCallTypeGlobal(CALL_TYPE_CALL, CALL_TYPE_BATCH)).to.eventually.be.false; + }); + }); + + describe('eqExecTypeGlobal', function () { + it('returns true if both exec types are equal', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_TRY, EXEC_TYPE_TRY)).to.eventually.be.true; + }); + + it('returns false if both exec types are different', async function () { + expect(this.utilsGlobal.$eqExecTypeGlobal(EXEC_TYPE_DEFAULT, EXEC_TYPE_TRY)).to.eventually.be.false; + }); + }); + + describe('eqModeSelectorGlobal', function () { + it('returns true if both selectors are equal', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x12345678')).to.eventually.be.true; + }); + + it('returns false if both selectors are different', async function () { + expect(this.utilsGlobal.$eqModeSelectorGlobal('0x12345678', '0x87654321')).to.eventually.be.false; + }); + }); + + describe('eqModePayloadGlobal', function () { + it('returns true if both payloads are equal', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(0, 22))).to.eventually.be + .true; + }); + + it('returns false if both payloads are different', async function () { + expect(this.utilsGlobal.$eqModePayloadGlobal(ethers.toBeHex(0, 22), ethers.toBeHex(1, 22))).to.eventually.be + .false; + }); + }); + }); +}); diff --git a/test/helpers/erc4337.js b/test/helpers/erc4337.js new file mode 100644 index 00000000000..5901375b174 --- /dev/null +++ b/test/helpers/erc4337.js @@ -0,0 +1,95 @@ +const { ethers } = require('hardhat'); + +const SIG_VALIDATION_SUCCESS = '0x0000000000000000000000000000000000000000'; +const SIG_VALIDATION_FAILURE = '0x0000000000000000000000000000000000000001'; + +function getAddress(account) { + return account.target ?? account.address ?? account; +} + +function pack(left, right) { + return ethers.solidityPacked(['uint128', 'uint128'], [left, right]); +} + +function packValidationData(validAfter, validUntil, authorizer) { + return ethers.solidityPacked( + ['uint48', 'uint48', 'address'], + [ + validAfter, + validUntil, + typeof authorizer == 'boolean' + ? authorizer + ? SIG_VALIDATION_SUCCESS + : SIG_VALIDATION_FAILURE + : getAddress(authorizer), + ], + ); +} + +function packPaymasterData(paymaster, verificationGasLimit, postOpGasLimit) { + return ethers.solidityPacked( + ['address', 'uint128', 'uint128'], + [getAddress(paymaster), verificationGasLimit, postOpGasLimit], + ); +} + +/// Represent one user operation +class UserOperation { + constructor(params) { + this.sender = getAddress(params.sender); + this.nonce = params.nonce; + this.initCode = params.initCode ?? '0x'; + this.callData = params.callData ?? '0x'; + this.verificationGas = params.verificationGas ?? 10_000_000n; + this.callGas = params.callGas ?? 100_000n; + this.preVerificationGas = params.preVerificationGas ?? 100_000n; + this.maxPriorityFee = params.maxPriorityFee ?? 100_000n; + this.maxFeePerGas = params.maxFeePerGas ?? 100_000n; + this.paymasterAndData = params.paymasterAndData ?? '0x'; + this.signature = params.signature ?? '0x'; + } + + get packed() { + return { + sender: this.sender, + nonce: this.nonce, + initCode: this.initCode, + callData: this.callData, + accountGasLimits: pack(this.verificationGas, this.callGas), + preVerificationGas: this.preVerificationGas, + gasFees: pack(this.maxPriorityFee, this.maxFeePerGas), + paymasterAndData: this.paymasterAndData, + signature: this.signature, + }; + } + + hash(entrypoint, chainId) { + const p = this.packed; + const h = ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'uint256', 'bytes32', 'bytes32', 'uint256', 'uint256', 'uint256', 'uint256'], + [ + p.sender, + p.nonce, + ethers.keccak256(p.initCode), + ethers.keccak256(p.callData), + p.accountGasLimits, + p.preVerificationGas, + p.gasFees, + ethers.keccak256(p.paymasterAndData), + ], + ), + ); + return ethers.keccak256( + ethers.AbiCoder.defaultAbiCoder().encode(['bytes32', 'address', 'uint256'], [h, getAddress(entrypoint), chainId]), + ); + } +} + +module.exports = { + SIG_VALIDATION_SUCCESS, + SIG_VALIDATION_FAILURE, + packValidationData, + packPaymasterData, + UserOperation, +}; diff --git a/test/helpers/erc7579.js b/test/helpers/erc7579.js new file mode 100644 index 00000000000..6c3b4759b73 --- /dev/null +++ b/test/helpers/erc7579.js @@ -0,0 +1,58 @@ +const { ethers } = require('hardhat'); + +const MODULE_TYPE_VALIDATOR = 1; +const MODULE_TYPE_EXECUTOR = 2; +const MODULE_TYPE_FALLBACK = 3; +const MODULE_TYPE_HOOK = 4; + +const EXEC_TYPE_DEFAULT = '0x00'; +const EXEC_TYPE_TRY = '0x01'; + +const CALL_TYPE_CALL = '0x00'; +const CALL_TYPE_BATCH = '0x01'; +const CALL_TYPE_DELEGATE = '0xff'; + +const encodeMode = ({ + callType = '0x00', + execType = '0x00', + selector = '0x00000000', + payload = '0x00000000000000000000000000000000000000000000', +} = {}) => + ethers.solidityPacked( + ['bytes1', 'bytes1', 'bytes4', 'bytes4', 'bytes22'], + [callType, execType, '0x00000000', selector, payload], + ); + +const encodeSingle = (target, value = 0n, data = '0x') => + ethers.solidityPacked(['address', 'uint256', 'bytes'], [target.target ?? target.address ?? target, value, data]); + +const encodeBatch = (...entries) => + ethers.AbiCoder.defaultAbiCoder().encode( + ['(address,uint256,bytes)[]'], + [ + entries.map(entry => + Array.isArray(entry) + ? [entry[0].target ?? entry[0].address ?? entry[0], entry[1] ?? 0n, entry[2] ?? '0x'] + : [entry.target.target ?? entry.target.address ?? entry.target, entry.value ?? 0n, entry.data ?? '0x'], + ), + ], + ); + +const encodeDelegate = (target, data = '0x') => + ethers.solidityPacked(['address', 'bytes'], [target.target ?? target.address ?? target, data]); + +module.exports = { + MODULE_TYPE_VALIDATOR, + MODULE_TYPE_EXECUTOR, + MODULE_TYPE_FALLBACK, + MODULE_TYPE_HOOK, + EXEC_TYPE_DEFAULT, + EXEC_TYPE_TRY, + CALL_TYPE_CALL, + CALL_TYPE_BATCH, + CALL_TYPE_DELEGATE, + encodeMode, + encodeSingle, + encodeBatch, + encodeDelegate, +}; diff --git a/test/utils/Packing.t.sol b/test/utils/Packing.t.sol index 9531f1bffbb..40f052c80ff 100644 --- a/test/utils/Packing.t.sol +++ b/test/utils/Packing.t.sol @@ -9,182 +9,287 @@ import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; contract PackingTest is Test { using Packing for *; - function testPack(bytes1 left, bytes1 right) external { + function testPack(bytes1 left, bytes1 right) external pure { assertEq(left, Packing.pack_1_1(left, right).extract_2_1(0)); assertEq(right, Packing.pack_1_1(left, right).extract_2_1(1)); } - function testPack(bytes2 left, bytes2 right) external { + function testPack(bytes2 left, bytes2 right) external pure { assertEq(left, Packing.pack_2_2(left, right).extract_4_2(0)); assertEq(right, Packing.pack_2_2(left, right).extract_4_2(2)); } - function testPack(bytes2 left, bytes4 right) external { + function testPack(bytes2 left, bytes4 right) external pure { assertEq(left, Packing.pack_2_4(left, right).extract_6_2(0)); assertEq(right, Packing.pack_2_4(left, right).extract_6_4(2)); } - function testPack(bytes2 left, bytes6 right) external { + function testPack(bytes2 left, bytes6 right) external pure { assertEq(left, Packing.pack_2_6(left, right).extract_8_2(0)); assertEq(right, Packing.pack_2_6(left, right).extract_8_6(2)); } - function testPack(bytes4 left, bytes2 right) external { + function testPack(bytes2 left, bytes8 right) external pure { + assertEq(left, Packing.pack_2_8(left, right).extract_10_2(0)); + assertEq(right, Packing.pack_2_8(left, right).extract_10_8(2)); + } + + function testPack(bytes2 left, bytes10 right) external pure { + assertEq(left, Packing.pack_2_10(left, right).extract_12_2(0)); + assertEq(right, Packing.pack_2_10(left, right).extract_12_10(2)); + } + + function testPack(bytes2 left, bytes20 right) external pure { + assertEq(left, Packing.pack_2_20(left, right).extract_22_2(0)); + assertEq(right, Packing.pack_2_20(left, right).extract_22_20(2)); + } + + function testPack(bytes2 left, bytes22 right) external pure { + assertEq(left, Packing.pack_2_22(left, right).extract_24_2(0)); + assertEq(right, Packing.pack_2_22(left, right).extract_24_22(2)); + } + + function testPack(bytes4 left, bytes2 right) external pure { assertEq(left, Packing.pack_4_2(left, right).extract_6_4(0)); assertEq(right, Packing.pack_4_2(left, right).extract_6_2(4)); } - function testPack(bytes4 left, bytes4 right) external { + function testPack(bytes4 left, bytes4 right) external pure { assertEq(left, Packing.pack_4_4(left, right).extract_8_4(0)); assertEq(right, Packing.pack_4_4(left, right).extract_8_4(4)); } - function testPack(bytes4 left, bytes8 right) external { + function testPack(bytes4 left, bytes6 right) external pure { + assertEq(left, Packing.pack_4_6(left, right).extract_10_4(0)); + assertEq(right, Packing.pack_4_6(left, right).extract_10_6(4)); + } + + function testPack(bytes4 left, bytes8 right) external pure { assertEq(left, Packing.pack_4_8(left, right).extract_12_4(0)); assertEq(right, Packing.pack_4_8(left, right).extract_12_8(4)); } - function testPack(bytes4 left, bytes12 right) external { + function testPack(bytes4 left, bytes12 right) external pure { assertEq(left, Packing.pack_4_12(left, right).extract_16_4(0)); assertEq(right, Packing.pack_4_12(left, right).extract_16_12(4)); } - function testPack(bytes4 left, bytes16 right) external { + function testPack(bytes4 left, bytes16 right) external pure { assertEq(left, Packing.pack_4_16(left, right).extract_20_4(0)); assertEq(right, Packing.pack_4_16(left, right).extract_20_16(4)); } - function testPack(bytes4 left, bytes20 right) external { + function testPack(bytes4 left, bytes20 right) external pure { assertEq(left, Packing.pack_4_20(left, right).extract_24_4(0)); assertEq(right, Packing.pack_4_20(left, right).extract_24_20(4)); } - function testPack(bytes4 left, bytes24 right) external { + function testPack(bytes4 left, bytes24 right) external pure { assertEq(left, Packing.pack_4_24(left, right).extract_28_4(0)); assertEq(right, Packing.pack_4_24(left, right).extract_28_24(4)); } - function testPack(bytes4 left, bytes28 right) external { + function testPack(bytes4 left, bytes28 right) external pure { assertEq(left, Packing.pack_4_28(left, right).extract_32_4(0)); assertEq(right, Packing.pack_4_28(left, right).extract_32_28(4)); } - function testPack(bytes6 left, bytes2 right) external { + function testPack(bytes6 left, bytes2 right) external pure { assertEq(left, Packing.pack_6_2(left, right).extract_8_6(0)); assertEq(right, Packing.pack_6_2(left, right).extract_8_2(6)); } - function testPack(bytes6 left, bytes6 right) external { + function testPack(bytes6 left, bytes4 right) external pure { + assertEq(left, Packing.pack_6_4(left, right).extract_10_6(0)); + assertEq(right, Packing.pack_6_4(left, right).extract_10_4(6)); + } + + function testPack(bytes6 left, bytes6 right) external pure { assertEq(left, Packing.pack_6_6(left, right).extract_12_6(0)); assertEq(right, Packing.pack_6_6(left, right).extract_12_6(6)); } - function testPack(bytes8 left, bytes4 right) external { + function testPack(bytes6 left, bytes10 right) external pure { + assertEq(left, Packing.pack_6_10(left, right).extract_16_6(0)); + assertEq(right, Packing.pack_6_10(left, right).extract_16_10(6)); + } + + function testPack(bytes6 left, bytes16 right) external pure { + assertEq(left, Packing.pack_6_16(left, right).extract_22_6(0)); + assertEq(right, Packing.pack_6_16(left, right).extract_22_16(6)); + } + + function testPack(bytes6 left, bytes22 right) external pure { + assertEq(left, Packing.pack_6_22(left, right).extract_28_6(0)); + assertEq(right, Packing.pack_6_22(left, right).extract_28_22(6)); + } + + function testPack(bytes8 left, bytes2 right) external pure { + assertEq(left, Packing.pack_8_2(left, right).extract_10_8(0)); + assertEq(right, Packing.pack_8_2(left, right).extract_10_2(8)); + } + + function testPack(bytes8 left, bytes4 right) external pure { assertEq(left, Packing.pack_8_4(left, right).extract_12_8(0)); assertEq(right, Packing.pack_8_4(left, right).extract_12_4(8)); } - function testPack(bytes8 left, bytes8 right) external { + function testPack(bytes8 left, bytes8 right) external pure { assertEq(left, Packing.pack_8_8(left, right).extract_16_8(0)); assertEq(right, Packing.pack_8_8(left, right).extract_16_8(8)); } - function testPack(bytes8 left, bytes12 right) external { + function testPack(bytes8 left, bytes12 right) external pure { assertEq(left, Packing.pack_8_12(left, right).extract_20_8(0)); assertEq(right, Packing.pack_8_12(left, right).extract_20_12(8)); } - function testPack(bytes8 left, bytes16 right) external { + function testPack(bytes8 left, bytes16 right) external pure { assertEq(left, Packing.pack_8_16(left, right).extract_24_8(0)); assertEq(right, Packing.pack_8_16(left, right).extract_24_16(8)); } - function testPack(bytes8 left, bytes20 right) external { + function testPack(bytes8 left, bytes20 right) external pure { assertEq(left, Packing.pack_8_20(left, right).extract_28_8(0)); assertEq(right, Packing.pack_8_20(left, right).extract_28_20(8)); } - function testPack(bytes8 left, bytes24 right) external { + function testPack(bytes8 left, bytes24 right) external pure { assertEq(left, Packing.pack_8_24(left, right).extract_32_8(0)); assertEq(right, Packing.pack_8_24(left, right).extract_32_24(8)); } - function testPack(bytes12 left, bytes4 right) external { + function testPack(bytes10 left, bytes2 right) external pure { + assertEq(left, Packing.pack_10_2(left, right).extract_12_10(0)); + assertEq(right, Packing.pack_10_2(left, right).extract_12_2(10)); + } + + function testPack(bytes10 left, bytes6 right) external pure { + assertEq(left, Packing.pack_10_6(left, right).extract_16_10(0)); + assertEq(right, Packing.pack_10_6(left, right).extract_16_6(10)); + } + + function testPack(bytes10 left, bytes10 right) external pure { + assertEq(left, Packing.pack_10_10(left, right).extract_20_10(0)); + assertEq(right, Packing.pack_10_10(left, right).extract_20_10(10)); + } + + function testPack(bytes10 left, bytes12 right) external pure { + assertEq(left, Packing.pack_10_12(left, right).extract_22_10(0)); + assertEq(right, Packing.pack_10_12(left, right).extract_22_12(10)); + } + + function testPack(bytes10 left, bytes22 right) external pure { + assertEq(left, Packing.pack_10_22(left, right).extract_32_10(0)); + assertEq(right, Packing.pack_10_22(left, right).extract_32_22(10)); + } + + function testPack(bytes12 left, bytes4 right) external pure { assertEq(left, Packing.pack_12_4(left, right).extract_16_12(0)); assertEq(right, Packing.pack_12_4(left, right).extract_16_4(12)); } - function testPack(bytes12 left, bytes8 right) external { + function testPack(bytes12 left, bytes8 right) external pure { assertEq(left, Packing.pack_12_8(left, right).extract_20_12(0)); assertEq(right, Packing.pack_12_8(left, right).extract_20_8(12)); } - function testPack(bytes12 left, bytes12 right) external { + function testPack(bytes12 left, bytes10 right) external pure { + assertEq(left, Packing.pack_12_10(left, right).extract_22_12(0)); + assertEq(right, Packing.pack_12_10(left, right).extract_22_10(12)); + } + + function testPack(bytes12 left, bytes12 right) external pure { assertEq(left, Packing.pack_12_12(left, right).extract_24_12(0)); assertEq(right, Packing.pack_12_12(left, right).extract_24_12(12)); } - function testPack(bytes12 left, bytes16 right) external { + function testPack(bytes12 left, bytes16 right) external pure { assertEq(left, Packing.pack_12_16(left, right).extract_28_12(0)); assertEq(right, Packing.pack_12_16(left, right).extract_28_16(12)); } - function testPack(bytes12 left, bytes20 right) external { + function testPack(bytes12 left, bytes20 right) external pure { assertEq(left, Packing.pack_12_20(left, right).extract_32_12(0)); assertEq(right, Packing.pack_12_20(left, right).extract_32_20(12)); } - function testPack(bytes16 left, bytes4 right) external { + function testPack(bytes16 left, bytes4 right) external pure { assertEq(left, Packing.pack_16_4(left, right).extract_20_16(0)); assertEq(right, Packing.pack_16_4(left, right).extract_20_4(16)); } - function testPack(bytes16 left, bytes8 right) external { + function testPack(bytes16 left, bytes6 right) external pure { + assertEq(left, Packing.pack_16_6(left, right).extract_22_16(0)); + assertEq(right, Packing.pack_16_6(left, right).extract_22_6(16)); + } + + function testPack(bytes16 left, bytes8 right) external pure { assertEq(left, Packing.pack_16_8(left, right).extract_24_16(0)); assertEq(right, Packing.pack_16_8(left, right).extract_24_8(16)); } - function testPack(bytes16 left, bytes12 right) external { + function testPack(bytes16 left, bytes12 right) external pure { assertEq(left, Packing.pack_16_12(left, right).extract_28_16(0)); assertEq(right, Packing.pack_16_12(left, right).extract_28_12(16)); } - function testPack(bytes16 left, bytes16 right) external { + function testPack(bytes16 left, bytes16 right) external pure { assertEq(left, Packing.pack_16_16(left, right).extract_32_16(0)); assertEq(right, Packing.pack_16_16(left, right).extract_32_16(16)); } - function testPack(bytes20 left, bytes4 right) external { + function testPack(bytes20 left, bytes2 right) external pure { + assertEq(left, Packing.pack_20_2(left, right).extract_22_20(0)); + assertEq(right, Packing.pack_20_2(left, right).extract_22_2(20)); + } + + function testPack(bytes20 left, bytes4 right) external pure { assertEq(left, Packing.pack_20_4(left, right).extract_24_20(0)); assertEq(right, Packing.pack_20_4(left, right).extract_24_4(20)); } - function testPack(bytes20 left, bytes8 right) external { + function testPack(bytes20 left, bytes8 right) external pure { assertEq(left, Packing.pack_20_8(left, right).extract_28_20(0)); assertEq(right, Packing.pack_20_8(left, right).extract_28_8(20)); } - function testPack(bytes20 left, bytes12 right) external { + function testPack(bytes20 left, bytes12 right) external pure { assertEq(left, Packing.pack_20_12(left, right).extract_32_20(0)); assertEq(right, Packing.pack_20_12(left, right).extract_32_12(20)); } - function testPack(bytes24 left, bytes4 right) external { + function testPack(bytes22 left, bytes2 right) external pure { + assertEq(left, Packing.pack_22_2(left, right).extract_24_22(0)); + assertEq(right, Packing.pack_22_2(left, right).extract_24_2(22)); + } + + function testPack(bytes22 left, bytes6 right) external pure { + assertEq(left, Packing.pack_22_6(left, right).extract_28_22(0)); + assertEq(right, Packing.pack_22_6(left, right).extract_28_6(22)); + } + + function testPack(bytes22 left, bytes10 right) external pure { + assertEq(left, Packing.pack_22_10(left, right).extract_32_22(0)); + assertEq(right, Packing.pack_22_10(left, right).extract_32_10(22)); + } + + function testPack(bytes24 left, bytes4 right) external pure { assertEq(left, Packing.pack_24_4(left, right).extract_28_24(0)); assertEq(right, Packing.pack_24_4(left, right).extract_28_4(24)); } - function testPack(bytes24 left, bytes8 right) external { + function testPack(bytes24 left, bytes8 right) external pure { assertEq(left, Packing.pack_24_8(left, right).extract_32_24(0)); assertEq(right, Packing.pack_24_8(left, right).extract_32_8(24)); } - function testPack(bytes28 left, bytes4 right) external { + function testPack(bytes28 left, bytes4 right) external pure { assertEq(left, Packing.pack_28_4(left, right).extract_32_28(0)); assertEq(right, Packing.pack_28_4(left, right).extract_32_4(28)); } - function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes2 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 1)); bytes1 oldValue = container.extract_2_1(offset); @@ -193,7 +298,7 @@ contract PackingTest is Test { assertEq(container, container.replace_2_1(newValue, offset).replace_2_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 3)); bytes1 oldValue = container.extract_4_1(offset); @@ -202,7 +307,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_1(newValue, offset).replace_4_1(oldValue, offset)); } - function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes4 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes2 oldValue = container.extract_4_2(offset); @@ -211,7 +316,7 @@ contract PackingTest is Test { assertEq(container, container.replace_4_2(newValue, offset).replace_4_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 5)); bytes1 oldValue = container.extract_6_1(offset); @@ -220,7 +325,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_1(newValue, offset).replace_6_1(oldValue, offset)); } - function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes2 oldValue = container.extract_6_2(offset); @@ -229,7 +334,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_2(newValue, offset).replace_6_2(oldValue, offset)); } - function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes6 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes4 oldValue = container.extract_6_4(offset); @@ -238,7 +343,7 @@ contract PackingTest is Test { assertEq(container, container.replace_6_4(newValue, offset).replace_6_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 7)); bytes1 oldValue = container.extract_8_1(offset); @@ -247,7 +352,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_1(newValue, offset).replace_8_1(oldValue, offset)); } - function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes2 oldValue = container.extract_8_2(offset); @@ -256,7 +361,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_2(newValue, offset).replace_8_2(oldValue, offset)); } - function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes4 oldValue = container.extract_8_4(offset); @@ -265,7 +370,7 @@ contract PackingTest is Test { assertEq(container, container.replace_8_4(newValue, offset).replace_8_4(oldValue, offset)); } - function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes8 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 2)); bytes6 oldValue = container.extract_8_6(offset); @@ -274,7 +379,52 @@ contract PackingTest is Test { assertEq(container, container.replace_8_6(newValue, offset).replace_8_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes10 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 9)); + + bytes1 oldValue = container.extract_10_1(offset); + + assertEq(newValue, container.replace_10_1(newValue, offset).extract_10_1(offset)); + assertEq(container, container.replace_10_1(newValue, offset).replace_10_1(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 8)); + + bytes2 oldValue = container.extract_10_2(offset); + + assertEq(newValue, container.replace_10_2(newValue, offset).extract_10_2(offset)); + assertEq(container, container.replace_10_2(newValue, offset).replace_10_2(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes4 oldValue = container.extract_10_4(offset); + + assertEq(newValue, container.replace_10_4(newValue, offset).extract_10_4(offset)); + assertEq(container, container.replace_10_4(newValue, offset).replace_10_4(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 4)); + + bytes6 oldValue = container.extract_10_6(offset); + + assertEq(newValue, container.replace_10_6(newValue, offset).extract_10_6(offset)); + assertEq(container, container.replace_10_6(newValue, offset).replace_10_6(oldValue, offset)); + } + + function testReplace(bytes10 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes8 oldValue = container.extract_10_8(offset); + + assertEq(newValue, container.replace_10_8(newValue, offset).extract_10_8(offset)); + assertEq(container, container.replace_10_8(newValue, offset).replace_10_8(oldValue, offset)); + } + + function testReplace(bytes12 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 11)); bytes1 oldValue = container.extract_12_1(offset); @@ -283,7 +433,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_1(newValue, offset).replace_12_1(oldValue, offset)); } - function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes2 oldValue = container.extract_12_2(offset); @@ -292,7 +442,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_2(newValue, offset).replace_12_2(oldValue, offset)); } - function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes4 oldValue = container.extract_12_4(offset); @@ -301,7 +451,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_4(newValue, offset).replace_12_4(oldValue, offset)); } - function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 6)); bytes6 oldValue = container.extract_12_6(offset); @@ -310,7 +460,7 @@ contract PackingTest is Test { assertEq(container, container.replace_12_6(newValue, offset).replace_12_6(oldValue, offset)); } - function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes8 oldValue = container.extract_12_8(offset); @@ -319,7 +469,16 @@ contract PackingTest is Test { assertEq(container, container.replace_12_8(newValue, offset).replace_12_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes12 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes10 oldValue = container.extract_12_10(offset); + + assertEq(newValue, container.replace_12_10(newValue, offset).extract_12_10(offset)); + assertEq(container, container.replace_12_10(newValue, offset).replace_12_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 15)); bytes1 oldValue = container.extract_16_1(offset); @@ -328,7 +487,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_1(newValue, offset).replace_16_1(oldValue, offset)); } - function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes2 oldValue = container.extract_16_2(offset); @@ -337,7 +496,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_2(newValue, offset).replace_16_2(oldValue, offset)); } - function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes4 oldValue = container.extract_16_4(offset); @@ -346,7 +505,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_4(newValue, offset).replace_16_4(oldValue, offset)); } - function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 10)); bytes6 oldValue = container.extract_16_6(offset); @@ -355,7 +514,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_6(newValue, offset).replace_16_6(oldValue, offset)); } - function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes8 oldValue = container.extract_16_8(offset); @@ -364,7 +523,16 @@ contract PackingTest is Test { assertEq(container, container.replace_16_8(newValue, offset).replace_16_8(oldValue, offset)); } - function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes16 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes10 oldValue = container.extract_16_10(offset); + + assertEq(newValue, container.replace_16_10(newValue, offset).extract_16_10(offset)); + assertEq(container, container.replace_16_10(newValue, offset).replace_16_10(oldValue, offset)); + } + + function testReplace(bytes16 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes12 oldValue = container.extract_16_12(offset); @@ -373,7 +541,7 @@ contract PackingTest is Test { assertEq(container, container.replace_16_12(newValue, offset).replace_16_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 19)); bytes1 oldValue = container.extract_20_1(offset); @@ -382,7 +550,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_1(newValue, offset).replace_20_1(oldValue, offset)); } - function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes2 oldValue = container.extract_20_2(offset); @@ -391,7 +559,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_2(newValue, offset).replace_20_2(oldValue, offset)); } - function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes4 oldValue = container.extract_20_4(offset); @@ -400,7 +568,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_4(newValue, offset).replace_20_4(oldValue, offset)); } - function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 14)); bytes6 oldValue = container.extract_20_6(offset); @@ -409,7 +577,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_6(newValue, offset).replace_20_6(oldValue, offset)); } - function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes8 oldValue = container.extract_20_8(offset); @@ -418,7 +586,16 @@ contract PackingTest is Test { assertEq(container, container.replace_20_8(newValue, offset).replace_20_8(oldValue, offset)); } - function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes10 oldValue = container.extract_20_10(offset); + + assertEq(newValue, container.replace_20_10(newValue, offset).extract_20_10(offset)); + assertEq(container, container.replace_20_10(newValue, offset).replace_20_10(oldValue, offset)); + } + + function testReplace(bytes20 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes12 oldValue = container.extract_20_12(offset); @@ -427,7 +604,7 @@ contract PackingTest is Test { assertEq(container, container.replace_20_12(newValue, offset).replace_20_12(oldValue, offset)); } - function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes20 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes16 oldValue = container.extract_20_16(offset); @@ -436,7 +613,88 @@ contract PackingTest is Test { assertEq(container, container.replace_20_16(newValue, offset).replace_20_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes22 container, bytes1 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 21)); + + bytes1 oldValue = container.extract_22_1(offset); + + assertEq(newValue, container.replace_22_1(newValue, offset).extract_22_1(offset)); + assertEq(container, container.replace_22_1(newValue, offset).replace_22_1(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes2 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 20)); + + bytes2 oldValue = container.extract_22_2(offset); + + assertEq(newValue, container.replace_22_2(newValue, offset).extract_22_2(offset)); + assertEq(container, container.replace_22_2(newValue, offset).replace_22_2(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes4 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes4 oldValue = container.extract_22_4(offset); + + assertEq(newValue, container.replace_22_4(newValue, offset).extract_22_4(offset)); + assertEq(container, container.replace_22_4(newValue, offset).replace_22_4(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes6 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 16)); + + bytes6 oldValue = container.extract_22_6(offset); + + assertEq(newValue, container.replace_22_6(newValue, offset).extract_22_6(offset)); + assertEq(container, container.replace_22_6(newValue, offset).replace_22_6(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes8 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes8 oldValue = container.extract_22_8(offset); + + assertEq(newValue, container.replace_22_8(newValue, offset).extract_22_8(offset)); + assertEq(container, container.replace_22_8(newValue, offset).replace_22_8(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 12)); + + bytes10 oldValue = container.extract_22_10(offset); + + assertEq(newValue, container.replace_22_10(newValue, offset).extract_22_10(offset)); + assertEq(container, container.replace_22_10(newValue, offset).replace_22_10(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes12 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes12 oldValue = container.extract_22_12(offset); + + assertEq(newValue, container.replace_22_12(newValue, offset).extract_22_12(offset)); + assertEq(container, container.replace_22_12(newValue, offset).replace_22_12(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes16 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes16 oldValue = container.extract_22_16(offset); + + assertEq(newValue, container.replace_22_16(newValue, offset).extract_22_16(offset)); + assertEq(container, container.replace_22_16(newValue, offset).replace_22_16(oldValue, offset)); + } + + function testReplace(bytes22 container, bytes20 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes20 oldValue = container.extract_22_20(offset); + + assertEq(newValue, container.replace_22_20(newValue, offset).extract_22_20(offset)); + assertEq(container, container.replace_22_20(newValue, offset).replace_22_20(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 23)); bytes1 oldValue = container.extract_24_1(offset); @@ -445,7 +703,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_1(newValue, offset).replace_24_1(oldValue, offset)); } - function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes2 oldValue = container.extract_24_2(offset); @@ -454,7 +712,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_2(newValue, offset).replace_24_2(oldValue, offset)); } - function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes4 oldValue = container.extract_24_4(offset); @@ -463,7 +721,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_4(newValue, offset).replace_24_4(oldValue, offset)); } - function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 18)); bytes6 oldValue = container.extract_24_6(offset); @@ -472,7 +730,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_6(newValue, offset).replace_24_6(oldValue, offset)); } - function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes8 oldValue = container.extract_24_8(offset); @@ -481,7 +739,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_8(newValue, offset).replace_24_8(oldValue, offset)); } - function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 14)); + + bytes10 oldValue = container.extract_24_10(offset); + + assertEq(newValue, container.replace_24_10(newValue, offset).extract_24_10(offset)); + assertEq(container, container.replace_24_10(newValue, offset).replace_24_10(oldValue, offset)); + } + + function testReplace(bytes24 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes12 oldValue = container.extract_24_12(offset); @@ -490,7 +757,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_12(newValue, offset).replace_24_12(oldValue, offset)); } - function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes16 oldValue = container.extract_24_16(offset); @@ -499,7 +766,7 @@ contract PackingTest is Test { assertEq(container, container.replace_24_16(newValue, offset).replace_24_16(oldValue, offset)); } - function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes20 oldValue = container.extract_24_20(offset); @@ -508,7 +775,16 @@ contract PackingTest is Test { assertEq(container, container.replace_24_20(newValue, offset).replace_24_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes24 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 2)); + + bytes22 oldValue = container.extract_24_22(offset); + + assertEq(newValue, container.replace_24_22(newValue, offset).extract_24_22(offset)); + assertEq(container, container.replace_24_22(newValue, offset).replace_24_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 27)); bytes1 oldValue = container.extract_28_1(offset); @@ -517,7 +793,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_1(newValue, offset).replace_28_1(oldValue, offset)); } - function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes2 oldValue = container.extract_28_2(offset); @@ -526,7 +802,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_2(newValue, offset).replace_28_2(oldValue, offset)); } - function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes4 oldValue = container.extract_28_4(offset); @@ -535,7 +811,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_4(newValue, offset).replace_28_4(oldValue, offset)); } - function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 22)); bytes6 oldValue = container.extract_28_6(offset); @@ -544,7 +820,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_6(newValue, offset).replace_28_6(oldValue, offset)); } - function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes8 oldValue = container.extract_28_8(offset); @@ -553,7 +829,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_8(newValue, offset).replace_28_8(oldValue, offset)); } - function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 18)); + + bytes10 oldValue = container.extract_28_10(offset); + + assertEq(newValue, container.replace_28_10(newValue, offset).extract_28_10(offset)); + assertEq(container, container.replace_28_10(newValue, offset).replace_28_10(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes12 oldValue = container.extract_28_12(offset); @@ -562,7 +847,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_12(newValue, offset).replace_28_12(oldValue, offset)); } - function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes16 oldValue = container.extract_28_16(offset); @@ -571,7 +856,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_16(newValue, offset).replace_28_16(oldValue, offset)); } - function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes20 oldValue = container.extract_28_20(offset); @@ -580,7 +865,16 @@ contract PackingTest is Test { assertEq(container, container.replace_28_20(newValue, offset).replace_28_20(oldValue, offset)); } - function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes28 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 6)); + + bytes22 oldValue = container.extract_28_22(offset); + + assertEq(newValue, container.replace_28_22(newValue, offset).extract_28_22(offset)); + assertEq(container, container.replace_28_22(newValue, offset).replace_28_22(oldValue, offset)); + } + + function testReplace(bytes28 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes24 oldValue = container.extract_28_24(offset); @@ -589,7 +883,7 @@ contract PackingTest is Test { assertEq(container, container.replace_28_24(newValue, offset).replace_28_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes1 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 31)); bytes1 oldValue = container.extract_32_1(offset); @@ -598,7 +892,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_1(newValue, offset).replace_32_1(oldValue, offset)); } - function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes2 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 30)); bytes2 oldValue = container.extract_32_2(offset); @@ -607,7 +901,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_2(newValue, offset).replace_32_2(oldValue, offset)); } - function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes4 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 28)); bytes4 oldValue = container.extract_32_4(offset); @@ -616,7 +910,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_4(newValue, offset).replace_32_4(oldValue, offset)); } - function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes6 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 26)); bytes6 oldValue = container.extract_32_6(offset); @@ -625,7 +919,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_6(newValue, offset).replace_32_6(oldValue, offset)); } - function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes8 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 24)); bytes8 oldValue = container.extract_32_8(offset); @@ -634,7 +928,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_8(newValue, offset).replace_32_8(oldValue, offset)); } - function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes10 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 22)); + + bytes10 oldValue = container.extract_32_10(offset); + + assertEq(newValue, container.replace_32_10(newValue, offset).extract_32_10(offset)); + assertEq(container, container.replace_32_10(newValue, offset).replace_32_10(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes12 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 20)); bytes12 oldValue = container.extract_32_12(offset); @@ -643,7 +946,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_12(newValue, offset).replace_32_12(oldValue, offset)); } - function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes16 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 16)); bytes16 oldValue = container.extract_32_16(offset); @@ -652,7 +955,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_16(newValue, offset).replace_32_16(oldValue, offset)); } - function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes20 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 12)); bytes20 oldValue = container.extract_32_20(offset); @@ -661,7 +964,16 @@ contract PackingTest is Test { assertEq(container, container.replace_32_20(newValue, offset).replace_32_20(oldValue, offset)); } - function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes22 newValue, uint8 offset) external pure { + offset = uint8(bound(offset, 0, 10)); + + bytes22 oldValue = container.extract_32_22(offset); + + assertEq(newValue, container.replace_32_22(newValue, offset).extract_32_22(offset)); + assertEq(container, container.replace_32_22(newValue, offset).replace_32_22(oldValue, offset)); + } + + function testReplace(bytes32 container, bytes24 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 8)); bytes24 oldValue = container.extract_32_24(offset); @@ -670,7 +982,7 @@ contract PackingTest is Test { assertEq(container, container.replace_32_24(newValue, offset).replace_32_24(oldValue, offset)); } - function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external { + function testReplace(bytes32 container, bytes28 newValue, uint8 offset) external pure { offset = uint8(bound(offset, 0, 4)); bytes28 oldValue = container.extract_32_28(offset);