diff --git a/lib_forge_std/foundry.toml b/lib_forge_std/foundry.toml deleted file mode 100644 index 2bc66fa..0000000 --- a/lib_forge_std/foundry.toml +++ /dev/null @@ -1,21 +0,0 @@ -[profile.default] -fs_permissions = [{ access = "read-write", path = "./"}] - -[rpc_endpoints] -# The RPC URLs are modified versions of the default for testing initialization. -mainnet = "https://eth-mainnet.alchemyapi.io/v2/WV407BEiBmjNJfKo9Uo_55u0z0ITyCOX" # Different API key. -optimism_sepolia = "https://sepolia.optimism.io/" # Adds a trailing slash. -arbitrum_one_sepolia = "https://sepolia-rollup.arbitrum.io/rpc/" # Adds a trailing slash. -needs_undefined_env_var = "${UNDEFINED_RPC_URL_PLACEHOLDER}" - -[fmt] -# These are all the `forge fmt` defaults. -line_length = 120 -tab_width = 4 -bracket_spacing = false -int_types = 'long' -multiline_func_header = 'attributes_first' -quote_style = 'double' -number_underscore = 'preserve' -single_line_statement_blocks = 'preserve' -ignore = ["src/console.sol", "src/console2.sol"] \ No newline at end of file diff --git a/lib_forge_std/package.json b/lib_forge_std/package.json deleted file mode 100644 index acc004b..0000000 --- a/lib_forge_std/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "forge-std", - "version": "1.7.6", - "description": "Forge Standard Library is a collection of helpful contracts and libraries for use with Forge and Foundry.", - "homepage": "https://book.getfoundry.sh/forge/forge-std", - "bugs": "https://github.com/foundry-rs/forge-std/issues", - "license": "(Apache-2.0 OR MIT)", - "author": "Contributors to Forge Standard Library", - "files": [ - "src/**/*" - ], - "repository": { - "type": "git", - "url": "https://github.com/foundry-rs/forge-std.git" - } -} diff --git a/lib_forge_std/scripts/vm.py b/lib_forge_std/scripts/vm.py deleted file mode 100755 index f0537db..0000000 --- a/lib_forge_std/scripts/vm.py +++ /dev/null @@ -1,635 +0,0 @@ -#!/usr/bin/env python3 - -import copy -import json -import re -import subprocess -from enum import Enum as PyEnum -from typing import Callable -from urllib import request - -VoidFn = Callable[[], None] - -CHEATCODES_JSON_URL = "https://raw.githubusercontent.com/foundry-rs/foundry/master/crates/cheatcodes/assets/cheatcodes.json" -OUT_PATH = "src/Vm.sol" - -VM_SAFE_DOC = """\ -/// The `VmSafe` interface does not allow manipulation of the EVM state or other actions that may -/// result in Script simulations differing from on-chain execution. It is recommended to only use -/// these cheats in scripts. -""" - -VM_DOC = """\ -/// The `Vm` interface does allow manipulation of the EVM state. These are all intended to be used -/// in tests, but it is not recommended to use these cheats in scripts. -""" - - -def main(): - json_str = request.urlopen(CHEATCODES_JSON_URL).read().decode("utf-8") - contract = Cheatcodes.from_json(json_str) - - ccs = contract.cheatcodes - ccs = list(filter(lambda cc: cc.status not in ["experimental", "internal"], ccs)) - ccs.sort(key=lambda cc: cc.func.id) - - safe = list(filter(lambda cc: cc.safety == "safe", ccs)) - safe.sort(key=CmpCheatcode) - unsafe = list(filter(lambda cc: cc.safety == "unsafe", ccs)) - unsafe.sort(key=CmpCheatcode) - assert len(safe) + len(unsafe) == len(ccs) - - prefix_with_group_headers(safe) - prefix_with_group_headers(unsafe) - - out = "" - - out += "// Automatically @generated by scripts/vm.py. Do not modify manually.\n\n" - - pp = CheatcodesPrinter( - spdx_identifier="MIT OR Apache-2.0", - solidity_requirement=">=0.6.2 <0.9.0", - abicoder_pragma=True, - ) - pp.p_prelude() - pp.prelude = False - out += pp.finish() - - out += "\n\n" - out += VM_SAFE_DOC - vm_safe = Cheatcodes( - # TODO: Custom errors were introduced in 0.8.4 - errors=[], # contract.errors - events=contract.events, - enums=contract.enums, - structs=contract.structs, - cheatcodes=safe, - ) - pp.p_contract(vm_safe, "VmSafe") - out += pp.finish() - - out += "\n\n" - out += VM_DOC - vm_unsafe = Cheatcodes( - errors=[], - events=[], - enums=[], - structs=[], - cheatcodes=unsafe, - ) - pp.p_contract(vm_unsafe, "Vm", "VmSafe") - out += pp.finish() - - # Compatibility with <0.8.0 - def memory_to_calldata(m: re.Match) -> str: - return " calldata " + m.group(1) - - out = re.sub(r" memory (.*returns)", memory_to_calldata, out) - - with open(OUT_PATH, "w") as f: - f.write(out) - - forge_fmt = ["forge", "fmt", OUT_PATH] - res = subprocess.run(forge_fmt) - assert res.returncode == 0, f"command failed: {forge_fmt}" - - print(f"Wrote to {OUT_PATH}") - - -class CmpCheatcode: - cheatcode: "Cheatcode" - - def __init__(self, cheatcode: "Cheatcode"): - self.cheatcode = cheatcode - - def __lt__(self, other: "CmpCheatcode") -> bool: - return cmp_cheatcode(self.cheatcode, other.cheatcode) < 0 - - def __eq__(self, other: "CmpCheatcode") -> bool: - return cmp_cheatcode(self.cheatcode, other.cheatcode) == 0 - - def __gt__(self, other: "CmpCheatcode") -> bool: - return cmp_cheatcode(self.cheatcode, other.cheatcode) > 0 - - -def cmp_cheatcode(a: "Cheatcode", b: "Cheatcode") -> int: - if a.group != b.group: - return -1 if a.group < b.group else 1 - if a.status != b.status: - return -1 if a.status < b.status else 1 - if a.safety != b.safety: - return -1 if a.safety < b.safety else 1 - if a.func.id != b.func.id: - return -1 if a.func.id < b.func.id else 1 - return 0 - - -# HACK: A way to add group header comments without having to modify printer code -def prefix_with_group_headers(cheats: list["Cheatcode"]): - s = set() - for i, cheat in enumerate(cheats): - if cheat.group in s: - continue - - s.add(cheat.group) - - c = copy.deepcopy(cheat) - c.func.description = "" - c.func.declaration = f"// ======== {group(c.group)} ========" - cheats.insert(i, c) - return cheats - - -def group(s: str) -> str: - if s == "evm": - return "EVM" - if s == "json": - return "JSON" - return s[0].upper() + s[1:] - - -class Visibility(PyEnum): - EXTERNAL: str = "external" - PUBLIC: str = "public" - INTERNAL: str = "internal" - PRIVATE: str = "private" - - def __str__(self): - return self.value - - -class Mutability(PyEnum): - PURE: str = "pure" - VIEW: str = "view" - NONE: str = "" - - def __str__(self): - return self.value - - -class Function: - id: str - description: str - declaration: str - visibility: Visibility - mutability: Mutability - signature: str - selector: str - selector_bytes: bytes - - def __init__( - self, - id: str, - description: str, - declaration: str, - visibility: Visibility, - mutability: Mutability, - signature: str, - selector: str, - selector_bytes: bytes, - ): - self.id = id - self.description = description - self.declaration = declaration - self.visibility = visibility - self.mutability = mutability - self.signature = signature - self.selector = selector - self.selector_bytes = selector_bytes - - @staticmethod - def from_dict(d: dict) -> "Function": - return Function( - d["id"], - d["description"], - d["declaration"], - Visibility(d["visibility"]), - Mutability(d["mutability"]), - d["signature"], - d["selector"], - bytes(d["selectorBytes"]), - ) - - -class Cheatcode: - func: Function - group: str - status: str - safety: str - - def __init__(self, func: Function, group: str, status: str, safety: str): - self.func = func - self.group = group - self.status = status - self.safety = safety - - @staticmethod - def from_dict(d: dict) -> "Cheatcode": - return Cheatcode( - Function.from_dict(d["func"]), - str(d["group"]), - str(d["status"]), - str(d["safety"]), - ) - - -class Error: - name: str - description: str - declaration: str - - def __init__(self, name: str, description: str, declaration: str): - self.name = name - self.description = description - self.declaration = declaration - - @staticmethod - def from_dict(d: dict) -> "Error": - return Error(**d) - - -class Event: - name: str - description: str - declaration: str - - def __init__(self, name: str, description: str, declaration: str): - self.name = name - self.description = description - self.declaration = declaration - - @staticmethod - def from_dict(d: dict) -> "Event": - return Event(**d) - - -class EnumVariant: - name: str - description: str - - def __init__(self, name: str, description: str): - self.name = name - self.description = description - - -class Enum: - name: str - description: str - variants: list[EnumVariant] - - def __init__(self, name: str, description: str, variants: list[EnumVariant]): - self.name = name - self.description = description - self.variants = variants - - @staticmethod - def from_dict(d: dict) -> "Enum": - return Enum( - d["name"], - d["description"], - list(map(lambda v: EnumVariant(**v), d["variants"])), - ) - - -class StructField: - name: str - ty: str - description: str - - def __init__(self, name: str, ty: str, description: str): - self.name = name - self.ty = ty - self.description = description - - -class Struct: - name: str - description: str - fields: list[StructField] - - def __init__(self, name: str, description: str, fields: list[StructField]): - self.name = name - self.description = description - self.fields = fields - - @staticmethod - def from_dict(d: dict) -> "Struct": - return Struct( - d["name"], - d["description"], - list(map(lambda f: StructField(**f), d["fields"])), - ) - - -class Cheatcodes: - errors: list[Error] - events: list[Event] - enums: list[Enum] - structs: list[Struct] - cheatcodes: list[Cheatcode] - - def __init__( - self, - errors: list[Error], - events: list[Event], - enums: list[Enum], - structs: list[Struct], - cheatcodes: list[Cheatcode], - ): - self.errors = errors - self.events = events - self.enums = enums - self.structs = structs - self.cheatcodes = cheatcodes - - @staticmethod - def from_dict(d: dict) -> "Cheatcodes": - return Cheatcodes( - errors=[Error.from_dict(e) for e in d["errors"]], - events=[Event.from_dict(e) for e in d["events"]], - enums=[Enum.from_dict(e) for e in d["enums"]], - structs=[Struct.from_dict(e) for e in d["structs"]], - cheatcodes=[Cheatcode.from_dict(e) for e in d["cheatcodes"]], - ) - - @staticmethod - def from_json(s) -> "Cheatcodes": - return Cheatcodes.from_dict(json.loads(s)) - - @staticmethod - def from_json_file(file_path: str) -> "Cheatcodes": - with open(file_path, "r") as f: - return Cheatcodes.from_dict(json.load(f)) - - -class Item(PyEnum): - ERROR: str = "error" - EVENT: str = "event" - ENUM: str = "enum" - STRUCT: str = "struct" - FUNCTION: str = "function" - - -class ItemOrder: - _list: list[Item] - - def __init__(self, list: list[Item]) -> None: - assert len(list) <= len(Item), "list must not contain more items than Item" - assert len(list) == len(set(list)), "list must not contain duplicates" - self._list = list - pass - - def get_list(self) -> list[Item]: - return self._list - - @staticmethod - def default() -> "ItemOrder": - return ItemOrder( - [ - Item.ERROR, - Item.EVENT, - Item.ENUM, - Item.STRUCT, - Item.FUNCTION, - ] - ) - - -class CheatcodesPrinter: - buffer: str - - prelude: bool - spdx_identifier: str - solidity_requirement: str - abicoder_v2: bool - - block_doc_style: bool - - indent_level: int - _indent_str: str - - nl_str: str - - items_order: ItemOrder - - def __init__( - self, - buffer: str = "", - prelude: bool = True, - spdx_identifier: str = "UNLICENSED", - solidity_requirement: str = "", - abicoder_pragma: bool = False, - block_doc_style: bool = False, - indent_level: int = 0, - indent_with: int | str = 4, - nl_str: str = "\n", - items_order: ItemOrder = ItemOrder.default(), - ): - self.prelude = prelude - self.spdx_identifier = spdx_identifier - self.solidity_requirement = solidity_requirement - self.abicoder_v2 = abicoder_pragma - self.block_doc_style = block_doc_style - self.buffer = buffer - self.indent_level = indent_level - self.nl_str = nl_str - - if isinstance(indent_with, int): - assert indent_with >= 0 - self._indent_str = " " * indent_with - elif isinstance(indent_with, str): - self._indent_str = indent_with - else: - assert False, "indent_with must be int or str" - - self.items_order = items_order - - def finish(self) -> str: - ret = self.buffer.rstrip() - self.buffer = "" - return ret - - def p_contract(self, contract: Cheatcodes, name: str, inherits: str = ""): - if self.prelude: - self.p_prelude(contract) - - self._p_str("interface ") - name = name.strip() - if name != "": - self._p_str(name) - self._p_str(" ") - if inherits != "": - self._p_str("is ") - self._p_str(inherits) - self._p_str(" ") - self._p_str("{") - self._p_nl() - self._with_indent(lambda: self._p_items(contract)) - self._p_str("}") - self._p_nl() - - def _p_items(self, contract: Cheatcodes): - for item in self.items_order.get_list(): - if item == Item.ERROR: - self.p_errors(contract.errors) - elif item == Item.EVENT: - self.p_events(contract.events) - elif item == Item.ENUM: - self.p_enums(contract.enums) - elif item == Item.STRUCT: - self.p_structs(contract.structs) - elif item == Item.FUNCTION: - self.p_functions(contract.cheatcodes) - else: - assert False, f"unknown item {item}" - - def p_prelude(self, contract: Cheatcodes | None = None): - self._p_str(f"// SPDX-License-Identifier: {self.spdx_identifier}") - self._p_nl() - - if self.solidity_requirement != "": - req = self.solidity_requirement - elif contract and len(contract.errors) > 0: - req = ">=0.8.4 <0.9.0" - else: - req = ">=0.6.0 <0.9.0" - self._p_str(f"pragma solidity {req};") - self._p_nl() - - if self.abicoder_v2: - self._p_str("pragma experimental ABIEncoderV2;") - self._p_nl() - - self._p_nl() - - def p_errors(self, errors: list[Error]): - for error in errors: - self._p_line(lambda: self.p_error(error)) - - def p_error(self, error: Error): - self._p_comment(error.description, doc=True) - self._p_line(lambda: self._p_str(error.declaration)) - - def p_events(self, events: list[Event]): - for event in events: - self._p_line(lambda: self.p_event(event)) - - def p_event(self, event: Event): - self._p_comment(event.description, doc=True) - self._p_line(lambda: self._p_str(event.declaration)) - - def p_enums(self, enums: list[Enum]): - for enum in enums: - self._p_line(lambda: self.p_enum(enum)) - - def p_enum(self, enum: Enum): - self._p_comment(enum.description, doc=True) - self._p_line(lambda: self._p_str(f"enum {enum.name} {{")) - self._with_indent(lambda: self.p_enum_variants(enum.variants)) - self._p_line(lambda: self._p_str("}")) - - def p_enum_variants(self, variants: list[EnumVariant]): - for i, variant in enumerate(variants): - self._p_indent() - self._p_comment(variant.description) - - self._p_indent() - self._p_str(variant.name) - if i < len(variants) - 1: - self._p_str(",") - self._p_nl() - - def p_structs(self, structs: list[Struct]): - for struct in structs: - self._p_line(lambda: self.p_struct(struct)) - - def p_struct(self, struct: Struct): - self._p_comment(struct.description, doc=True) - self._p_line(lambda: self._p_str(f"struct {struct.name} {{")) - self._with_indent(lambda: self.p_struct_fields(struct.fields)) - self._p_line(lambda: self._p_str("}")) - - def p_struct_fields(self, fields: list[StructField]): - for field in fields: - self._p_line(lambda: self.p_struct_field(field)) - - def p_struct_field(self, field: StructField): - self._p_comment(field.description) - self._p_indented(lambda: self._p_str(f"{field.ty} {field.name};")) - - def p_functions(self, cheatcodes: list[Cheatcode]): - for cheatcode in cheatcodes: - self._p_line(lambda: self.p_function(cheatcode.func)) - - def p_function(self, func: Function): - self._p_comment(func.description, doc=True) - self._p_line(lambda: self._p_str(func.declaration)) - - def _p_comment(self, s: str, doc: bool = False): - s = s.strip() - if s == "": - return - - s = map(lambda line: line.lstrip(), s.split("\n")) - if self.block_doc_style: - self._p_str("/*") - if doc: - self._p_str("*") - self._p_nl() - for line in s: - self._p_indent() - self._p_str(" ") - if doc: - self._p_str("* ") - self._p_str(line) - self._p_nl() - self._p_indent() - self._p_str(" */") - self._p_nl() - else: - first_line = True - for line in s: - if not first_line: - self._p_indent() - first_line = False - - if doc: - self._p_str("/// ") - else: - self._p_str("// ") - self._p_str(line) - self._p_nl() - - def _with_indent(self, f: VoidFn): - self._inc_indent() - f() - self._dec_indent() - - def _p_line(self, f: VoidFn): - self._p_indent() - f() - self._p_nl() - - def _p_indented(self, f: VoidFn): - self._p_indent() - f() - - def _p_indent(self): - for _ in range(self.indent_level): - self._p_str(self._indent_str) - - def _p_nl(self): - self._p_str(self.nl_str) - - def _p_str(self, txt: str): - self.buffer += txt - - def _inc_indent(self): - self.indent_level += 1 - - def _dec_indent(self): - self.indent_level -= 1 - - -if __name__ == "__main__": - main() diff --git a/lib_forge_std/test/StdAssertions.t.sol b/lib_forge_std/test/StdAssertions.t.sol deleted file mode 100644 index ea79468..0000000 --- a/lib_forge_std/test/StdAssertions.t.sol +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/StdAssertions.sol"; -import {Vm} from "../src/Vm.sol"; - -interface VmInternal is Vm { - function _expectCheatcodeRevert(bytes memory message) external; -} - -contract StdAssertionsTest is StdAssertions { - string constant errorMessage = "User provided message"; - uint256 constant maxDecimals = 77; - - bool constant SHOULD_REVERT = true; - bool constant SHOULD_RETURN = false; - - bool constant STRICT_REVERT_DATA = true; - bool constant NON_STRICT_REVERT_DATA = false; - - VmInternal constant vm = VmInternal(address(uint160(uint256(keccak256("hevm cheat code"))))); - - function testFuzz_AssertEqCall_Return_Pass( - bytes memory callDataA, - bytes memory callDataB, - bytes memory returnData, - bool strictRevertData - ) external { - address targetA = address(new TestMockCall(returnData, SHOULD_RETURN)); - address targetB = address(new TestMockCall(returnData, SHOULD_RETURN)); - - assertEqCall(targetA, callDataA, targetB, callDataB, strictRevertData); - } - - function testFuzz_RevertWhenCalled_AssertEqCall_Return_Fail( - bytes memory callDataA, - bytes memory callDataB, - bytes memory returnDataA, - bytes memory returnDataB, - bool strictRevertData - ) external { - vm.assume(keccak256(returnDataA) != keccak256(returnDataB)); - - address targetA = address(new TestMockCall(returnDataA, SHOULD_RETURN)); - address targetB = address(new TestMockCall(returnDataB, SHOULD_RETURN)); - - vm._expectCheatcodeRevert( - bytes( - string.concat( - "Call return data does not match: ", vm.toString(returnDataA), " != ", vm.toString(returnDataB) - ) - ) - ); - assertEqCall(targetA, callDataA, targetB, callDataB, strictRevertData); - } - - function testFuzz_AssertEqCall_Revert_Pass( - bytes memory callDataA, - bytes memory callDataB, - bytes memory revertDataA, - bytes memory revertDataB - ) external { - address targetA = address(new TestMockCall(revertDataA, SHOULD_REVERT)); - address targetB = address(new TestMockCall(revertDataB, SHOULD_REVERT)); - - assertEqCall(targetA, callDataA, targetB, callDataB, NON_STRICT_REVERT_DATA); - } - - function testFuzz_RevertWhenCalled_AssertEqCall_Revert_Fail( - bytes memory callDataA, - bytes memory callDataB, - bytes memory revertDataA, - bytes memory revertDataB - ) external { - vm.assume(keccak256(revertDataA) != keccak256(revertDataB)); - - address targetA = address(new TestMockCall(revertDataA, SHOULD_REVERT)); - address targetB = address(new TestMockCall(revertDataB, SHOULD_REVERT)); - - vm._expectCheatcodeRevert( - bytes( - string.concat( - "Call revert data does not match: ", vm.toString(revertDataA), " != ", vm.toString(revertDataB) - ) - ) - ); - assertEqCall(targetA, callDataA, targetB, callDataB, STRICT_REVERT_DATA); - } - - function testFuzz_RevertWhenCalled_AssertEqCall_Fail( - bytes memory callDataA, - bytes memory callDataB, - bytes memory returnDataA, - bytes memory returnDataB, - bool strictRevertData - ) external { - address targetA = address(new TestMockCall(returnDataA, SHOULD_RETURN)); - address targetB = address(new TestMockCall(returnDataB, SHOULD_REVERT)); - - vm.expectRevert(bytes("assertion failed")); - this.assertEqCallExternal(targetA, callDataA, targetB, callDataB, strictRevertData); - - vm.expectRevert(bytes("assertion failed")); - this.assertEqCallExternal(targetB, callDataB, targetA, callDataA, strictRevertData); - } - - // Helper function to test outcome of assertEqCall via `expect` cheatcodes - function assertEqCallExternal( - address targetA, - bytes memory callDataA, - address targetB, - bytes memory callDataB, - bool strictRevertData - ) public { - assertEqCall(targetA, callDataA, targetB, callDataB, strictRevertData); - } - - function testFailFail() public { - fail(); - } -} - -contract TestMockCall { - bytes returnData; - bool shouldRevert; - - constructor(bytes memory returnData_, bool shouldRevert_) { - returnData = returnData_; - shouldRevert = shouldRevert_; - } - - fallback() external payable { - bytes memory returnData_ = returnData; - - if (shouldRevert) { - assembly { - revert(add(returnData_, 0x20), mload(returnData_)) - } - } else { - assembly { - return(add(returnData_, 0x20), mload(returnData_)) - } - } - } -} diff --git a/lib_forge_std/test/StdChains.t.sol b/lib_forge_std/test/StdChains.t.sol deleted file mode 100644 index f1ee354..0000000 --- a/lib_forge_std/test/StdChains.t.sol +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/Test.sol"; - -contract StdChainsMock is Test { - function exposed_getChain(string memory chainAlias) public returns (Chain memory) { - return getChain(chainAlias); - } - - function exposed_getChain(uint256 chainId) public returns (Chain memory) { - return getChain(chainId); - } - - function exposed_setChain(string memory chainAlias, ChainData memory chainData) public { - setChain(chainAlias, chainData); - } - - function exposed_setFallbackToDefaultRpcUrls(bool useDefault) public { - setFallbackToDefaultRpcUrls(useDefault); - } -} - -contract StdChainsTest is Test { - function test_ChainRpcInitialization() public { - // RPCs specified in `foundry.toml` should be updated. - assertEq(getChain(1).rpcUrl, "https://eth-mainnet.alchemyapi.io/v2/WV407BEiBmjNJfKo9Uo_55u0z0ITyCOX"); - assertEq(getChain("optimism_sepolia").rpcUrl, "https://sepolia.optimism.io/"); - assertEq(getChain("arbitrum_one_sepolia").rpcUrl, "https://sepolia-rollup.arbitrum.io/rpc/"); - - // Environment variables should be the next fallback - assertEq(getChain("arbitrum_nova").rpcUrl, "https://nova.arbitrum.io/rpc"); - vm.setEnv("ARBITRUM_NOVA_RPC_URL", "myoverride"); - assertEq(getChain("arbitrum_nova").rpcUrl, "myoverride"); - vm.setEnv("ARBITRUM_NOVA_RPC_URL", "https://nova.arbitrum.io/rpc"); - - // Cannot override RPCs defined in `foundry.toml` - vm.setEnv("MAINNET_RPC_URL", "myoverride2"); - assertEq(getChain("mainnet").rpcUrl, "https://eth-mainnet.alchemyapi.io/v2/WV407BEiBmjNJfKo9Uo_55u0z0ITyCOX"); - - // Other RPCs should remain unchanged. - assertEq(getChain(31337).rpcUrl, "http://127.0.0.1:8545"); - assertEq(getChain("sepolia").rpcUrl, "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001"); - } - - // Named with a leading underscore to clarify this is not intended to be run as a normal test, - // and is intended to be used in the below `test_Rpcs` test. - function _testRpc(string memory rpcAlias) internal { - string memory rpcUrl = getChain(rpcAlias).rpcUrl; - vm.createSelectFork(rpcUrl); - } - - // Ensure we can connect to the default RPC URL for each chain. - // Currently commented out since this is slow and public RPCs are flaky, often resulting in failing CI. - // function test_Rpcs() public { - // _testRpc("mainnet"); - // _testRpc("sepolia"); - // _testRpc("holesky"); - // _testRpc("optimism"); - // _testRpc("optimism_sepolia"); - // _testRpc("arbitrum_one"); - // _testRpc("arbitrum_one_sepolia"); - // _testRpc("arbitrum_nova"); - // _testRpc("polygon"); - // _testRpc("polygon_amoy"); - // _testRpc("avalanche"); - // _testRpc("avalanche_fuji"); - // _testRpc("bnb_smart_chain"); - // _testRpc("bnb_smart_chain_testnet"); - // _testRpc("gnosis_chain"); - // _testRpc("moonbeam"); - // _testRpc("moonriver"); - // _testRpc("moonbase"); - // _testRpc("base_sepolia"); - // _testRpc("base"); - // _testRpc("fraxtal"); - // _testRpc("fraxtal_testnet"); - // } - - function test_ChainNoDefault() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains getChain(string): Chain with alias \"does_not_exist\" not found."); - stdChainsMock.exposed_getChain("does_not_exist"); - } - - function test_SetChainFirstFails() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains setChain(string,ChainData): Chain ID 31337 already used by \"anvil\"."); - stdChainsMock.exposed_setChain("anvil2", ChainData("Anvil", 31337, "URL")); - } - - function test_ChainBubbleUp() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - stdChainsMock.exposed_setChain("needs_undefined_env_var", ChainData("", 123456789, "")); - vm.expectRevert( - "Failed to resolve env var `UNDEFINED_RPC_URL_PLACEHOLDER` in `${UNDEFINED_RPC_URL_PLACEHOLDER}`: environment variable not found" - ); - stdChainsMock.exposed_getChain("needs_undefined_env_var"); - } - - function test_CannotSetChain_ChainIdExists() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - stdChainsMock.exposed_setChain("custom_chain", ChainData("Custom Chain", 123456789, "https://custom.chain/")); - - vm.expectRevert('StdChains setChain(string,ChainData): Chain ID 123456789 already used by "custom_chain".'); - - stdChainsMock.exposed_setChain("another_custom_chain", ChainData("", 123456789, "")); - } - - function test_SetChain() public { - setChain("custom_chain", ChainData("Custom Chain", 123456789, "https://custom.chain/")); - Chain memory customChain = getChain("custom_chain"); - assertEq(customChain.name, "Custom Chain"); - assertEq(customChain.chainId, 123456789); - assertEq(customChain.chainAlias, "custom_chain"); - assertEq(customChain.rpcUrl, "https://custom.chain/"); - Chain memory chainById = getChain(123456789); - assertEq(chainById.name, customChain.name); - assertEq(chainById.chainId, customChain.chainId); - assertEq(chainById.chainAlias, customChain.chainAlias); - assertEq(chainById.rpcUrl, customChain.rpcUrl); - customChain.name = "Another Custom Chain"; - customChain.chainId = 987654321; - setChain("another_custom_chain", customChain); - Chain memory anotherCustomChain = getChain("another_custom_chain"); - assertEq(anotherCustomChain.name, "Another Custom Chain"); - assertEq(anotherCustomChain.chainId, 987654321); - assertEq(anotherCustomChain.chainAlias, "another_custom_chain"); - assertEq(anotherCustomChain.rpcUrl, "https://custom.chain/"); - // Verify the first chain data was not overwritten - chainById = getChain(123456789); - assertEq(chainById.name, "Custom Chain"); - assertEq(chainById.chainId, 123456789); - } - - function test_SetNoEmptyAlias() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains setChain(string,ChainData): Chain alias cannot be the empty string."); - stdChainsMock.exposed_setChain("", ChainData("", 123456789, "")); - } - - function test_SetNoChainId0() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains setChain(string,ChainData): Chain ID cannot be 0."); - stdChainsMock.exposed_setChain("alias", ChainData("", 0, "")); - } - - function test_GetNoChainId0() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains getChain(uint256): Chain ID cannot be 0."); - stdChainsMock.exposed_getChain(0); - } - - function test_GetNoEmptyAlias() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains getChain(string): Chain alias cannot be the empty string."); - stdChainsMock.exposed_getChain(""); - } - - function test_ChainIdNotFound() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains getChain(string): Chain with alias \"no_such_alias\" not found."); - stdChainsMock.exposed_getChain("no_such_alias"); - } - - function test_ChainAliasNotFound() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - vm.expectRevert("StdChains getChain(uint256): Chain with ID 321 not found."); - - stdChainsMock.exposed_getChain(321); - } - - function test_SetChain_ExistingOne() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - setChain("custom_chain", ChainData("Custom Chain", 123456789, "https://custom.chain/")); - assertEq(getChain(123456789).chainId, 123456789); - - setChain("custom_chain", ChainData("Modified Chain", 999999999, "https://modified.chain/")); - vm.expectRevert("StdChains getChain(uint256): Chain with ID 123456789 not found."); - stdChainsMock.exposed_getChain(123456789); - - Chain memory modifiedChain = getChain(999999999); - assertEq(modifiedChain.name, "Modified Chain"); - assertEq(modifiedChain.chainId, 999999999); - assertEq(modifiedChain.rpcUrl, "https://modified.chain/"); - } - - function test_DontUseDefaultRpcUrl() public { - // We deploy a mock to properly test the revert. - StdChainsMock stdChainsMock = new StdChainsMock(); - - // Should error if default RPCs flag is set to false. - stdChainsMock.exposed_setFallbackToDefaultRpcUrls(false); - vm.expectRevert(); - stdChainsMock.exposed_getChain(31337); - vm.expectRevert(); - stdChainsMock.exposed_getChain("sepolia"); - } -} diff --git a/lib_forge_std/test/StdCheats.t.sol b/lib_forge_std/test/StdCheats.t.sol deleted file mode 100644 index 7bac428..0000000 --- a/lib_forge_std/test/StdCheats.t.sol +++ /dev/null @@ -1,618 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/StdCheats.sol"; -import "../src/Test.sol"; -import "../src/StdJson.sol"; -import "../src/StdToml.sol"; -import "../src/interfaces/IERC20.sol"; - -contract StdCheatsTest is Test { - Bar test; - - using stdJson for string; - - function setUp() public { - test = new Bar(); - } - - function test_Skip() public { - vm.warp(100); - skip(25); - assertEq(block.timestamp, 125); - } - - function test_Rewind() public { - vm.warp(100); - rewind(25); - assertEq(block.timestamp, 75); - } - - function test_Hoax() public { - hoax(address(1337)); - test.bar{value: 100}(address(1337)); - } - - function test_HoaxOrigin() public { - hoax(address(1337), address(1337)); - test.origin{value: 100}(address(1337)); - } - - function test_HoaxDifferentAddresses() public { - hoax(address(1337), address(7331)); - test.origin{value: 100}(address(1337), address(7331)); - } - - function test_StartHoax() public { - startHoax(address(1337)); - test.bar{value: 100}(address(1337)); - test.bar{value: 100}(address(1337)); - vm.stopPrank(); - test.bar(address(this)); - } - - function test_StartHoaxOrigin() public { - startHoax(address(1337), address(1337)); - test.origin{value: 100}(address(1337)); - test.origin{value: 100}(address(1337)); - vm.stopPrank(); - test.bar(address(this)); - } - - function test_ChangePrankMsgSender() public { - vm.startPrank(address(1337)); - test.bar(address(1337)); - changePrank(address(0xdead)); - test.bar(address(0xdead)); - changePrank(address(1337)); - test.bar(address(1337)); - vm.stopPrank(); - } - - function test_ChangePrankMsgSenderAndTxOrigin() public { - vm.startPrank(address(1337), address(1338)); - test.origin(address(1337), address(1338)); - changePrank(address(0xdead), address(0xbeef)); - test.origin(address(0xdead), address(0xbeef)); - changePrank(address(1337), address(1338)); - test.origin(address(1337), address(1338)); - vm.stopPrank(); - } - - function test_MakeAccountEquivalence() public { - Account memory account = makeAccount("1337"); - (address addr, uint256 key) = makeAddrAndKey("1337"); - assertEq(account.addr, addr); - assertEq(account.key, key); - } - - function test_MakeAddrEquivalence() public { - (address addr,) = makeAddrAndKey("1337"); - assertEq(makeAddr("1337"), addr); - } - - function test_MakeAddrSigning() public { - (address addr, uint256 key) = makeAddrAndKey("1337"); - bytes32 hash = keccak256("some_message"); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(key, hash); - assertEq(ecrecover(hash, v, r, s), addr); - } - - function test_Deal() public { - deal(address(this), 1 ether); - assertEq(address(this).balance, 1 ether); - } - - function test_DealToken() public { - Bar barToken = new Bar(); - address bar = address(barToken); - deal(bar, address(this), 10000e18); - assertEq(barToken.balanceOf(address(this)), 10000e18); - } - - function test_DealTokenAdjustTotalSupply() public { - Bar barToken = new Bar(); - address bar = address(barToken); - deal(bar, address(this), 10000e18, true); - assertEq(barToken.balanceOf(address(this)), 10000e18); - assertEq(barToken.totalSupply(), 20000e18); - deal(bar, address(this), 0, true); - assertEq(barToken.balanceOf(address(this)), 0); - assertEq(barToken.totalSupply(), 10000e18); - } - - function test_DealERC1155Token() public { - BarERC1155 barToken = new BarERC1155(); - address bar = address(barToken); - dealERC1155(bar, address(this), 0, 10000e18, false); - assertEq(barToken.balanceOf(address(this), 0), 10000e18); - } - - function test_DealERC1155TokenAdjustTotalSupply() public { - BarERC1155 barToken = new BarERC1155(); - address bar = address(barToken); - dealERC1155(bar, address(this), 0, 10000e18, true); - assertEq(barToken.balanceOf(address(this), 0), 10000e18); - assertEq(barToken.totalSupply(0), 20000e18); - dealERC1155(bar, address(this), 0, 0, true); - assertEq(barToken.balanceOf(address(this), 0), 0); - assertEq(barToken.totalSupply(0), 10000e18); - } - - function test_DealERC721Token() public { - BarERC721 barToken = new BarERC721(); - address bar = address(barToken); - dealERC721(bar, address(2), 1); - assertEq(barToken.balanceOf(address(2)), 1); - assertEq(barToken.balanceOf(address(1)), 0); - dealERC721(bar, address(1), 2); - assertEq(barToken.balanceOf(address(1)), 1); - assertEq(barToken.balanceOf(bar), 1); - } - - function test_DeployCode() public { - address deployed = deployCode("StdCheats.t.sol:Bar", bytes("")); - assertEq(string(getCode(deployed)), string(getCode(address(test)))); - } - - function test_DestroyAccount() public { - // deploy something to destroy it - BarERC721 barToken = new BarERC721(); - address bar = address(barToken); - vm.setNonce(bar, 10); - deal(bar, 100); - - uint256 prevThisBalance = address(this).balance; - uint256 size; - assembly { - size := extcodesize(bar) - } - - assertGt(size, 0); - assertEq(bar.balance, 100); - assertEq(vm.getNonce(bar), 10); - - destroyAccount(bar, address(this)); - assembly { - size := extcodesize(bar) - } - assertEq(address(this).balance, prevThisBalance + 100); - assertEq(vm.getNonce(bar), 0); - assertEq(size, 0); - assertEq(bar.balance, 0); - } - - function test_DeployCodeNoArgs() public { - address deployed = deployCode("StdCheats.t.sol:Bar"); - assertEq(string(getCode(deployed)), string(getCode(address(test)))); - } - - function test_DeployCodeVal() public { - address deployed = deployCode("StdCheats.t.sol:Bar", bytes(""), 1 ether); - assertEq(string(getCode(deployed)), string(getCode(address(test)))); - assertEq(deployed.balance, 1 ether); - } - - function test_DeployCodeValNoArgs() public { - address deployed = deployCode("StdCheats.t.sol:Bar", 1 ether); - assertEq(string(getCode(deployed)), string(getCode(address(test)))); - assertEq(deployed.balance, 1 ether); - } - - // We need this so we can call "this.deployCode" rather than "deployCode" directly - function deployCodeHelper(string memory what) external { - deployCode(what); - } - - function test_DeployCodeFail() public { - vm.expectRevert(bytes("StdCheats deployCode(string): Deployment failed.")); - this.deployCodeHelper("StdCheats.t.sol:RevertingContract"); - } - - function getCode(address who) internal view returns (bytes memory o_code) { - /// @solidity memory-safe-assembly - assembly { - // retrieve the size of the code, this needs assembly - let size := extcodesize(who) - // allocate output byte array - this could also be done without assembly - // by using o_code = new bytes(size) - o_code := mload(0x40) - // new "memory end" including padding - mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - // store length in memory - mstore(o_code, size) - // actually retrieve the code, this needs assembly - extcodecopy(who, add(o_code, 0x20), 0, size) - } - } - - function test_DeriveRememberKey() public { - string memory mnemonic = "test test test test test test test test test test test junk"; - - (address deployer, uint256 privateKey) = deriveRememberKey(mnemonic, 0); - assertEq(deployer, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - assertEq(privateKey, 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); - } - - function test_BytesToUint() public pure { - assertEq(3, bytesToUint_test(hex"03")); - assertEq(2, bytesToUint_test(hex"02")); - assertEq(255, bytesToUint_test(hex"ff")); - assertEq(29625, bytesToUint_test(hex"73b9")); - } - - function test_ParseJsonTxDetail() public view { - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); - string memory json = vm.readFile(path); - bytes memory transactionDetails = json.parseRaw(".transactions[0].tx"); - RawTx1559Detail memory rawTxDetail = abi.decode(transactionDetails, (RawTx1559Detail)); - Tx1559Detail memory txDetail = rawToConvertedEIP1559Detail(rawTxDetail); - assertEq(txDetail.from, 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - assertEq(txDetail.to, 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512); - assertEq( - txDetail.data, - hex"23e99187000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000013370000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004" - ); - assertEq(txDetail.nonce, 3); - assertEq(txDetail.txType, 2); - assertEq(txDetail.gas, 29625); - assertEq(txDetail.value, 0); - } - - function test_ReadEIP1559Transaction() public view { - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); - uint256 index = 0; - Tx1559 memory transaction = readTx1559(path, index); - transaction; - } - - function test_ReadEIP1559Transactions() public view { - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); - Tx1559[] memory transactions = readTx1559s(path); - transactions; - } - - function test_ReadReceipt() public view { - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); - uint256 index = 5; - Receipt memory receipt = readReceipt(path, index); - assertEq( - receipt.logsBloom, - hex"00000000000800000000000000000010000000000000000000000000000180000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100" - ); - } - - function test_ReadReceipts() public view { - string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/test/fixtures/broadcast.log.json"); - Receipt[] memory receipts = readReceipts(path); - receipts; - } - - function test_GasMeteringModifier() public { - uint256 gas_start_normal = gasleft(); - addInLoop(); - uint256 gas_used_normal = gas_start_normal - gasleft(); - - uint256 gas_start_single = gasleft(); - addInLoopNoGas(); - uint256 gas_used_single = gas_start_single - gasleft(); - - uint256 gas_start_double = gasleft(); - addInLoopNoGasNoGas(); - uint256 gas_used_double = gas_start_double - gasleft(); - - assertTrue(gas_used_double + gas_used_single < gas_used_normal); - } - - function addInLoop() internal pure returns (uint256) { - uint256 b; - for (uint256 i; i < 10000; i++) { - b += i; - } - return b; - } - - function addInLoopNoGas() internal noGasMetering returns (uint256) { - return addInLoop(); - } - - function addInLoopNoGasNoGas() internal noGasMetering returns (uint256) { - return addInLoopNoGas(); - } - - function bytesToUint_test(bytes memory b) private pure returns (uint256) { - uint256 number; - for (uint256 i = 0; i < b.length; i++) { - number = number + uint256(uint8(b[i])) * (2 ** (8 * (b.length - (i + 1)))); - } - return number; - } - - function testFuzz_AssumeAddressIsNot(address addr) external { - // skip over Payable and NonPayable enums - for (uint8 i = 2; i < uint8(type(AddressType).max); i++) { - assumeAddressIsNot(addr, AddressType(i)); - } - assertTrue(addr != address(0)); - assertTrue(addr < address(1) || addr > address(9)); - assertTrue(addr != address(vm) || addr != 0x000000000000000000636F6e736F6c652e6c6f67); - } - - function test_AssumePayable() external { - // We deploy a mock version so we can properly test the revert. - StdCheatsMock stdCheatsMock = new StdCheatsMock(); - - // all should revert since these addresses are not payable - - // VM address - vm.expectRevert(); - stdCheatsMock.exposed_assumePayable(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - // Console address - vm.expectRevert(); - stdCheatsMock.exposed_assumePayable(0x000000000000000000636F6e736F6c652e6c6f67); - - // Create2Deployer - vm.expectRevert(); - stdCheatsMock.exposed_assumePayable(0x4e59b44847b379578588920cA78FbF26c0B4956C); - - // all should pass since these addresses are payable - - // vitalik.eth - stdCheatsMock.exposed_assumePayable(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045); - - // mock payable contract - MockContractPayable cp = new MockContractPayable(); - stdCheatsMock.exposed_assumePayable(address(cp)); - } - - function test_AssumeNotPayable() external { - // We deploy a mock version so we can properly test the revert. - StdCheatsMock stdCheatsMock = new StdCheatsMock(); - - // all should pass since these addresses are not payable - - // VM address - stdCheatsMock.exposed_assumeNotPayable(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - // Console address - stdCheatsMock.exposed_assumeNotPayable(0x000000000000000000636F6e736F6c652e6c6f67); - - // Create2Deployer - stdCheatsMock.exposed_assumeNotPayable(0x4e59b44847b379578588920cA78FbF26c0B4956C); - - // all should revert since these addresses are payable - - // vitalik.eth - vm.expectRevert(); - stdCheatsMock.exposed_assumeNotPayable(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045); - - // mock payable contract - MockContractPayable cp = new MockContractPayable(); - vm.expectRevert(); - stdCheatsMock.exposed_assumeNotPayable(address(cp)); - } - - function testFuzz_AssumeNotPrecompile(address addr) external { - assumeNotPrecompile(addr, getChain("optimism_sepolia").chainId); - assertTrue( - addr < address(1) || (addr > address(9) && addr < address(0x4200000000000000000000000000000000000000)) - || addr > address(0x4200000000000000000000000000000000000800) - ); - } - - function testFuzz_AssumeNotForgeAddress(address addr) external pure { - assumeNotForgeAddress(addr); - assertTrue( - addr != address(vm) && addr != 0x000000000000000000636F6e736F6c652e6c6f67 - && addr != 0x4e59b44847b379578588920cA78FbF26c0B4956C - ); - } - - function test_CannotDeployCodeTo() external { - vm.expectRevert("StdCheats deployCodeTo(string,bytes,uint256,address): Failed to create runtime bytecode."); - this._revertDeployCodeTo(); - } - - function _revertDeployCodeTo() external { - deployCodeTo("StdCheats.t.sol:RevertingContract", address(0)); - } - - function test_DeployCodeTo() external { - address arbitraryAddress = makeAddr("arbitraryAddress"); - - deployCodeTo( - "StdCheats.t.sol:MockContractWithConstructorArgs", - abi.encode(uint256(6), true, bytes20(arbitraryAddress)), - 1 ether, - arbitraryAddress - ); - - MockContractWithConstructorArgs ct = MockContractWithConstructorArgs(arbitraryAddress); - - assertEq(arbitraryAddress.balance, 1 ether); - assertEq(ct.x(), 6); - assertTrue(ct.y()); - assertEq(ct.z(), bytes20(arbitraryAddress)); - } -} - -contract StdCheatsMock is StdCheats { - function exposed_assumePayable(address addr) external { - assumePayable(addr); - } - - function exposed_assumeNotPayable(address addr) external { - assumeNotPayable(addr); - } - - // We deploy a mock version so we can properly test expected reverts. - function exposed_assumeNotBlacklisted(address token, address addr) external view { - return assumeNotBlacklisted(token, addr); - } -} - -contract StdCheatsForkTest is Test { - address internal constant SHIB = 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE; - address internal constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - address internal constant USDC_BLACKLISTED_USER = 0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD; - address internal constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7; - address internal constant USDT_BLACKLISTED_USER = 0x8f8a8F4B54a2aAC7799d7bc81368aC27b852822A; - - function setUp() public { - // All tests of the `assumeNotBlacklisted` method are fork tests using live contracts. - vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 16_428_900}); - } - - function test_CannotAssumeNoBlacklisted_EOA() external { - // We deploy a mock version so we can properly test the revert. - StdCheatsMock stdCheatsMock = new StdCheatsMock(); - address eoa = vm.addr({privateKey: 1}); - vm.expectRevert("StdCheats assumeNotBlacklisted(address,address): Token address is not a contract."); - stdCheatsMock.exposed_assumeNotBlacklisted(eoa, address(0)); - } - - function testFuzz_AssumeNotBlacklisted_TokenWithoutBlacklist(address addr) external view { - assumeNotBlacklisted(SHIB, addr); - assertTrue(true); - } - - function test_AssumeNoBlacklisted_USDC() external { - // We deploy a mock version so we can properly test the revert. - StdCheatsMock stdCheatsMock = new StdCheatsMock(); - vm.expectRevert(); - stdCheatsMock.exposed_assumeNotBlacklisted(USDC, USDC_BLACKLISTED_USER); - } - - function testFuzz_AssumeNotBlacklisted_USDC(address addr) external view { - assumeNotBlacklisted(USDC, addr); - assertFalse(USDCLike(USDC).isBlacklisted(addr)); - } - - function test_AssumeNoBlacklisted_USDT() external { - // We deploy a mock version so we can properly test the revert. - StdCheatsMock stdCheatsMock = new StdCheatsMock(); - vm.expectRevert(); - stdCheatsMock.exposed_assumeNotBlacklisted(USDT, USDT_BLACKLISTED_USER); - } - - function testFuzz_AssumeNotBlacklisted_USDT(address addr) external view { - assumeNotBlacklisted(USDT, addr); - assertFalse(USDTLike(USDT).isBlackListed(addr)); - } - - function test_dealUSDC() external { - // roll fork to the point when USDC contract updated to store balance in packed slots - vm.rollFork(19279215); - - uint256 balance = 100e6; - deal(USDC, address(this), balance); - assertEq(IERC20(USDC).balanceOf(address(this)), balance); - } -} - -contract Bar { - constructor() payable { - /// `DEAL` STDCHEAT - totalSupply = 10000e18; - balanceOf[address(this)] = totalSupply; - } - - /// `HOAX` and `CHANGEPRANK` STDCHEATS - function bar(address expectedSender) public payable { - require(msg.sender == expectedSender, "!prank"); - } - - function origin(address expectedSender) public payable { - require(msg.sender == expectedSender, "!prank"); - require(tx.origin == expectedSender, "!prank"); - } - - function origin(address expectedSender, address expectedOrigin) public payable { - require(msg.sender == expectedSender, "!prank"); - require(tx.origin == expectedOrigin, "!prank"); - } - - /// `DEAL` STDCHEAT - mapping(address => uint256) public balanceOf; - uint256 public totalSupply; -} - -contract BarERC1155 { - constructor() payable { - /// `DEALERC1155` STDCHEAT - _totalSupply[0] = 10000e18; - _balances[0][address(this)] = _totalSupply[0]; - } - - function balanceOf(address account, uint256 id) public view virtual returns (uint256) { - return _balances[id][account]; - } - - function totalSupply(uint256 id) public view virtual returns (uint256) { - return _totalSupply[id]; - } - - /// `DEALERC1155` STDCHEAT - mapping(uint256 => mapping(address => uint256)) private _balances; - mapping(uint256 => uint256) private _totalSupply; -} - -contract BarERC721 { - constructor() payable { - /// `DEALERC721` STDCHEAT - _owners[1] = address(1); - _balances[address(1)] = 1; - _owners[2] = address(this); - _owners[3] = address(this); - _balances[address(this)] = 2; - } - - function balanceOf(address owner) public view virtual returns (uint256) { - return _balances[owner]; - } - - function ownerOf(uint256 tokenId) public view virtual returns (address) { - address owner = _owners[tokenId]; - return owner; - } - - mapping(uint256 => address) private _owners; - mapping(address => uint256) private _balances; -} - -interface USDCLike { - function isBlacklisted(address) external view returns (bool); -} - -interface USDTLike { - function isBlackListed(address) external view returns (bool); -} - -contract RevertingContract { - constructor() { - revert(); - } -} - -contract MockContractWithConstructorArgs { - uint256 public immutable x; - bool public y; - bytes20 public z; - - constructor(uint256 _x, bool _y, bytes20 _z) payable { - x = _x; - y = _y; - z = _z; - } -} - -contract MockContractPayable { - receive() external payable {} -} diff --git a/lib_forge_std/test/StdError.t.sol b/lib_forge_std/test/StdError.t.sol deleted file mode 100644 index a306eaa..0000000 --- a/lib_forge_std/test/StdError.t.sol +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "../src/StdError.sol"; -import "../src/Test.sol"; - -contract StdErrorsTest is Test { - ErrorsTest test; - - function setUp() public { - test = new ErrorsTest(); - } - - function test_ExpectAssertion() public { - vm.expectRevert(stdError.assertionError); - test.assertionError(); - } - - function test_ExpectArithmetic() public { - vm.expectRevert(stdError.arithmeticError); - test.arithmeticError(10); - } - - function test_ExpectDiv() public { - vm.expectRevert(stdError.divisionError); - test.divError(0); - } - - function test_ExpectMod() public { - vm.expectRevert(stdError.divisionError); - test.modError(0); - } - - function test_ExpectEnum() public { - vm.expectRevert(stdError.enumConversionError); - test.enumConversion(1); - } - - function test_ExpectEncodeStg() public { - vm.expectRevert(stdError.encodeStorageError); - test.encodeStgError(); - } - - function test_ExpectPop() public { - vm.expectRevert(stdError.popError); - test.pop(); - } - - function test_ExpectOOB() public { - vm.expectRevert(stdError.indexOOBError); - test.indexOOBError(1); - } - - function test_ExpectMem() public { - vm.expectRevert(stdError.memOverflowError); - test.mem(); - } - - function test_ExpectIntern() public { - vm.expectRevert(stdError.zeroVarError); - test.intern(); - } -} - -contract ErrorsTest { - enum T { - T1 - } - - uint256[] public someArr; - bytes someBytes; - - function assertionError() public pure { - assert(false); - } - - function arithmeticError(uint256 a) public pure { - a -= 100; - } - - function divError(uint256 a) public pure { - 100 / a; - } - - function modError(uint256 a) public pure { - 100 % a; - } - - function enumConversion(uint256 a) public pure { - T(a); - } - - function encodeStgError() public { - /// @solidity memory-safe-assembly - assembly { - sstore(someBytes.slot, 1) - } - keccak256(someBytes); - } - - function pop() public { - someArr.pop(); - } - - function indexOOBError(uint256 a) public pure { - uint256[] memory t = new uint256[](0); - t[a]; - } - - function mem() public pure { - uint256 l = 2 ** 256 / 32; - new uint256[](l); - } - - function intern() public returns (uint256) { - function(uint256) internal returns (uint256) x; - x(2); - return 7; - } -} diff --git a/lib_forge_std/test/StdJson.t.sol b/lib_forge_std/test/StdJson.t.sol deleted file mode 100644 index e32b92e..0000000 --- a/lib_forge_std/test/StdJson.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/Test.sol"; - -contract StdJsonTest is Test { - using stdJson for string; - - string root; - string path; - - function setUp() public { - root = vm.projectRoot(); - path = string.concat(root, "/test/fixtures/test.json"); - } - - struct SimpleJson { - uint256 a; - string b; - } - - struct NestedJson { - uint256 a; - string b; - SimpleJson c; - } - - function test_readJson() public view { - string memory json = vm.readFile(path); - assertEq(json.readUint(".a"), 123); - } - - function test_writeJson() public { - string memory json = "json"; - json.serialize("a", uint256(123)); - string memory semiFinal = json.serialize("b", string("test")); - string memory finalJson = json.serialize("c", semiFinal); - finalJson.write(path); - - string memory json_ = vm.readFile(path); - bytes memory data = json_.parseRaw("$"); - NestedJson memory decodedData = abi.decode(data, (NestedJson)); - - assertEq(decodedData.a, 123); - assertEq(decodedData.b, "test"); - assertEq(decodedData.c.a, 123); - assertEq(decodedData.c.b, "test"); - } -} diff --git a/lib_forge_std/test/StdMath.t.sol b/lib_forge_std/test/StdMath.t.sol deleted file mode 100644 index ed0f9ba..0000000 --- a/lib_forge_std/test/StdMath.t.sol +++ /dev/null @@ -1,212 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "../src/StdMath.sol"; -import "../src/Test.sol"; - -contract StdMathMock is Test { - function exposed_percentDelta(uint256 a, uint256 b) public pure returns (uint256) { - return stdMath.percentDelta(a, b); - } - - function exposed_percentDelta(int256 a, int256 b) public pure returns (uint256) { - return stdMath.percentDelta(a, b); - } -} - -contract StdMathTest is Test { - function test_GetAbs() external pure { - assertEq(stdMath.abs(-50), 50); - assertEq(stdMath.abs(50), 50); - assertEq(stdMath.abs(-1337), 1337); - assertEq(stdMath.abs(0), 0); - - assertEq(stdMath.abs(type(int256).min), (type(uint256).max >> 1) + 1); - assertEq(stdMath.abs(type(int256).max), (type(uint256).max >> 1)); - } - - function testFuzz_GetAbs(int256 a) external pure { - uint256 manualAbs = getAbs(a); - - uint256 abs = stdMath.abs(a); - - assertEq(abs, manualAbs); - } - - function test_GetDelta_Uint() external pure { - assertEq(stdMath.delta(uint256(0), uint256(0)), 0); - assertEq(stdMath.delta(uint256(0), uint256(1337)), 1337); - assertEq(stdMath.delta(uint256(0), type(uint64).max), type(uint64).max); - assertEq(stdMath.delta(uint256(0), type(uint128).max), type(uint128).max); - assertEq(stdMath.delta(uint256(0), type(uint256).max), type(uint256).max); - - assertEq(stdMath.delta(0, uint256(0)), 0); - assertEq(stdMath.delta(1337, uint256(0)), 1337); - assertEq(stdMath.delta(type(uint64).max, uint256(0)), type(uint64).max); - assertEq(stdMath.delta(type(uint128).max, uint256(0)), type(uint128).max); - assertEq(stdMath.delta(type(uint256).max, uint256(0)), type(uint256).max); - - assertEq(stdMath.delta(1337, uint256(1337)), 0); - assertEq(stdMath.delta(type(uint256).max, type(uint256).max), 0); - assertEq(stdMath.delta(5000, uint256(1250)), 3750); - } - - function testFuzz_GetDelta_Uint(uint256 a, uint256 b) external pure { - uint256 manualDelta; - if (a > b) { - manualDelta = a - b; - } else { - manualDelta = b - a; - } - - uint256 delta = stdMath.delta(a, b); - - assertEq(delta, manualDelta); - } - - function test_GetDelta_Int() external pure { - assertEq(stdMath.delta(int256(0), int256(0)), 0); - assertEq(stdMath.delta(int256(0), int256(1337)), 1337); - assertEq(stdMath.delta(int256(0), type(int64).max), type(uint64).max >> 1); - assertEq(stdMath.delta(int256(0), type(int128).max), type(uint128).max >> 1); - assertEq(stdMath.delta(int256(0), type(int256).max), type(uint256).max >> 1); - - assertEq(stdMath.delta(0, int256(0)), 0); - assertEq(stdMath.delta(1337, int256(0)), 1337); - assertEq(stdMath.delta(type(int64).max, int256(0)), type(uint64).max >> 1); - assertEq(stdMath.delta(type(int128).max, int256(0)), type(uint128).max >> 1); - assertEq(stdMath.delta(type(int256).max, int256(0)), type(uint256).max >> 1); - - assertEq(stdMath.delta(-0, int256(0)), 0); - assertEq(stdMath.delta(-1337, int256(0)), 1337); - assertEq(stdMath.delta(type(int64).min, int256(0)), (type(uint64).max >> 1) + 1); - assertEq(stdMath.delta(type(int128).min, int256(0)), (type(uint128).max >> 1) + 1); - assertEq(stdMath.delta(type(int256).min, int256(0)), (type(uint256).max >> 1) + 1); - - assertEq(stdMath.delta(int256(0), -0), 0); - assertEq(stdMath.delta(int256(0), -1337), 1337); - assertEq(stdMath.delta(int256(0), type(int64).min), (type(uint64).max >> 1) + 1); - assertEq(stdMath.delta(int256(0), type(int128).min), (type(uint128).max >> 1) + 1); - assertEq(stdMath.delta(int256(0), type(int256).min), (type(uint256).max >> 1) + 1); - - assertEq(stdMath.delta(1337, int256(1337)), 0); - assertEq(stdMath.delta(type(int256).max, type(int256).max), 0); - assertEq(stdMath.delta(type(int256).min, type(int256).min), 0); - assertEq(stdMath.delta(type(int256).min, type(int256).max), type(uint256).max); - assertEq(stdMath.delta(5000, int256(1250)), 3750); - } - - function testFuzz_GetDelta_Int(int256 a, int256 b) external pure { - uint256 absA = getAbs(a); - uint256 absB = getAbs(b); - uint256 absDelta = absA > absB ? absA - absB : absB - absA; - - uint256 manualDelta; - if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { - manualDelta = absDelta; - } - // (a < 0 && b >= 0) || (a >= 0 && b < 0) - else { - manualDelta = absA + absB; - } - - uint256 delta = stdMath.delta(a, b); - - assertEq(delta, manualDelta); - } - - function test_GetPercentDelta_Uint() external { - StdMathMock stdMathMock = new StdMathMock(); - - assertEq(stdMath.percentDelta(uint256(0), uint256(1337)), 1e18); - assertEq(stdMath.percentDelta(uint256(0), type(uint64).max), 1e18); - assertEq(stdMath.percentDelta(uint256(0), type(uint128).max), 1e18); - assertEq(stdMath.percentDelta(uint256(0), type(uint192).max), 1e18); - - assertEq(stdMath.percentDelta(1337, uint256(1337)), 0); - assertEq(stdMath.percentDelta(type(uint192).max, type(uint192).max), 0); - assertEq(stdMath.percentDelta(0, uint256(2500)), 1e18); - assertEq(stdMath.percentDelta(2500, uint256(2500)), 0); - assertEq(stdMath.percentDelta(5000, uint256(2500)), 1e18); - assertEq(stdMath.percentDelta(7500, uint256(2500)), 2e18); - - vm.expectRevert(stdError.divisionError); - stdMathMock.exposed_percentDelta(uint256(1), 0); - } - - function testFuzz_GetPercentDelta_Uint(uint192 a, uint192 b) external pure { - vm.assume(b != 0); - uint256 manualDelta; - if (a > b) { - manualDelta = a - b; - } else { - manualDelta = b - a; - } - - uint256 manualPercentDelta = manualDelta * 1e18 / b; - uint256 percentDelta = stdMath.percentDelta(a, b); - - assertEq(percentDelta, manualPercentDelta); - } - - function test_GetPercentDelta_Int() external { - // We deploy a mock version so we can properly test the revert. - StdMathMock stdMathMock = new StdMathMock(); - - assertEq(stdMath.percentDelta(int256(0), int256(1337)), 1e18); - assertEq(stdMath.percentDelta(int256(0), -1337), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int64).min), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int128).min), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int192).min), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int64).max), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int128).max), 1e18); - assertEq(stdMath.percentDelta(int256(0), type(int192).max), 1e18); - - assertEq(stdMath.percentDelta(1337, int256(1337)), 0); - assertEq(stdMath.percentDelta(type(int192).max, type(int192).max), 0); - assertEq(stdMath.percentDelta(type(int192).min, type(int192).min), 0); - - assertEq(stdMath.percentDelta(type(int192).min, type(int192).max), 2e18); // rounds the 1 wei diff down - assertEq(stdMath.percentDelta(type(int192).max, type(int192).min), 2e18 - 1); // rounds the 1 wei diff down - assertEq(stdMath.percentDelta(0, int256(2500)), 1e18); - assertEq(stdMath.percentDelta(2500, int256(2500)), 0); - assertEq(stdMath.percentDelta(5000, int256(2500)), 1e18); - assertEq(stdMath.percentDelta(7500, int256(2500)), 2e18); - - vm.expectRevert(stdError.divisionError); - stdMathMock.exposed_percentDelta(int256(1), 0); - } - - function testFuzz_GetPercentDelta_Int(int192 a, int192 b) external pure { - vm.assume(b != 0); - uint256 absA = getAbs(a); - uint256 absB = getAbs(b); - uint256 absDelta = absA > absB ? absA - absB : absB - absA; - - uint256 manualDelta; - if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { - manualDelta = absDelta; - } - // (a < 0 && b >= 0) || (a >= 0 && b < 0) - else { - manualDelta = absA + absB; - } - - uint256 manualPercentDelta = manualDelta * 1e18 / absB; - uint256 percentDelta = stdMath.percentDelta(a, b); - - assertEq(percentDelta, manualPercentDelta); - } - - /*////////////////////////////////////////////////////////////////////////// - HELPERS - //////////////////////////////////////////////////////////////////////////*/ - - function getAbs(int256 a) private pure returns (uint256) { - if (a < 0) { - return a == type(int256).min ? uint256(type(int256).max) + 1 : uint256(-a); - } - - return uint256(a); - } -} diff --git a/lib_forge_std/test/StdStorage.t.sol b/lib_forge_std/test/StdStorage.t.sol deleted file mode 100644 index e1e6aa0..0000000 --- a/lib_forge_std/test/StdStorage.t.sol +++ /dev/null @@ -1,463 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/StdStorage.sol"; -import "../src/Test.sol"; - -contract StdStorageTest is Test { - using stdStorage for StdStorage; - - StorageTest internal test; - - function setUp() public { - test = new StorageTest(); - } - - function test_StorageHidden() public { - assertEq(uint256(keccak256("my.random.var")), stdstore.target(address(test)).sig("hidden()").find()); - } - - function test_StorageObvious() public { - assertEq(uint256(0), stdstore.target(address(test)).sig("exists()").find()); - } - - function test_StorageExtraSload() public { - assertEq(16, stdstore.target(address(test)).sig(test.extra_sload.selector).find()); - } - - function test_StorageCheckedWriteHidden() public { - stdstore.target(address(test)).sig(test.hidden.selector).checked_write(100); - assertEq(uint256(test.hidden()), 100); - } - - function test_StorageCheckedWriteObvious() public { - stdstore.target(address(test)).sig(test.exists.selector).checked_write(100); - assertEq(test.exists(), 100); - } - - function test_StorageCheckedWriteSignedIntegerHidden() public { - stdstore.target(address(test)).sig(test.hidden.selector).checked_write_int(-100); - assertEq(int256(uint256(test.hidden())), -100); - } - - function test_StorageCheckedWriteSignedIntegerObvious() public { - stdstore.target(address(test)).sig(test.tG.selector).checked_write_int(-100); - assertEq(test.tG(), -100); - } - - function test_StorageMapStructA() public { - uint256 slot = - stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(0).find(); - assertEq(uint256(keccak256(abi.encode(address(this), 4))), slot); - } - - function test_StorageMapStructB() public { - uint256 slot = - stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(1).find(); - assertEq(uint256(keccak256(abi.encode(address(this), 4))) + 1, slot); - } - - function test_StorageDeepMap() public { - uint256 slot = stdstore.target(address(test)).sig(test.deep_map.selector).with_key(address(this)).with_key( - address(this) - ).find(); - assertEq(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint256(5)))))), slot); - } - - function test_StorageCheckedWriteDeepMap() public { - stdstore.target(address(test)).sig(test.deep_map.selector).with_key(address(this)).with_key(address(this)) - .checked_write(100); - assertEq(100, test.deep_map(address(this), address(this))); - } - - function test_StorageDeepMapStructA() public { - uint256 slot = stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)) - .with_key(address(this)).depth(0).find(); - assertEq( - bytes32(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint256(6)))))) + 0), - bytes32(slot) - ); - } - - function test_StorageDeepMapStructB() public { - uint256 slot = stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)) - .with_key(address(this)).depth(1).find(); - assertEq( - bytes32(uint256(keccak256(abi.encode(address(this), keccak256(abi.encode(address(this), uint256(6)))))) + 1), - bytes32(slot) - ); - } - - function test_StorageCheckedWriteDeepMapStructA() public { - stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)).with_key( - address(this) - ).depth(0).checked_write(100); - (uint256 a, uint256 b) = test.deep_map_struct(address(this), address(this)); - assertEq(100, a); - assertEq(0, b); - } - - function test_StorageCheckedWriteDeepMapStructB() public { - stdstore.target(address(test)).sig(test.deep_map_struct.selector).with_key(address(this)).with_key( - address(this) - ).depth(1).checked_write(100); - (uint256 a, uint256 b) = test.deep_map_struct(address(this), address(this)); - assertEq(0, a); - assertEq(100, b); - } - - function test_StorageCheckedWriteMapStructA() public { - stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(0).checked_write(100); - (uint256 a, uint256 b) = test.map_struct(address(this)); - assertEq(a, 100); - assertEq(b, 0); - } - - function test_StorageCheckedWriteMapStructB() public { - stdstore.target(address(test)).sig(test.map_struct.selector).with_key(address(this)).depth(1).checked_write(100); - (uint256 a, uint256 b) = test.map_struct(address(this)); - assertEq(a, 0); - assertEq(b, 100); - } - - function test_StorageStructA() public { - uint256 slot = stdstore.target(address(test)).sig(test.basic.selector).depth(0).find(); - assertEq(uint256(7), slot); - } - - function test_StorageStructB() public { - uint256 slot = stdstore.target(address(test)).sig(test.basic.selector).depth(1).find(); - assertEq(uint256(7) + 1, slot); - } - - function test_StorageCheckedWriteStructA() public { - stdstore.target(address(test)).sig(test.basic.selector).depth(0).checked_write(100); - (uint256 a, uint256 b) = test.basic(); - assertEq(a, 100); - assertEq(b, 1337); - } - - function test_StorageCheckedWriteStructB() public { - stdstore.target(address(test)).sig(test.basic.selector).depth(1).checked_write(100); - (uint256 a, uint256 b) = test.basic(); - assertEq(a, 1337); - assertEq(b, 100); - } - - function test_StorageMapAddrFound() public { - uint256 slot = stdstore.target(address(test)).sig(test.map_addr.selector).with_key(address(this)).find(); - assertEq(uint256(keccak256(abi.encode(address(this), uint256(1)))), slot); - } - - function test_StorageMapAddrRoot() public { - (uint256 slot, bytes32 key) = - stdstore.target(address(test)).sig(test.map_addr.selector).with_key(address(this)).parent(); - assertEq(address(uint160(uint256(key))), address(this)); - assertEq(uint256(1), slot); - slot = stdstore.target(address(test)).sig(test.map_addr.selector).with_key(address(this)).root(); - assertEq(uint256(1), slot); - } - - function test_StorageMapUintFound() public { - uint256 slot = stdstore.target(address(test)).sig(test.map_uint.selector).with_key(100).find(); - assertEq(uint256(keccak256(abi.encode(100, uint256(2)))), slot); - } - - function test_StorageCheckedWriteMapUint() public { - stdstore.target(address(test)).sig(test.map_uint.selector).with_key(100).checked_write(100); - assertEq(100, test.map_uint(100)); - } - - function test_StorageCheckedWriteMapAddr() public { - stdstore.target(address(test)).sig(test.map_addr.selector).with_key(address(this)).checked_write(100); - assertEq(100, test.map_addr(address(this))); - } - - function test_StorageCheckedWriteMapBool() public { - stdstore.target(address(test)).sig(test.map_bool.selector).with_key(address(this)).checked_write(true); - assertTrue(test.map_bool(address(this))); - } - - function testFuzz_StorageCheckedWriteMapPacked(address addr, uint128 value) public { - stdstore.enable_packed_slots().target(address(test)).sig(test.read_struct_lower.selector).with_key(addr) - .checked_write(value); - assertEq(test.read_struct_lower(addr), value); - - stdstore.enable_packed_slots().target(address(test)).sig(test.read_struct_upper.selector).with_key(addr) - .checked_write(value); - assertEq(test.read_struct_upper(addr), value); - } - - function test_StorageCheckedWriteMapPackedFullSuccess() public { - uint256 full = test.map_packed(address(1337)); - // keep upper 128, set lower 128 to 1337 - full = (full & (uint256((1 << 128) - 1) << 128)) | 1337; - stdstore.target(address(test)).sig(test.map_packed.selector).with_key(address(uint160(1337))).checked_write( - full - ); - assertEq(1337, test.read_struct_lower(address(1337))); - } - - function testFail_StorageConst() public { - // vm.expectRevert(abi.encodeWithSignature("NotStorage(bytes4)", bytes4(keccak256("const()")))); - stdstore.target(address(test)).sig("const()").find(); - } - - function testFuzz_StorageNativePack(uint248 val1, uint248 val2, bool boolVal1, bool boolVal2) public { - stdstore.enable_packed_slots().target(address(test)).sig(test.tA.selector).checked_write(val1); - stdstore.enable_packed_slots().target(address(test)).sig(test.tB.selector).checked_write(boolVal1); - stdstore.enable_packed_slots().target(address(test)).sig(test.tC.selector).checked_write(boolVal2); - stdstore.enable_packed_slots().target(address(test)).sig(test.tD.selector).checked_write(val2); - - assertEq(test.tA(), val1); - assertEq(test.tB(), boolVal1); - assertEq(test.tC(), boolVal2); - assertEq(test.tD(), val2); - } - - function test_StorageReadBytes32() public { - bytes32 val = stdstore.target(address(test)).sig(test.tE.selector).read_bytes32(); - assertEq(val, hex"1337"); - } - - function test_StorageReadBool_False() public { - bool val = stdstore.target(address(test)).sig(test.tB.selector).read_bool(); - assertEq(val, false); - } - - function test_StorageReadBool_True() public { - bool val = stdstore.target(address(test)).sig(test.tH.selector).read_bool(); - assertEq(val, true); - } - - function test_StorageReadBool_Revert() public { - vm.expectRevert("stdStorage read_bool(StdStorage): Cannot decode. Make sure you are reading a bool."); - this.readNonBoolValue(); - } - - function readNonBoolValue() public { - stdstore.target(address(test)).sig(test.tE.selector).read_bool(); - } - - function test_StorageReadAddress() public { - address val = stdstore.target(address(test)).sig(test.tF.selector).read_address(); - assertEq(val, address(1337)); - } - - function test_StorageReadUint() public { - uint256 val = stdstore.target(address(test)).sig(test.exists.selector).read_uint(); - assertEq(val, 1); - } - - function test_StorageReadInt() public { - int256 val = stdstore.target(address(test)).sig(test.tG.selector).read_int(); - assertEq(val, type(int256).min); - } - - function testFuzzPacked(uint256 val, uint8 elemToGet) public { - // This function tries an assortment of packed slots, shifts meaning number of elements - // that are packed. Shiftsizes are the size of each element, i.e. 8 means a data type that is 8 bits, 16 == 16 bits, etc. - // Combined, these determine how a slot is packed. Making it random is too hard to avoid global rejection limit - // and make it performant. - - // change the number of shifts - for (uint256 i = 1; i < 5; i++) { - uint256 shifts = i; - - elemToGet = uint8(bound(elemToGet, 0, shifts - 1)); - - uint256[] memory shiftSizes = new uint256[](shifts); - for (uint256 j; j < shifts; j++) { - shiftSizes[j] = 8 * (j + 1); - } - - test.setRandomPacking(val); - - uint256 leftBits; - uint256 rightBits; - for (uint256 j; j < shiftSizes.length; j++) { - if (j < elemToGet) { - leftBits += shiftSizes[j]; - } else if (elemToGet != j) { - rightBits += shiftSizes[j]; - } - } - - // we may have some right bits unaccounted for - leftBits += 256 - (leftBits + shiftSizes[elemToGet] + rightBits); - // clear left bits, then clear right bits and realign - uint256 expectedValToRead = (val << leftBits) >> (leftBits + rightBits); - - uint256 readVal = stdstore.target(address(test)).enable_packed_slots().sig( - "getRandomPacked(uint8,uint8[],uint8)" - ).with_calldata(abi.encode(shifts, shiftSizes, elemToGet)).read_uint(); - - assertEq(readVal, expectedValToRead); - } - } - - function testFuzzPacked2(uint256 nvars, uint256 seed) public { - // Number of random variables to generate. - nvars = bound(nvars, 1, 20); - - // This will decrease as we generate values in the below loop. - uint256 bitsRemaining = 256; - - // Generate a random value and size for each variable. - uint256[] memory vals = new uint256[](nvars); - uint256[] memory sizes = new uint256[](nvars); - uint256[] memory offsets = new uint256[](nvars); - - for (uint256 i = 0; i < nvars; i++) { - // Generate a random value and size. - offsets[i] = i == 0 ? 0 : offsets[i - 1] + sizes[i - 1]; - - uint256 nvarsRemaining = nvars - i; - uint256 maxVarSize = bitsRemaining - nvarsRemaining + 1; - sizes[i] = bound(uint256(keccak256(abi.encodePacked(seed, i + 256))), 1, maxVarSize); - bitsRemaining -= sizes[i]; - - uint256 maxVal; - uint256 varSize = sizes[i]; - assembly { - // mask = (1 << varSize) - 1 - maxVal := sub(shl(varSize, 1), 1) - } - vals[i] = bound(uint256(keccak256(abi.encodePacked(seed, i))), 0, maxVal); - } - - // Pack all values into the slot. - for (uint256 i = 0; i < nvars; i++) { - stdstore.enable_packed_slots().target(address(test)).sig("getRandomPacked(uint256,uint256)").with_key( - sizes[i] - ).with_key(offsets[i]).checked_write(vals[i]); - } - - // Verify the read data matches. - for (uint256 i = 0; i < nvars; i++) { - uint256 readVal = stdstore.enable_packed_slots().target(address(test)).sig( - "getRandomPacked(uint256,uint256)" - ).with_key(sizes[i]).with_key(offsets[i]).read_uint(); - - uint256 retVal = test.getRandomPacked(sizes[i], offsets[i]); - - assertEq(readVal, vals[i]); - assertEq(retVal, vals[i]); - } - } -} - -contract StorageTest { - uint256 public exists = 1; - mapping(address => uint256) public map_addr; - mapping(uint256 => uint256) public map_uint; - mapping(address => uint256) public map_packed; - mapping(address => UnpackedStruct) public map_struct; - mapping(address => mapping(address => uint256)) public deep_map; - mapping(address => mapping(address => UnpackedStruct)) public deep_map_struct; - UnpackedStruct public basic; - - uint248 public tA; - bool public tB; - - bool public tC = false; - uint248 public tD = 1; - - struct UnpackedStruct { - uint256 a; - uint256 b; - } - - mapping(address => bool) public map_bool; - - bytes32 public tE = hex"1337"; - address public tF = address(1337); - int256 public tG = type(int256).min; - bool public tH = true; - bytes32 private tI = ~bytes32(hex"1337"); - - uint256 randomPacking; - - constructor() { - basic = UnpackedStruct({a: 1337, b: 1337}); - - uint256 two = (1 << 128) | 1; - map_packed[msg.sender] = two; - map_packed[address(uint160(1337))] = 1 << 128; - } - - function read_struct_upper(address who) public view returns (uint256) { - return map_packed[who] >> 128; - } - - function read_struct_lower(address who) public view returns (uint256) { - return map_packed[who] & ((1 << 128) - 1); - } - - function hidden() public view returns (bytes32 t) { - bytes32 slot = keccak256("my.random.var"); - /// @solidity memory-safe-assembly - assembly { - t := sload(slot) - } - } - - function const() public pure returns (bytes32 t) { - t = bytes32(hex"1337"); - } - - function extra_sload() public view returns (bytes32 t) { - // trigger read on slot `tE`, and make a staticcall to make sure compiler doesn't optimize this SLOAD away - assembly { - pop(staticcall(gas(), sload(tE.slot), 0, 0, 0, 0)) - } - t = tI; - } - - function setRandomPacking(uint256 val) public { - randomPacking = val; - } - - function _getMask(uint256 size) internal pure returns (uint256 mask) { - assembly { - // mask = (1 << size) - 1 - mask := sub(shl(size, 1), 1) - } - } - - function setRandomPacking(uint256 val, uint256 size, uint256 offset) public { - // Generate mask based on the size of the value - uint256 mask = _getMask(size); - // Zero out all bits for the word we're about to set - uint256 cleanedWord = randomPacking & ~(mask << offset); - // Place val in the correct spot of the cleaned word - randomPacking = cleanedWord | val << offset; - } - - function getRandomPacked(uint256 size, uint256 offset) public view returns (uint256) { - // Generate mask based on the size of the value - uint256 mask = _getMask(size); - // Shift to place the bits in the correct position, and use mask to zero out remaining bits - return (randomPacking >> offset) & mask; - } - - function getRandomPacked(uint8 shifts, uint8[] memory shiftSizes, uint8 elem) public view returns (uint256) { - require(elem < shifts, "!elem"); - uint256 leftBits; - uint256 rightBits; - - for (uint256 i; i < shiftSizes.length; i++) { - if (i < elem) { - leftBits += shiftSizes[i]; - } else if (elem != i) { - rightBits += shiftSizes[i]; - } - } - - // we may have some right bits unaccounted for - leftBits += 256 - (leftBits + shiftSizes[elem] + rightBits); - - // clear left bits, then clear right bits and realign - return (randomPacking << leftBits) >> (leftBits + rightBits); - } -} diff --git a/lib_forge_std/test/StdStyle.t.sol b/lib_forge_std/test/StdStyle.t.sol deleted file mode 100644 index e12c005..0000000 --- a/lib_forge_std/test/StdStyle.t.sol +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/Test.sol"; - -contract StdStyleTest is Test { - function test_StyleColor() public pure { - console2.log(StdStyle.red("StdStyle.red String Test")); - console2.log(StdStyle.red(uint256(10e18))); - console2.log(StdStyle.red(int256(-10e18))); - console2.log(StdStyle.red(true)); - console2.log(StdStyle.red(address(0))); - console2.log(StdStyle.redBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.redBytes32("StdStyle.redBytes32")); - console2.log(StdStyle.green("StdStyle.green String Test")); - console2.log(StdStyle.green(uint256(10e18))); - console2.log(StdStyle.green(int256(-10e18))); - console2.log(StdStyle.green(true)); - console2.log(StdStyle.green(address(0))); - console2.log(StdStyle.greenBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.greenBytes32("StdStyle.greenBytes32")); - console2.log(StdStyle.yellow("StdStyle.yellow String Test")); - console2.log(StdStyle.yellow(uint256(10e18))); - console2.log(StdStyle.yellow(int256(-10e18))); - console2.log(StdStyle.yellow(true)); - console2.log(StdStyle.yellow(address(0))); - console2.log(StdStyle.yellowBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.yellowBytes32("StdStyle.yellowBytes32")); - console2.log(StdStyle.blue("StdStyle.blue String Test")); - console2.log(StdStyle.blue(uint256(10e18))); - console2.log(StdStyle.blue(int256(-10e18))); - console2.log(StdStyle.blue(true)); - console2.log(StdStyle.blue(address(0))); - console2.log(StdStyle.blueBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.blueBytes32("StdStyle.blueBytes32")); - console2.log(StdStyle.magenta("StdStyle.magenta String Test")); - console2.log(StdStyle.magenta(uint256(10e18))); - console2.log(StdStyle.magenta(int256(-10e18))); - console2.log(StdStyle.magenta(true)); - console2.log(StdStyle.magenta(address(0))); - console2.log(StdStyle.magentaBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.magentaBytes32("StdStyle.magentaBytes32")); - console2.log(StdStyle.cyan("StdStyle.cyan String Test")); - console2.log(StdStyle.cyan(uint256(10e18))); - console2.log(StdStyle.cyan(int256(-10e18))); - console2.log(StdStyle.cyan(true)); - console2.log(StdStyle.cyan(address(0))); - console2.log(StdStyle.cyanBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.cyanBytes32("StdStyle.cyanBytes32")); - } - - function test_StyleFontWeight() public pure { - console2.log(StdStyle.bold("StdStyle.bold String Test")); - console2.log(StdStyle.bold(uint256(10e18))); - console2.log(StdStyle.bold(int256(-10e18))); - console2.log(StdStyle.bold(address(0))); - console2.log(StdStyle.bold(true)); - console2.log(StdStyle.boldBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.boldBytes32("StdStyle.boldBytes32")); - console2.log(StdStyle.dim("StdStyle.dim String Test")); - console2.log(StdStyle.dim(uint256(10e18))); - console2.log(StdStyle.dim(int256(-10e18))); - console2.log(StdStyle.dim(address(0))); - console2.log(StdStyle.dim(true)); - console2.log(StdStyle.dimBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.dimBytes32("StdStyle.dimBytes32")); - console2.log(StdStyle.italic("StdStyle.italic String Test")); - console2.log(StdStyle.italic(uint256(10e18))); - console2.log(StdStyle.italic(int256(-10e18))); - console2.log(StdStyle.italic(address(0))); - console2.log(StdStyle.italic(true)); - console2.log(StdStyle.italicBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.italicBytes32("StdStyle.italicBytes32")); - console2.log(StdStyle.underline("StdStyle.underline String Test")); - console2.log(StdStyle.underline(uint256(10e18))); - console2.log(StdStyle.underline(int256(-10e18))); - console2.log(StdStyle.underline(address(0))); - console2.log(StdStyle.underline(true)); - console2.log(StdStyle.underlineBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.underlineBytes32("StdStyle.underlineBytes32")); - console2.log(StdStyle.inverse("StdStyle.inverse String Test")); - console2.log(StdStyle.inverse(uint256(10e18))); - console2.log(StdStyle.inverse(int256(-10e18))); - console2.log(StdStyle.inverse(address(0))); - console2.log(StdStyle.inverse(true)); - console2.log(StdStyle.inverseBytes(hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D")); - console2.log(StdStyle.inverseBytes32("StdStyle.inverseBytes32")); - } - - function test_StyleCombined() public pure { - console2.log(StdStyle.red(StdStyle.bold("Red Bold String Test"))); - console2.log(StdStyle.green(StdStyle.dim(uint256(10e18)))); - console2.log(StdStyle.yellow(StdStyle.italic(int256(-10e18)))); - console2.log(StdStyle.blue(StdStyle.underline(address(0)))); - console2.log(StdStyle.magenta(StdStyle.inverse(true))); - } - - function test_StyleCustom() public pure { - console2.log(h1("Custom Style 1")); - console2.log(h2("Custom Style 2")); - } - - function h1(string memory a) private pure returns (string memory) { - return StdStyle.cyan(StdStyle.inverse(StdStyle.bold(a))); - } - - function h2(string memory a) private pure returns (string memory) { - return StdStyle.magenta(StdStyle.bold(StdStyle.underline(a))); - } -} diff --git a/lib_forge_std/test/StdToml.t.sol b/lib_forge_std/test/StdToml.t.sol deleted file mode 100644 index 631b1b5..0000000 --- a/lib_forge_std/test/StdToml.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/Test.sol"; - -contract StdTomlTest is Test { - using stdToml for string; - - string root; - string path; - - function setUp() public { - root = vm.projectRoot(); - path = string.concat(root, "/test/fixtures/test.toml"); - } - - struct SimpleToml { - uint256 a; - string b; - } - - struct NestedToml { - uint256 a; - string b; - SimpleToml c; - } - - function test_readToml() public view { - string memory json = vm.readFile(path); - assertEq(json.readUint(".a"), 123); - } - - function test_writeToml() public { - string memory json = "json"; - json.serialize("a", uint256(123)); - string memory semiFinal = json.serialize("b", string("test")); - string memory finalJson = json.serialize("c", semiFinal); - finalJson.write(path); - - string memory toml = vm.readFile(path); - bytes memory data = toml.parseRaw("$"); - NestedToml memory decodedData = abi.decode(data, (NestedToml)); - - assertEq(decodedData.a, 123); - assertEq(decodedData.b, "test"); - assertEq(decodedData.c.a, 123); - assertEq(decodedData.c.b, "test"); - } -} diff --git a/lib_forge_std/test/StdUtils.t.sol b/lib_forge_std/test/StdUtils.t.sol deleted file mode 100644 index 4994c6c..0000000 --- a/lib_forge_std/test/StdUtils.t.sol +++ /dev/null @@ -1,342 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import "../src/Test.sol"; - -contract StdUtilsMock is StdUtils { - // We deploy a mock version so we can properly test expected reverts. - function exposed_getTokenBalances(address token, address[] memory addresses) - external - returns (uint256[] memory balances) - { - return getTokenBalances(token, addresses); - } - - function exposed_bound(int256 num, int256 min, int256 max) external pure returns (int256) { - return bound(num, min, max); - } - - function exposed_bound(uint256 num, uint256 min, uint256 max) external pure returns (uint256) { - return bound(num, min, max); - } - - function exposed_bytesToUint(bytes memory b) external pure returns (uint256) { - return bytesToUint(b); - } -} - -contract StdUtilsTest is Test { - /*////////////////////////////////////////////////////////////////////////// - BOUND UINT - //////////////////////////////////////////////////////////////////////////*/ - - function test_Bound() public pure { - assertEq(bound(uint256(5), 0, 4), 0); - assertEq(bound(uint256(0), 69, 69), 69); - assertEq(bound(uint256(0), 68, 69), 68); - assertEq(bound(uint256(10), 150, 190), 174); - assertEq(bound(uint256(300), 2800, 3200), 3107); - assertEq(bound(uint256(9999), 1337, 6666), 4669); - } - - function test_Bound_WithinRange() public pure { - assertEq(bound(uint256(51), 50, 150), 51); - assertEq(bound(uint256(51), 50, 150), bound(bound(uint256(51), 50, 150), 50, 150)); - assertEq(bound(uint256(149), 50, 150), 149); - assertEq(bound(uint256(149), 50, 150), bound(bound(uint256(149), 50, 150), 50, 150)); - } - - function test_Bound_EdgeCoverage() public pure { - assertEq(bound(uint256(0), 50, 150), 50); - assertEq(bound(uint256(1), 50, 150), 51); - assertEq(bound(uint256(2), 50, 150), 52); - assertEq(bound(uint256(3), 50, 150), 53); - assertEq(bound(type(uint256).max, 50, 150), 150); - assertEq(bound(type(uint256).max - 1, 50, 150), 149); - assertEq(bound(type(uint256).max - 2, 50, 150), 148); - assertEq(bound(type(uint256).max - 3, 50, 150), 147); - } - - function test_Bound_DistributionIsEven(uint256 min, uint256 size) public pure { - size = size % 100 + 1; - min = bound(min, UINT256_MAX / 2, UINT256_MAX / 2 + size); - uint256 max = min + size - 1; - uint256 result; - - for (uint256 i = 1; i <= size * 4; ++i) { - // x > max - result = bound(max + i, min, max); - assertEq(result, min + (i - 1) % size); - // x < min - result = bound(min - i, min, max); - assertEq(result, max - (i - 1) % size); - } - } - - function test_Bound(uint256 num, uint256 min, uint256 max) public pure { - if (min > max) (min, max) = (max, min); - - uint256 result = bound(num, min, max); - - assertGe(result, min); - assertLe(result, max); - assertEq(result, bound(result, min, max)); - if (num >= min && num <= max) assertEq(result, num); - } - - function test_BoundUint256Max() public pure { - assertEq(bound(0, type(uint256).max - 1, type(uint256).max), type(uint256).max - 1); - assertEq(bound(1, type(uint256).max - 1, type(uint256).max), type(uint256).max); - } - - function test_CannotBoundMaxLessThanMin() public { - // We deploy a mock version so we can properly test the revert. - StdUtilsMock stdUtils = new StdUtilsMock(); - - vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); - stdUtils.exposed_bound(uint256(5), 100, 10); - } - - function test_CannotBoundMaxLessThanMin(uint256 num, uint256 min, uint256 max) public { - // We deploy a mock version so we can properly test the revert. - StdUtilsMock stdUtils = new StdUtilsMock(); - - vm.assume(min > max); - vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); - stdUtils.exposed_bound(num, min, max); - } - - /*////////////////////////////////////////////////////////////////////////// - BOUND INT - //////////////////////////////////////////////////////////////////////////*/ - - function test_BoundInt() public pure { - assertEq(bound(-3, 0, 4), 2); - assertEq(bound(0, -69, -69), -69); - assertEq(bound(0, -69, -68), -68); - assertEq(bound(-10, 150, 190), 154); - assertEq(bound(-300, 2800, 3200), 2908); - assertEq(bound(9999, -1337, 6666), 1995); - } - - function test_BoundInt_WithinRange() public pure { - assertEq(bound(51, -50, 150), 51); - assertEq(bound(51, -50, 150), bound(bound(51, -50, 150), -50, 150)); - assertEq(bound(149, -50, 150), 149); - assertEq(bound(149, -50, 150), bound(bound(149, -50, 150), -50, 150)); - } - - function test_BoundInt_EdgeCoverage() public pure { - assertEq(bound(type(int256).min, -50, 150), -50); - assertEq(bound(type(int256).min + 1, -50, 150), -49); - assertEq(bound(type(int256).min + 2, -50, 150), -48); - assertEq(bound(type(int256).min + 3, -50, 150), -47); - assertEq(bound(type(int256).min, 10, 150), 10); - assertEq(bound(type(int256).min + 1, 10, 150), 11); - assertEq(bound(type(int256).min + 2, 10, 150), 12); - assertEq(bound(type(int256).min + 3, 10, 150), 13); - - assertEq(bound(type(int256).max, -50, 150), 150); - assertEq(bound(type(int256).max - 1, -50, 150), 149); - assertEq(bound(type(int256).max - 2, -50, 150), 148); - assertEq(bound(type(int256).max - 3, -50, 150), 147); - assertEq(bound(type(int256).max, -50, -10), -10); - assertEq(bound(type(int256).max - 1, -50, -10), -11); - assertEq(bound(type(int256).max - 2, -50, -10), -12); - assertEq(bound(type(int256).max - 3, -50, -10), -13); - } - - function test_BoundInt_DistributionIsEven(int256 min, uint256 size) public pure { - size = size % 100 + 1; - min = bound(min, -int256(size / 2), int256(size - size / 2)); - int256 max = min + int256(size) - 1; - int256 result; - - for (uint256 i = 1; i <= size * 4; ++i) { - // x > max - result = bound(max + int256(i), min, max); - assertEq(result, min + int256((i - 1) % size)); - // x < min - result = bound(min - int256(i), min, max); - assertEq(result, max - int256((i - 1) % size)); - } - } - - function test_BoundInt(int256 num, int256 min, int256 max) public pure { - if (min > max) (min, max) = (max, min); - - int256 result = bound(num, min, max); - - assertGe(result, min); - assertLe(result, max); - assertEq(result, bound(result, min, max)); - if (num >= min && num <= max) assertEq(result, num); - } - - function test_BoundIntInt256Max() public pure { - assertEq(bound(0, type(int256).max - 1, type(int256).max), type(int256).max - 1); - assertEq(bound(1, type(int256).max - 1, type(int256).max), type(int256).max); - } - - function test_BoundIntInt256Min() public pure { - assertEq(bound(0, type(int256).min, type(int256).min + 1), type(int256).min); - assertEq(bound(1, type(int256).min, type(int256).min + 1), type(int256).min + 1); - } - - function test_CannotBoundIntMaxLessThanMin() public { - // We deploy a mock version so we can properly test the revert. - StdUtilsMock stdUtils = new StdUtilsMock(); - - vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min.")); - stdUtils.exposed_bound(-5, 100, 10); - } - - function test_CannotBoundIntMaxLessThanMin(int256 num, int256 min, int256 max) public { - // We deploy a mock version so we can properly test the revert. - StdUtilsMock stdUtils = new StdUtilsMock(); - - vm.assume(min > max); - vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min.")); - stdUtils.exposed_bound(num, min, max); - } - - /*////////////////////////////////////////////////////////////////////////// - BOUND PRIVATE KEY - //////////////////////////////////////////////////////////////////////////*/ - - function test_BoundPrivateKey() public pure { - assertEq(boundPrivateKey(0), 1); - assertEq(boundPrivateKey(1), 1); - assertEq(boundPrivateKey(300), 300); - assertEq(boundPrivateKey(9999), 9999); - assertEq(boundPrivateKey(SECP256K1_ORDER - 1), SECP256K1_ORDER - 1); - assertEq(boundPrivateKey(SECP256K1_ORDER), 1); - assertEq(boundPrivateKey(SECP256K1_ORDER + 1), 2); - assertEq(boundPrivateKey(UINT256_MAX), UINT256_MAX & SECP256K1_ORDER - 1); // x&y is equivalent to x-x%y - } - - /*////////////////////////////////////////////////////////////////////////// - BYTES TO UINT - //////////////////////////////////////////////////////////////////////////*/ - - function test_BytesToUint() external pure { - bytes memory maxUint = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; - bytes memory two = hex"02"; - bytes memory millionEther = hex"d3c21bcecceda1000000"; - - assertEq(bytesToUint(maxUint), type(uint256).max); - assertEq(bytesToUint(two), 2); - assertEq(bytesToUint(millionEther), 1_000_000 ether); - } - - function test_CannotConvertGT32Bytes() external { - // We deploy a mock version so we can properly test the revert. - StdUtilsMock stdUtils = new StdUtilsMock(); - - bytes memory thirty3Bytes = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; - vm.expectRevert("StdUtils bytesToUint(bytes): Bytes length exceeds 32."); - stdUtils.exposed_bytesToUint(thirty3Bytes); - } - - /*////////////////////////////////////////////////////////////////////////// - COMPUTE CREATE ADDRESS - //////////////////////////////////////////////////////////////////////////*/ - - function test_ComputeCreateAddress() external pure { - address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; - uint256 nonce = 14; - address createAddress = computeCreateAddress(deployer, nonce); - assertEq(createAddress, 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); - } - - /*////////////////////////////////////////////////////////////////////////// - COMPUTE CREATE2 ADDRESS - //////////////////////////////////////////////////////////////////////////*/ - - function test_ComputeCreate2Address() external pure { - bytes32 salt = bytes32(uint256(31415)); - bytes32 initcodeHash = keccak256(abi.encode(0x6080)); - address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; - address create2Address = computeCreate2Address(salt, initcodeHash, deployer); - assertEq(create2Address, 0xB147a5d25748fda14b463EB04B111027C290f4d3); - } - - function test_ComputeCreate2AddressWithDefaultDeployer() external pure { - bytes32 salt = 0xc290c670fde54e5ef686f9132cbc8711e76a98f0333a438a92daa442c71403c0; - bytes32 initcodeHash = hashInitCode(hex"6080", ""); - assertEq(initcodeHash, 0x1a578b7a4b0b5755db6d121b4118d4bc68fe170dca840c59bc922f14175a76b0); - address create2Address = computeCreate2Address(salt, initcodeHash); - assertEq(create2Address, 0xc0ffEe2198a06235aAbFffe5Db0CacF1717f5Ac6); - } -} - -contract StdUtilsForkTest is Test { - /*////////////////////////////////////////////////////////////////////////// - GET TOKEN BALANCES - //////////////////////////////////////////////////////////////////////////*/ - - address internal SHIB = 0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE; - address internal SHIB_HOLDER_0 = 0x855F5981e831D83e6A4b4EBFCAdAa68D92333170; - address internal SHIB_HOLDER_1 = 0x8F509A90c2e47779cA408Fe00d7A72e359229AdA; - address internal SHIB_HOLDER_2 = 0x0e3bbc0D04fF62211F71f3e4C45d82ad76224385; - - address internal USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; - address internal USDC_HOLDER_0 = 0xDa9CE944a37d218c3302F6B82a094844C6ECEb17; - address internal USDC_HOLDER_1 = 0x3e67F4721E6d1c41a015f645eFa37BEd854fcf52; - - function setUp() public { - // All tests of the `getTokenBalances` method are fork tests using live contracts. - vm.createSelectFork({urlOrAlias: "mainnet", blockNumber: 16_428_900}); - } - - function test_CannotGetTokenBalances_NonTokenContract() external { - // We deploy a mock version so we can properly test the revert. - StdUtilsMock stdUtils = new StdUtilsMock(); - - // The UniswapV2Factory contract has neither a `balanceOf` function nor a fallback function, - // so the `balanceOf` call should revert. - address token = address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); - address[] memory addresses = new address[](1); - addresses[0] = USDC_HOLDER_0; - - vm.expectRevert("Multicall3: call failed"); - stdUtils.exposed_getTokenBalances(token, addresses); - } - - function test_CannotGetTokenBalances_EOA() external { - // We deploy a mock version so we can properly test the revert. - StdUtilsMock stdUtils = new StdUtilsMock(); - - address eoa = vm.addr({privateKey: 1}); - address[] memory addresses = new address[](1); - addresses[0] = USDC_HOLDER_0; - vm.expectRevert("StdUtils getTokenBalances(address,address[]): Token address is not a contract."); - stdUtils.exposed_getTokenBalances(eoa, addresses); - } - - function test_GetTokenBalances_Empty() external { - address[] memory addresses = new address[](0); - uint256[] memory balances = getTokenBalances(USDC, addresses); - assertEq(balances.length, 0); - } - - function test_GetTokenBalances_USDC() external { - address[] memory addresses = new address[](2); - addresses[0] = USDC_HOLDER_0; - addresses[1] = USDC_HOLDER_1; - uint256[] memory balances = getTokenBalances(USDC, addresses); - assertEq(balances[0], 159_000_000_000_000); - assertEq(balances[1], 131_350_000_000_000); - } - - function test_GetTokenBalances_SHIB() external { - address[] memory addresses = new address[](3); - addresses[0] = SHIB_HOLDER_0; - addresses[1] = SHIB_HOLDER_1; - addresses[2] = SHIB_HOLDER_2; - uint256[] memory balances = getTokenBalances(SHIB, addresses); - assertEq(balances[0], 3_323_256_285_484.42e18); - assertEq(balances[1], 1_271_702_771_149.99999928e18); - assertEq(balances[2], 606_357_106_247e18); - } -} diff --git a/lib_forge_std/test/Vm.t.sol b/lib_forge_std/test/Vm.t.sol deleted file mode 100644 index fc8253b..0000000 --- a/lib_forge_std/test/Vm.t.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import {Test} from "../src/Test.sol"; -import {Vm, VmSafe} from "../src/Vm.sol"; - -contract VmTest is Test { - // This test ensures that functions are never accidentally removed from a Vm interface, or - // inadvertently moved between Vm and VmSafe. This test must be updated each time a function is - // added to or removed from Vm or VmSafe. - function test_interfaceId() public pure { - assertEq(type(VmSafe).interfaceId, bytes4(0xdf5d274d), "VmSafe"); - assertEq(type(Vm).interfaceId, bytes4(0xb91a22ba), "Vm"); - } -} diff --git a/lib_forge_std/test/compilation/CompilationScript.sol b/lib_forge_std/test/compilation/CompilationScript.sol deleted file mode 100644 index e205cff..0000000 --- a/lib_forge_std/test/compilation/CompilationScript.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; - -pragma experimental ABIEncoderV2; - -import "../../src/Script.sol"; - -// The purpose of this contract is to benchmark compilation time to avoid accidentally introducing -// a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 -contract CompilationScript is Script {} diff --git a/lib_forge_std/test/compilation/CompilationScriptBase.sol b/lib_forge_std/test/compilation/CompilationScriptBase.sol deleted file mode 100644 index ce8e0e9..0000000 --- a/lib_forge_std/test/compilation/CompilationScriptBase.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; - -pragma experimental ABIEncoderV2; - -import "../../src/Script.sol"; - -// The purpose of this contract is to benchmark compilation time to avoid accidentally introducing -// a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 -contract CompilationScriptBase is ScriptBase {} diff --git a/lib_forge_std/test/compilation/CompilationTest.sol b/lib_forge_std/test/compilation/CompilationTest.sol deleted file mode 100644 index 9beeafe..0000000 --- a/lib_forge_std/test/compilation/CompilationTest.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; - -pragma experimental ABIEncoderV2; - -import "../../src/Test.sol"; - -// The purpose of this contract is to benchmark compilation time to avoid accidentally introducing -// a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 -contract CompilationTest is Test {} diff --git a/lib_forge_std/test/compilation/CompilationTestBase.sol b/lib_forge_std/test/compilation/CompilationTestBase.sol deleted file mode 100644 index e993535..0000000 --- a/lib_forge_std/test/compilation/CompilationTestBase.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; - -pragma experimental ABIEncoderV2; - -import "../../src/Test.sol"; - -// The purpose of this contract is to benchmark compilation time to avoid accidentally introducing -// a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 -contract CompilationTestBase is TestBase {} diff --git a/lib_forge_std/test/fixtures/broadcast.log.json b/lib_forge_std/test/fixtures/broadcast.log.json deleted file mode 100644 index 0a0200b..0000000 --- a/lib_forge_std/test/fixtures/broadcast.log.json +++ /dev/null @@ -1,187 +0,0 @@ -{ - "transactions": [ - { - "hash": "0xc6006863c267735a11476b7f15b15bc718e117e2da114a2be815dd651e1a509f", - "type": "CALL", - "contractName": "Test", - "contractAddress": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "function": "multiple_arguments(uint256,address,uint256[]):(uint256)", - "arguments": ["1", "0000000000000000000000000000000000001337", "[3,4]"], - "tx": { - "type": "0x02", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "gas": "0x73b9", - "value": "0x0", - "data": "0x23e99187000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000013370000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004", - "nonce": "0x3", - "accessList": [] - } - }, - { - "hash": "0xedf2b38d8d896519a947a1acf720f859bb35c0c5ecb8dd7511995b67b9853298", - "type": "CALL", - "contractName": "Test", - "contractAddress": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "function": "inc():(uint256)", - "arguments": [], - "tx": { - "type": "0x02", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "gas": "0xdcb2", - "value": "0x0", - "data": "0x371303c0", - "nonce": "0x4", - "accessList": [] - } - }, - { - "hash": "0xa57e8e3981a6c861442e46c9471bd19cb3e21f9a8a6c63a72e7b5c47c6675a7c", - "type": "CALL", - "contractName": "Test", - "contractAddress": "0x7c6b4bbe207d642d98d5c537142d85209e585087", - "function": "t(uint256):(uint256)", - "arguments": ["1"], - "tx": { - "type": "0x02", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0x7c6b4bbe207d642d98d5c537142d85209e585087", - "gas": "0x8599", - "value": "0x0", - "data": "0xafe29f710000000000000000000000000000000000000000000000000000000000000001", - "nonce": "0x5", - "accessList": [] - } - } - ], - "receipts": [ - { - "transactionHash": "0x481dc86e40bba90403c76f8e144aa9ff04c1da2164299d0298573835f0991181", - "transactionIndex": "0x0", - "blockHash": "0xef0730448490304e5403be0fa8f8ce64f118e9adcca60c07a2ae1ab921d748af", - "blockNumber": "0x1", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": null, - "cumulativeGasUsed": "0x13f3a", - "gasUsed": "0x13f3a", - "contractAddress": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "logs": [], - "status": "0x1", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "effectiveGasPrice": "0xee6b2800" - }, - { - "transactionHash": "0x6a187183545b8a9e7f1790e847139379bf5622baff2cb43acf3f5c79470af782", - "transactionIndex": "0x0", - "blockHash": "0xf3acb96a90071640c2a8c067ae4e16aad87e634ea8d8bbbb5b352fba86ba0148", - "blockNumber": "0x2", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": null, - "cumulativeGasUsed": "0x45d80", - "gasUsed": "0x45d80", - "contractAddress": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "logs": [], - "status": "0x1", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "effectiveGasPrice": "0xee6b2800" - }, - { - "transactionHash": "0x064ad173b4867bdef2fb60060bbdaf01735fbf10414541ea857772974e74ea9d", - "transactionIndex": "0x0", - "blockHash": "0x8373d02109d3ee06a0225f23da4c161c656ccc48fe0fcee931d325508ae73e58", - "blockNumber": "0x3", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", - "cumulativeGasUsed": "0x45feb", - "gasUsed": "0x45feb", - "contractAddress": null, - "logs": [], - "status": "0x1", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "effectiveGasPrice": "0xee6b2800" - }, - { - "transactionHash": "0xc6006863c267735a11476b7f15b15bc718e117e2da114a2be815dd651e1a509f", - "transactionIndex": "0x0", - "blockHash": "0x16712fae5c0e18f75045f84363fb6b4d9a9fe25e660c4ce286833a533c97f629", - "blockNumber": "0x4", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "cumulativeGasUsed": "0x5905", - "gasUsed": "0x5905", - "contractAddress": null, - "logs": [], - "status": "0x1", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "effectiveGasPrice": "0xee6b2800" - }, - { - "transactionHash": "0xedf2b38d8d896519a947a1acf720f859bb35c0c5ecb8dd7511995b67b9853298", - "transactionIndex": "0x0", - "blockHash": "0x156b88c3eb9a1244ba00a1834f3f70de735b39e3e59006dd03af4fe7d5480c11", - "blockNumber": "0x5", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", - "cumulativeGasUsed": "0xa9c4", - "gasUsed": "0xa9c4", - "contractAddress": null, - "logs": [], - "status": "0x1", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "effectiveGasPrice": "0xee6b2800" - }, - { - "transactionHash": "0xa57e8e3981a6c861442e46c9471bd19cb3e21f9a8a6c63a72e7b5c47c6675a7c", - "transactionIndex": "0x0", - "blockHash": "0xcf61faca67dbb2c28952b0b8a379e53b1505ae0821e84779679390cb8571cadb", - "blockNumber": "0x6", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0x7c6b4bbe207d642d98d5c537142d85209e585087", - "cumulativeGasUsed": "0x66c5", - "gasUsed": "0x66c5", - "contractAddress": null, - "logs": [ - { - "address": "0x7c6b4bbe207d642d98d5c537142d85209e585087", - "topics": [ - "0x0b2e13ff20ac7b474198655583edf70dedd2c1dc980e329c4fbb2fc0748b796b" - ], - "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046865726500000000000000000000000000000000000000000000000000000000", - "blockHash": "0xcf61faca67dbb2c28952b0b8a379e53b1505ae0821e84779679390cb8571cadb", - "blockNumber": "0x6", - "transactionHash": "0xa57e8e3981a6c861442e46c9471bd19cb3e21f9a8a6c63a72e7b5c47c6675a7c", - "transactionIndex": "0x1", - "logIndex": "0x0", - "transactionLogIndex": "0x0", - "removed": false - } - ], - "status": "0x1", - "logsBloom": "0x00000000000800000000000000000010000000000000000000000000000180000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100", - "effectiveGasPrice": "0xee6b2800" - }, - { - "transactionHash": "0x11fbb10230c168ca1e36a7e5c69a6dbcd04fd9e64ede39d10a83e36ee8065c16", - "transactionIndex": "0x0", - "blockHash": "0xf1e0ed2eda4e923626ec74621006ed50b3fc27580dc7b4cf68a07ca77420e29c", - "blockNumber": "0x7", - "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "to": "0x0000000000000000000000000000000000001337", - "cumulativeGasUsed": "0x5208", - "gasUsed": "0x5208", - "contractAddress": null, - "logs": [], - "status": "0x1", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "effectiveGasPrice": "0xee6b2800" - } - ], - "libraries": [ - "src/Broadcast.t.sol:F:0x5fbdb2315678afecb367f032d93f642f64180aa3" - ], - "pending": [], - "path": "broadcast/Broadcast.t.sol/31337/run-latest.json", - "returns": {}, - "timestamp": 1655140035 -} diff --git a/lib_forge_std/test/fixtures/test.json b/lib_forge_std/test/fixtures/test.json deleted file mode 100644 index caebf6d..0000000 --- a/lib_forge_std/test/fixtures/test.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "a": 123, - "b": "test", - "c": { - "a": 123, - "b": "test" - } -} \ No newline at end of file diff --git a/lib_forge_std/test/fixtures/test.toml b/lib_forge_std/test/fixtures/test.toml deleted file mode 100644 index 60692bc..0000000 --- a/lib_forge_std/test/fixtures/test.toml +++ /dev/null @@ -1,6 +0,0 @@ -a = 123 -b = "test" - -[c] -a = 123 -b = "test" diff --git a/lib_forge_std/test/mocks/MockERC20.t.sol b/lib_forge_std/test/mocks/MockERC20.t.sol deleted file mode 100644 index e246810..0000000 --- a/lib_forge_std/test/mocks/MockERC20.t.sol +++ /dev/null @@ -1,441 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import {MockERC20} from "../../src/mocks/MockERC20.sol"; -import {StdCheats} from "../../src/StdCheats.sol"; -import {Test} from "../../src/Test.sol"; - -contract Token_ERC20 is MockERC20 { - constructor(string memory name, string memory symbol, uint8 decimals) { - initialize(name, symbol, decimals); - } - - function mint(address to, uint256 value) public virtual { - _mint(to, value); - } - - function burn(address from, uint256 value) public virtual { - _burn(from, value); - } -} - -contract MockERC20Test is StdCheats, Test { - Token_ERC20 token; - - bytes32 constant PERMIT_TYPEHASH = - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - - function setUp() public { - token = new Token_ERC20("Token", "TKN", 18); - } - - function invariantMetadata() public view { - assertEq(token.name(), "Token"); - assertEq(token.symbol(), "TKN"); - assertEq(token.decimals(), 18); - } - - function testMint() public { - token.mint(address(0xBEEF), 1e18); - - assertEq(token.totalSupply(), 1e18); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); - } - - function testBurn() public { - token.mint(address(0xBEEF), 1e18); - token.burn(address(0xBEEF), 0.9e18); - - assertEq(token.totalSupply(), 1e18 - 0.9e18); - assertEq(token.balanceOf(address(0xBEEF)), 0.1e18); - } - - function testApprove() public { - assertTrue(token.approve(address(0xBEEF), 1e18)); - - assertEq(token.allowance(address(this), address(0xBEEF)), 1e18); - } - - function testTransfer() public { - token.mint(address(this), 1e18); - - assertTrue(token.transfer(address(0xBEEF), 1e18)); - assertEq(token.totalSupply(), 1e18); - - assertEq(token.balanceOf(address(this)), 0); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); - } - - function testTransferFrom() public { - address from = address(0xABCD); - - token.mint(from, 1e18); - - vm.prank(from); - token.approve(address(this), 1e18); - - assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); - assertEq(token.totalSupply(), 1e18); - - assertEq(token.allowance(from, address(this)), 0); - - assertEq(token.balanceOf(from), 0); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); - } - - function testInfiniteApproveTransferFrom() public { - address from = address(0xABCD); - - token.mint(from, 1e18); - - vm.prank(from); - token.approve(address(this), type(uint256).max); - - assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); - assertEq(token.totalSupply(), 1e18); - - assertEq(token.allowance(from, address(this)), type(uint256).max); - - assertEq(token.balanceOf(from), 0); - assertEq(token.balanceOf(address(0xBEEF)), 1e18); - } - - function testPermit() public { - uint256 privateKey = 0xBEEF; - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) - ) - ) - ); - - token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); - - assertEq(token.allowance(owner, address(0xCAFE)), 1e18); - assertEq(token.nonces(owner), 1); - } - - function testFailTransferInsufficientBalance() public { - token.mint(address(this), 0.9e18); - token.transfer(address(0xBEEF), 1e18); - } - - function testFailTransferFromInsufficientAllowance() public { - address from = address(0xABCD); - - token.mint(from, 1e18); - - vm.prank(from); - token.approve(address(this), 0.9e18); - - token.transferFrom(from, address(0xBEEF), 1e18); - } - - function testFailTransferFromInsufficientBalance() public { - address from = address(0xABCD); - - token.mint(from, 0.9e18); - - vm.prank(from); - token.approve(address(this), 1e18); - - token.transferFrom(from, address(0xBEEF), 1e18); - } - - function testFailPermitBadNonce() public { - uint256 privateKey = 0xBEEF; - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 1, block.timestamp)) - ) - ) - ); - - token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); - } - - function testFailPermitBadDeadline() public { - uint256 privateKey = 0xBEEF; - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) - ) - ) - ); - - token.permit(owner, address(0xCAFE), 1e18, block.timestamp + 1, v, r, s); - } - - function testFailPermitPastDeadline() public { - uint256 oldTimestamp = block.timestamp; - uint256 privateKey = 0xBEEF; - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, oldTimestamp)) - ) - ) - ); - - vm.warp(block.timestamp + 1); - token.permit(owner, address(0xCAFE), 1e18, oldTimestamp, v, r, s); - } - - function testFailPermitReplay() public { - uint256 privateKey = 0xBEEF; - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) - ) - ) - ); - - token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); - token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); - } - - function testMetadata(string calldata name, string calldata symbol, uint8 decimals) public { - Token_ERC20 tkn = new Token_ERC20(name, symbol, decimals); - assertEq(tkn.name(), name); - assertEq(tkn.symbol(), symbol); - assertEq(tkn.decimals(), decimals); - } - - function testMint(address from, uint256 amount) public { - token.mint(from, amount); - - assertEq(token.totalSupply(), amount); - assertEq(token.balanceOf(from), amount); - } - - function testBurn(address from, uint256 mintAmount, uint256 burnAmount) public { - burnAmount = bound(burnAmount, 0, mintAmount); - - token.mint(from, mintAmount); - token.burn(from, burnAmount); - - assertEq(token.totalSupply(), mintAmount - burnAmount); - assertEq(token.balanceOf(from), mintAmount - burnAmount); - } - - function testApprove(address to, uint256 amount) public { - assertTrue(token.approve(to, amount)); - - assertEq(token.allowance(address(this), to), amount); - } - - function testTransfer(address from, uint256 amount) public { - token.mint(address(this), amount); - - assertTrue(token.transfer(from, amount)); - assertEq(token.totalSupply(), amount); - - if (address(this) == from) { - assertEq(token.balanceOf(address(this)), amount); - } else { - assertEq(token.balanceOf(address(this)), 0); - assertEq(token.balanceOf(from), amount); - } - } - - function testTransferFrom(address to, uint256 approval, uint256 amount) public { - amount = bound(amount, 0, approval); - - address from = address(0xABCD); - - token.mint(from, amount); - - vm.prank(from); - token.approve(address(this), approval); - - assertTrue(token.transferFrom(from, to, amount)); - assertEq(token.totalSupply(), amount); - - uint256 app = from == address(this) || approval == type(uint256).max ? approval : approval - amount; - assertEq(token.allowance(from, address(this)), app); - - if (from == to) { - assertEq(token.balanceOf(from), amount); - } else { - assertEq(token.balanceOf(from), 0); - assertEq(token.balanceOf(to), amount); - } - } - - function testPermit(uint248 privKey, address to, uint256 amount, uint256 deadline) public { - uint256 privateKey = privKey; - if (deadline < block.timestamp) deadline = block.timestamp; - if (privateKey == 0) privateKey = 1; - - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) - ) - ) - ); - - token.permit(owner, to, amount, deadline, v, r, s); - - assertEq(token.allowance(owner, to), amount); - assertEq(token.nonces(owner), 1); - } - - function testFailBurnInsufficientBalance(address to, uint256 mintAmount, uint256 burnAmount) public { - burnAmount = bound(burnAmount, mintAmount + 1, type(uint256).max); - - token.mint(to, mintAmount); - token.burn(to, burnAmount); - } - - function testFailTransferInsufficientBalance(address to, uint256 mintAmount, uint256 sendAmount) public { - sendAmount = bound(sendAmount, mintAmount + 1, type(uint256).max); - - token.mint(address(this), mintAmount); - token.transfer(to, sendAmount); - } - - function testFailTransferFromInsufficientAllowance(address to, uint256 approval, uint256 amount) public { - amount = bound(amount, approval + 1, type(uint256).max); - - address from = address(0xABCD); - - token.mint(from, amount); - - vm.prank(from); - token.approve(address(this), approval); - - token.transferFrom(from, to, amount); - } - - function testFailTransferFromInsufficientBalance(address to, uint256 mintAmount, uint256 sendAmount) public { - sendAmount = bound(sendAmount, mintAmount + 1, type(uint256).max); - - address from = address(0xABCD); - - token.mint(from, mintAmount); - - vm.prank(from); - token.approve(address(this), sendAmount); - - token.transferFrom(from, to, sendAmount); - } - - function testFailPermitBadNonce(uint256 privateKey, address to, uint256 amount, uint256 deadline, uint256 nonce) - public - { - if (deadline < block.timestamp) deadline = block.timestamp; - if (privateKey == 0) privateKey = 1; - if (nonce == 0) nonce = 1; - - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, nonce, deadline)) - ) - ) - ); - - token.permit(owner, to, amount, deadline, v, r, s); - } - - function testFailPermitBadDeadline(uint256 privateKey, address to, uint256 amount, uint256 deadline) public { - if (deadline < block.timestamp) deadline = block.timestamp; - if (privateKey == 0) privateKey = 1; - - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) - ) - ) - ); - - token.permit(owner, to, amount, deadline + 1, v, r, s); - } - - function testFailPermitPastDeadline(uint256 privateKey, address to, uint256 amount, uint256 deadline) public { - deadline = bound(deadline, 0, block.timestamp - 1); - if (privateKey == 0) privateKey = 1; - - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) - ) - ) - ); - - token.permit(owner, to, amount, deadline, v, r, s); - } - - function testFailPermitReplay(uint256 privateKey, address to, uint256 amount, uint256 deadline) public { - if (deadline < block.timestamp) deadline = block.timestamp; - if (privateKey == 0) privateKey = 1; - - address owner = vm.addr(privateKey); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - privateKey, - keccak256( - abi.encodePacked( - "\x19\x01", - token.DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) - ) - ) - ); - - token.permit(owner, to, amount, deadline, v, r, s); - token.permit(owner, to, amount, deadline, v, r, s); - } -} diff --git a/lib_forge_std/test/mocks/MockERC721.t.sol b/lib_forge_std/test/mocks/MockERC721.t.sol deleted file mode 100644 index f986d79..0000000 --- a/lib_forge_std/test/mocks/MockERC721.t.sol +++ /dev/null @@ -1,721 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.7.0 <0.9.0; - -import {MockERC721, IERC721TokenReceiver} from "../../src/mocks/MockERC721.sol"; -import {StdCheats} from "../../src/StdCheats.sol"; -import {Test} from "../../src/Test.sol"; - -contract ERC721Recipient is IERC721TokenReceiver { - address public operator; - address public from; - uint256 public id; - bytes public data; - - function onERC721Received(address _operator, address _from, uint256 _id, bytes calldata _data) - public - virtual - override - returns (bytes4) - { - operator = _operator; - from = _from; - id = _id; - data = _data; - - return IERC721TokenReceiver.onERC721Received.selector; - } -} - -contract RevertingERC721Recipient is IERC721TokenReceiver { - function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { - revert(string(abi.encodePacked(IERC721TokenReceiver.onERC721Received.selector))); - } -} - -contract WrongReturnDataERC721Recipient is IERC721TokenReceiver { - function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) { - return 0xCAFEBEEF; - } -} - -contract NonERC721Recipient {} - -contract Token_ERC721 is MockERC721 { - constructor(string memory _name, string memory _symbol) { - initialize(_name, _symbol); - } - - function tokenURI(uint256) public pure virtual override returns (string memory) {} - - function mint(address to, uint256 tokenId) public virtual { - _mint(to, tokenId); - } - - function burn(uint256 tokenId) public virtual { - _burn(tokenId); - } - - function safeMint(address to, uint256 tokenId) public virtual { - _safeMint(to, tokenId); - } - - function safeMint(address to, uint256 tokenId, bytes memory data) public virtual { - _safeMint(to, tokenId, data); - } -} - -contract MockERC721Test is StdCheats, Test { - Token_ERC721 token; - - function setUp() public { - token = new Token_ERC721("Token", "TKN"); - } - - function invariantMetadata() public view { - assertEq(token.name(), "Token"); - assertEq(token.symbol(), "TKN"); - } - - function testMint() public { - token.mint(address(0xBEEF), 1337); - - assertEq(token.balanceOf(address(0xBEEF)), 1); - assertEq(token.ownerOf(1337), address(0xBEEF)); - } - - function testBurn() public { - token.mint(address(0xBEEF), 1337); - token.burn(1337); - - assertEq(token.balanceOf(address(0xBEEF)), 0); - - vm.expectRevert("NOT_MINTED"); - token.ownerOf(1337); - } - - function testApprove() public { - token.mint(address(this), 1337); - - token.approve(address(0xBEEF), 1337); - - assertEq(token.getApproved(1337), address(0xBEEF)); - } - - function testApproveBurn() public { - token.mint(address(this), 1337); - - token.approve(address(0xBEEF), 1337); - - token.burn(1337); - - assertEq(token.balanceOf(address(this)), 0); - assertEq(token.getApproved(1337), address(0)); - - vm.expectRevert("NOT_MINTED"); - token.ownerOf(1337); - } - - function testApproveAll() public { - token.setApprovalForAll(address(0xBEEF), true); - - assertTrue(token.isApprovedForAll(address(this), address(0xBEEF))); - } - - function testTransferFrom() public { - address from = address(0xABCD); - - token.mint(from, 1337); - - vm.prank(from); - token.approve(address(this), 1337); - - token.transferFrom(from, address(0xBEEF), 1337); - - assertEq(token.getApproved(1337), address(0)); - assertEq(token.ownerOf(1337), address(0xBEEF)); - assertEq(token.balanceOf(address(0xBEEF)), 1); - assertEq(token.balanceOf(from), 0); - } - - function testTransferFromSelf() public { - token.mint(address(this), 1337); - - token.transferFrom(address(this), address(0xBEEF), 1337); - - assertEq(token.getApproved(1337), address(0)); - assertEq(token.ownerOf(1337), address(0xBEEF)); - assertEq(token.balanceOf(address(0xBEEF)), 1); - assertEq(token.balanceOf(address(this)), 0); - } - - function testTransferFromApproveAll() public { - address from = address(0xABCD); - - token.mint(from, 1337); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.transferFrom(from, address(0xBEEF), 1337); - - assertEq(token.getApproved(1337), address(0)); - assertEq(token.ownerOf(1337), address(0xBEEF)); - assertEq(token.balanceOf(address(0xBEEF)), 1); - assertEq(token.balanceOf(from), 0); - } - - function testSafeTransferFromToEOA() public { - address from = address(0xABCD); - - token.mint(from, 1337); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.safeTransferFrom(from, address(0xBEEF), 1337); - - assertEq(token.getApproved(1337), address(0)); - assertEq(token.ownerOf(1337), address(0xBEEF)); - assertEq(token.balanceOf(address(0xBEEF)), 1); - assertEq(token.balanceOf(from), 0); - } - - function testSafeTransferFromToERC721Recipient() public { - address from = address(0xABCD); - ERC721Recipient recipient = new ERC721Recipient(); - - token.mint(from, 1337); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.safeTransferFrom(from, address(recipient), 1337); - - assertEq(token.getApproved(1337), address(0)); - assertEq(token.ownerOf(1337), address(recipient)); - assertEq(token.balanceOf(address(recipient)), 1); - assertEq(token.balanceOf(from), 0); - - assertEq(recipient.operator(), address(this)); - assertEq(recipient.from(), from); - assertEq(recipient.id(), 1337); - assertEq(recipient.data(), ""); - } - - function testSafeTransferFromToERC721RecipientWithData() public { - address from = address(0xABCD); - ERC721Recipient recipient = new ERC721Recipient(); - - token.mint(from, 1337); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.safeTransferFrom(from, address(recipient), 1337, "testing 123"); - - assertEq(token.getApproved(1337), address(0)); - assertEq(token.ownerOf(1337), address(recipient)); - assertEq(token.balanceOf(address(recipient)), 1); - assertEq(token.balanceOf(from), 0); - - assertEq(recipient.operator(), address(this)); - assertEq(recipient.from(), from); - assertEq(recipient.id(), 1337); - assertEq(recipient.data(), "testing 123"); - } - - function testSafeMintToEOA() public { - token.safeMint(address(0xBEEF), 1337); - - assertEq(token.ownerOf(1337), address(address(0xBEEF))); - assertEq(token.balanceOf(address(address(0xBEEF))), 1); - } - - function testSafeMintToERC721Recipient() public { - ERC721Recipient to = new ERC721Recipient(); - - token.safeMint(address(to), 1337); - - assertEq(token.ownerOf(1337), address(to)); - assertEq(token.balanceOf(address(to)), 1); - - assertEq(to.operator(), address(this)); - assertEq(to.from(), address(0)); - assertEq(to.id(), 1337); - assertEq(to.data(), ""); - } - - function testSafeMintToERC721RecipientWithData() public { - ERC721Recipient to = new ERC721Recipient(); - - token.safeMint(address(to), 1337, "testing 123"); - - assertEq(token.ownerOf(1337), address(to)); - assertEq(token.balanceOf(address(to)), 1); - - assertEq(to.operator(), address(this)); - assertEq(to.from(), address(0)); - assertEq(to.id(), 1337); - assertEq(to.data(), "testing 123"); - } - - function testFailMintToZero() public { - token.mint(address(0), 1337); - } - - function testFailDoubleMint() public { - token.mint(address(0xBEEF), 1337); - token.mint(address(0xBEEF), 1337); - } - - function testFailBurnUnMinted() public { - token.burn(1337); - } - - function testFailDoubleBurn() public { - token.mint(address(0xBEEF), 1337); - - token.burn(1337); - token.burn(1337); - } - - function testFailApproveUnMinted() public { - token.approve(address(0xBEEF), 1337); - } - - function testFailApproveUnAuthorized() public { - token.mint(address(0xCAFE), 1337); - - token.approve(address(0xBEEF), 1337); - } - - function testFailTransferFromUnOwned() public { - token.transferFrom(address(0xFEED), address(0xBEEF), 1337); - } - - function testFailTransferFromWrongFrom() public { - token.mint(address(0xCAFE), 1337); - - token.transferFrom(address(0xFEED), address(0xBEEF), 1337); - } - - function testFailTransferFromToZero() public { - token.mint(address(this), 1337); - - token.transferFrom(address(this), address(0), 1337); - } - - function testFailTransferFromNotOwner() public { - token.mint(address(0xFEED), 1337); - - token.transferFrom(address(0xFEED), address(0xBEEF), 1337); - } - - function testFailSafeTransferFromToNonERC721Recipient() public { - token.mint(address(this), 1337); - - token.safeTransferFrom(address(this), address(new NonERC721Recipient()), 1337); - } - - function testFailSafeTransferFromToNonERC721RecipientWithData() public { - token.mint(address(this), 1337); - - token.safeTransferFrom(address(this), address(new NonERC721Recipient()), 1337, "testing 123"); - } - - function testFailSafeTransferFromToRevertingERC721Recipient() public { - token.mint(address(this), 1337); - - token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), 1337); - } - - function testFailSafeTransferFromToRevertingERC721RecipientWithData() public { - token.mint(address(this), 1337); - - token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), 1337, "testing 123"); - } - - function testFailSafeTransferFromToERC721RecipientWithWrongReturnData() public { - token.mint(address(this), 1337); - - token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), 1337); - } - - function testFailSafeTransferFromToERC721RecipientWithWrongReturnDataWithData() public { - token.mint(address(this), 1337); - - token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), 1337, "testing 123"); - } - - function testFailSafeMintToNonERC721Recipient() public { - token.safeMint(address(new NonERC721Recipient()), 1337); - } - - function testFailSafeMintToNonERC721RecipientWithData() public { - token.safeMint(address(new NonERC721Recipient()), 1337, "testing 123"); - } - - function testFailSafeMintToRevertingERC721Recipient() public { - token.safeMint(address(new RevertingERC721Recipient()), 1337); - } - - function testFailSafeMintToRevertingERC721RecipientWithData() public { - token.safeMint(address(new RevertingERC721Recipient()), 1337, "testing 123"); - } - - function testFailSafeMintToERC721RecipientWithWrongReturnData() public { - token.safeMint(address(new WrongReturnDataERC721Recipient()), 1337); - } - - function testFailSafeMintToERC721RecipientWithWrongReturnDataWithData() public { - token.safeMint(address(new WrongReturnDataERC721Recipient()), 1337, "testing 123"); - } - - function testFailBalanceOfZeroAddress() public view { - token.balanceOf(address(0)); - } - - function testFailOwnerOfUnminted() public view { - token.ownerOf(1337); - } - - function testMetadata(string memory name, string memory symbol) public { - MockERC721 tkn = new Token_ERC721(name, symbol); - - assertEq(tkn.name(), name); - assertEq(tkn.symbol(), symbol); - } - - function testMint(address to, uint256 id) public { - if (to == address(0)) to = address(0xBEEF); - - token.mint(to, id); - - assertEq(token.balanceOf(to), 1); - assertEq(token.ownerOf(id), to); - } - - function testBurn(address to, uint256 id) public { - if (to == address(0)) to = address(0xBEEF); - - token.mint(to, id); - token.burn(id); - - assertEq(token.balanceOf(to), 0); - - vm.expectRevert("NOT_MINTED"); - token.ownerOf(id); - } - - function testApprove(address to, uint256 id) public { - if (to == address(0)) to = address(0xBEEF); - - token.mint(address(this), id); - - token.approve(to, id); - - assertEq(token.getApproved(id), to); - } - - function testApproveBurn(address to, uint256 id) public { - token.mint(address(this), id); - - token.approve(address(to), id); - - token.burn(id); - - assertEq(token.balanceOf(address(this)), 0); - assertEq(token.getApproved(id), address(0)); - - vm.expectRevert("NOT_MINTED"); - token.ownerOf(id); - } - - function testApproveAll(address to, bool approved) public { - token.setApprovalForAll(to, approved); - - assertEq(token.isApprovedForAll(address(this), to), approved); - } - - function testTransferFrom(uint256 id, address to) public { - address from = address(0xABCD); - - if (to == address(0) || to == from) to = address(0xBEEF); - - token.mint(from, id); - - vm.prank(from); - token.approve(address(this), id); - - token.transferFrom(from, to, id); - - assertEq(token.getApproved(id), address(0)); - assertEq(token.ownerOf(id), to); - assertEq(token.balanceOf(to), 1); - assertEq(token.balanceOf(from), 0); - } - - function testTransferFromSelf(uint256 id, address to) public { - if (to == address(0) || to == address(this)) to = address(0xBEEF); - - token.mint(address(this), id); - - token.transferFrom(address(this), to, id); - - assertEq(token.getApproved(id), address(0)); - assertEq(token.ownerOf(id), to); - assertEq(token.balanceOf(to), 1); - assertEq(token.balanceOf(address(this)), 0); - } - - function testTransferFromApproveAll(uint256 id, address to) public { - address from = address(0xABCD); - - if (to == address(0) || to == from) to = address(0xBEEF); - - token.mint(from, id); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.transferFrom(from, to, id); - - assertEq(token.getApproved(id), address(0)); - assertEq(token.ownerOf(id), to); - assertEq(token.balanceOf(to), 1); - assertEq(token.balanceOf(from), 0); - } - - function testSafeTransferFromToEOA(uint256 id, address to) public { - address from = address(0xABCD); - - if (to == address(0) || to == from) to = address(0xBEEF); - - if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; - - token.mint(from, id); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.safeTransferFrom(from, to, id); - - assertEq(token.getApproved(id), address(0)); - assertEq(token.ownerOf(id), to); - assertEq(token.balanceOf(to), 1); - assertEq(token.balanceOf(from), 0); - } - - function testSafeTransferFromToERC721Recipient(uint256 id) public { - address from = address(0xABCD); - - ERC721Recipient recipient = new ERC721Recipient(); - - token.mint(from, id); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.safeTransferFrom(from, address(recipient), id); - - assertEq(token.getApproved(id), address(0)); - assertEq(token.ownerOf(id), address(recipient)); - assertEq(token.balanceOf(address(recipient)), 1); - assertEq(token.balanceOf(from), 0); - - assertEq(recipient.operator(), address(this)); - assertEq(recipient.from(), from); - assertEq(recipient.id(), id); - assertEq(recipient.data(), ""); - } - - function testSafeTransferFromToERC721RecipientWithData(uint256 id, bytes calldata data) public { - address from = address(0xABCD); - ERC721Recipient recipient = new ERC721Recipient(); - - token.mint(from, id); - - vm.prank(from); - token.setApprovalForAll(address(this), true); - - token.safeTransferFrom(from, address(recipient), id, data); - - assertEq(token.getApproved(id), address(0)); - assertEq(token.ownerOf(id), address(recipient)); - assertEq(token.balanceOf(address(recipient)), 1); - assertEq(token.balanceOf(from), 0); - - assertEq(recipient.operator(), address(this)); - assertEq(recipient.from(), from); - assertEq(recipient.id(), id); - assertEq(recipient.data(), data); - } - - function testSafeMintToEOA(uint256 id, address to) public { - if (to == address(0)) to = address(0xBEEF); - - if (uint256(uint160(to)) <= 18 || to.code.length > 0) return; - - token.safeMint(to, id); - - assertEq(token.ownerOf(id), address(to)); - assertEq(token.balanceOf(address(to)), 1); - } - - function testSafeMintToERC721Recipient(uint256 id) public { - ERC721Recipient to = new ERC721Recipient(); - - token.safeMint(address(to), id); - - assertEq(token.ownerOf(id), address(to)); - assertEq(token.balanceOf(address(to)), 1); - - assertEq(to.operator(), address(this)); - assertEq(to.from(), address(0)); - assertEq(to.id(), id); - assertEq(to.data(), ""); - } - - function testSafeMintToERC721RecipientWithData(uint256 id, bytes calldata data) public { - ERC721Recipient to = new ERC721Recipient(); - - token.safeMint(address(to), id, data); - - assertEq(token.ownerOf(id), address(to)); - assertEq(token.balanceOf(address(to)), 1); - - assertEq(to.operator(), address(this)); - assertEq(to.from(), address(0)); - assertEq(to.id(), id); - assertEq(to.data(), data); - } - - function testFailMintToZero(uint256 id) public { - token.mint(address(0), id); - } - - function testFailDoubleMint(uint256 id, address to) public { - if (to == address(0)) to = address(0xBEEF); - - token.mint(to, id); - token.mint(to, id); - } - - function testFailBurnUnMinted(uint256 id) public { - token.burn(id); - } - - function testFailDoubleBurn(uint256 id, address to) public { - if (to == address(0)) to = address(0xBEEF); - - token.mint(to, id); - - token.burn(id); - token.burn(id); - } - - function testFailApproveUnMinted(uint256 id, address to) public { - token.approve(to, id); - } - - function testFailApproveUnAuthorized(address owner, uint256 id, address to) public { - if (owner == address(0) || owner == address(this)) owner = address(0xBEEF); - - token.mint(owner, id); - - token.approve(to, id); - } - - function testFailTransferFromUnOwned(address from, address to, uint256 id) public { - token.transferFrom(from, to, id); - } - - function testFailTransferFromWrongFrom(address owner, address from, address to, uint256 id) public { - if (owner == address(0)) to = address(0xBEEF); - if (from == owner) revert(); - - token.mint(owner, id); - - token.transferFrom(from, to, id); - } - - function testFailTransferFromToZero(uint256 id) public { - token.mint(address(this), id); - - token.transferFrom(address(this), address(0), id); - } - - function testFailTransferFromNotOwner(address from, address to, uint256 id) public { - if (from == address(this)) from = address(0xBEEF); - - token.mint(from, id); - - token.transferFrom(from, to, id); - } - - function testFailSafeTransferFromToNonERC721Recipient(uint256 id) public { - token.mint(address(this), id); - - token.safeTransferFrom(address(this), address(new NonERC721Recipient()), id); - } - - function testFailSafeTransferFromToNonERC721RecipientWithData(uint256 id, bytes calldata data) public { - token.mint(address(this), id); - - token.safeTransferFrom(address(this), address(new NonERC721Recipient()), id, data); - } - - function testFailSafeTransferFromToRevertingERC721Recipient(uint256 id) public { - token.mint(address(this), id); - - token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), id); - } - - function testFailSafeTransferFromToRevertingERC721RecipientWithData(uint256 id, bytes calldata data) public { - token.mint(address(this), id); - - token.safeTransferFrom(address(this), address(new RevertingERC721Recipient()), id, data); - } - - function testFailSafeTransferFromToERC721RecipientWithWrongReturnData(uint256 id) public { - token.mint(address(this), id); - - token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), id); - } - - function testFailSafeTransferFromToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes calldata data) - public - { - token.mint(address(this), id); - - token.safeTransferFrom(address(this), address(new WrongReturnDataERC721Recipient()), id, data); - } - - function testFailSafeMintToNonERC721Recipient(uint256 id) public { - token.safeMint(address(new NonERC721Recipient()), id); - } - - function testFailSafeMintToNonERC721RecipientWithData(uint256 id, bytes calldata data) public { - token.safeMint(address(new NonERC721Recipient()), id, data); - } - - function testFailSafeMintToRevertingERC721Recipient(uint256 id) public { - token.safeMint(address(new RevertingERC721Recipient()), id); - } - - function testFailSafeMintToRevertingERC721RecipientWithData(uint256 id, bytes calldata data) public { - token.safeMint(address(new RevertingERC721Recipient()), id, data); - } - - function testFailSafeMintToERC721RecipientWithWrongReturnData(uint256 id) public { - token.safeMint(address(new WrongReturnDataERC721Recipient()), id); - } - - function testFailSafeMintToERC721RecipientWithWrongReturnDataWithData(uint256 id, bytes calldata data) public { - token.safeMint(address(new WrongReturnDataERC721Recipient()), id, data); - } - - function testFailOwnerOfUnminted(uint256 id) public view { - token.ownerOf(id); - } -} diff --git a/lib_openzeppelin_contracts/.changeset/chilled-walls-develop.md b/lib_openzeppelin_contracts/.changeset/chilled-walls-develop.md deleted file mode 100644 index 4108feb..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/cold-cheetahs-check.md b/lib_openzeppelin_contracts/.changeset/cold-cheetahs-check.md deleted file mode 100644 index 0697dcd..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/config.json b/lib_openzeppelin_contracts/.changeset/config.json deleted file mode 100644 index 66794fa..0000000 --- a/lib_openzeppelin_contracts/.changeset/config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", - "changelog": [ - "@changesets/changelog-github", - { - "repo": "OpenZeppelin/openzeppelin-contracts" - } - ], - "commit": false, - "access": "public", - "baseBranch": "master" -} diff --git a/lib_openzeppelin_contracts/.changeset/cool-mangos-compare.md b/lib_openzeppelin_contracts/.changeset/cool-mangos-compare.md deleted file mode 100644 index 470ee08..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/dirty-cobras-smile.md b/lib_openzeppelin_contracts/.changeset/dirty-cobras-smile.md deleted file mode 100644 index d71194c..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/eleven-planets-relax.md b/lib_openzeppelin_contracts/.changeset/eleven-planets-relax.md deleted file mode 100644 index a1f1bbf..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/flat-turtles-repeat.md b/lib_openzeppelin_contracts/.changeset/flat-turtles-repeat.md deleted file mode 100644 index 6b62720..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/fluffy-steaks-exist.md b/lib_openzeppelin_contracts/.changeset/fluffy-steaks-exist.md deleted file mode 100644 index b625e24..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/friendly-nails-push.md b/lib_openzeppelin_contracts/.changeset/friendly-nails-push.md deleted file mode 100644 index 157bf05..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/gentle-bulldogs-turn.md b/lib_openzeppelin_contracts/.changeset/gentle-bulldogs-turn.md deleted file mode 100644 index 12bc87a..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/gorgeous-badgers-vanish.md b/lib_openzeppelin_contracts/.changeset/gorgeous-badgers-vanish.md deleted file mode 100644 index ce75ed6..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/heavy-baboons-give.md b/lib_openzeppelin_contracts/.changeset/heavy-baboons-give.md deleted file mode 100644 index ebdde90..0000000 --- a/lib_openzeppelin_contracts/.changeset/heavy-baboons-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Packing`: Added a new utility for packing and unpacking multiple values into a single bytes32. Includes initial support for packing two `uint128` in an `Uint128x2` type. diff --git a/lib_openzeppelin_contracts/.changeset/kind-planets-cough.md b/lib_openzeppelin_contracts/.changeset/kind-planets-cough.md deleted file mode 100644 index 988e24c..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/lucky-crews-eat.md b/lib_openzeppelin_contracts/.changeset/lucky-crews-eat.md deleted file mode 100644 index 48592b5..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/nervous-pans-grow.md b/lib_openzeppelin_contracts/.changeset/nervous-pans-grow.md deleted file mode 100644 index b86a075..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/nice-paws-pull.md b/lib_openzeppelin_contracts/.changeset/nice-paws-pull.md deleted file mode 100644 index 11f48d5..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/odd-files-protect.md b/lib_openzeppelin_contracts/.changeset/odd-files-protect.md deleted file mode 100644 index 8b334ac..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/poor-chefs-cheat.md b/lib_openzeppelin_contracts/.changeset/poor-chefs-cheat.md deleted file mode 100644 index 39db3d5..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/shiny-poets-whisper.md b/lib_openzeppelin_contracts/.changeset/shiny-poets-whisper.md deleted file mode 100644 index 9249703..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/silver-swans-promise.md b/lib_openzeppelin_contracts/.changeset/silver-swans-promise.md deleted file mode 100644 index 1d2ff2e..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/smart-bugs-switch.md b/lib_openzeppelin_contracts/.changeset/smart-bugs-switch.md deleted file mode 100644 index 8a001ae..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/spotty-falcons-explain.md b/lib_openzeppelin_contracts/.changeset/spotty-falcons-explain.md deleted file mode 100644 index 28cb951..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/strong-singers-talk.md b/lib_openzeppelin_contracts/.changeset/strong-singers-talk.md deleted file mode 100644 index 7897980..0000000 --- a/lib_openzeppelin_contracts/.changeset/strong-singers-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`Errors`: New library of common custom errors. diff --git a/lib_openzeppelin_contracts/.changeset/thick-pumpkins-report.md b/lib_openzeppelin_contracts/.changeset/thick-pumpkins-report.md deleted file mode 100644 index f17a208..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/thin-walls-drop.md b/lib_openzeppelin_contracts/.changeset/thin-walls-drop.md deleted file mode 100644 index 8026002..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/twenty-feet-grin.md b/lib_openzeppelin_contracts/.changeset/twenty-feet-grin.md deleted file mode 100644 index 69b4fe6..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/violet-moons-tell.md b/lib_openzeppelin_contracts/.changeset/violet-moons-tell.md deleted file mode 100644 index be215e1..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/warm-sheep-cover.md b/lib_openzeppelin_contracts/.changeset/warm-sheep-cover.md deleted file mode 100644 index f0a2eba..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/wise-bobcats-speak.md b/lib_openzeppelin_contracts/.changeset/wise-bobcats-speak.md deleted file mode 100644 index 6ecd969..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/witty-chicken-smile.md b/lib_openzeppelin_contracts/.changeset/witty-chicken-smile.md deleted file mode 100644 index 6fae3e7..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.changeset/yellow-deers-walk.md b/lib_openzeppelin_contracts/.changeset/yellow-deers-walk.md deleted file mode 100644 index ad370b3..0000000 --- a/lib_openzeppelin_contracts/.changeset/yellow-deers-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'openzeppelin-solidity': minor ---- - -`EnumerableMap`: add `UintToBytes32Map`, `AddressToAddressMap`, `AddressToBytes32Map` and `Bytes32ToAddressMap`. diff --git a/lib_openzeppelin_contracts/.changeset/yellow-moles-hammer.md b/lib_openzeppelin_contracts/.changeset/yellow-moles-hammer.md deleted file mode 100644 index b13971a..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.codecov.yml b/lib_openzeppelin_contracts/.codecov.yml deleted file mode 100644 index 9455306..0000000 --- a/lib_openzeppelin_contracts/.codecov.yml +++ /dev/null @@ -1,12 +0,0 @@ -comment: off -github_checks: - annotations: false -coverage: - status: - patch: - default: - target: 95% - only_pulls: true - project: - default: - threshold: 1% diff --git a/lib_openzeppelin_contracts/.editorconfig b/lib_openzeppelin_contracts/.editorconfig deleted file mode 100644 index f162e8d..0000000 --- a/lib_openzeppelin_contracts/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = false -max_line_length = 120 - -[*.sol] -indent_size = 4 - -[*.js] -indent_size = 2 - -[*.{adoc,md}] -max_line_length = 0 diff --git a/lib_openzeppelin_contracts/.eslintrc b/lib_openzeppelin_contracts/.eslintrc deleted file mode 100644 index a5418c5..0000000 --- a/lib_openzeppelin_contracts/.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/lib_openzeppelin_contracts/.mocharc.js b/lib_openzeppelin_contracts/.mocharc.js deleted file mode 100644 index 920662d..0000000 --- a/lib_openzeppelin_contracts/.mocharc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - require: 'hardhat/register', - timeout: 4000, -}; diff --git a/lib_openzeppelin_contracts/.prettierrc b/lib_openzeppelin_contracts/.prettierrc deleted file mode 100644 index 39c004c..0000000 --- a/lib_openzeppelin_contracts/.prettierrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "printWidth": 120, - "singleQuote": true, - "trailingComma": "all", - "arrowParens": "avoid", - "overrides": [ - { - "files": "*.sol", - "options": { - "singleQuote": false - } - } - ], - "plugins": ["prettier-plugin-solidity"] -} diff --git a/lib_openzeppelin_contracts/.solcover.js b/lib_openzeppelin_contracts/.solcover.js deleted file mode 100644 index e0dea5e..0000000 --- a/lib_openzeppelin_contracts/.solcover.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - norpc: true, - testCommand: 'npm test', - compileCommand: 'npm run compile', - skipFiles: ['mocks'], - providerOptions: { - default_balance_ether: '10000000000000000000000000', - }, - mocha: { - fgrep: '[skip-on-coverage]', - invert: true, - }, -}; diff --git a/lib_openzeppelin_contracts/CHANGELOG.md b/lib_openzeppelin_contracts/CHANGELOG.md deleted file mode 100644 index b607313..0000000 --- a/lib_openzeppelin_contracts/CHANGELOG.md +++ /dev/null @@ -1,1033 +0,0 @@ -# Changelog - -### 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. - -### Custom error changes - -This version comes with changes to the custom error identifiers. Contracts previously depending on the following errors should be replaced accordingly: - -- Replace `Address.FailedInnerCall` with `Errors.FailedCall` -- Replace `Address.AddressInsufficientBalance` with `Errors.InsufficientBalance` -- Replace `Clones.Create2InsufficientBalance` with `Errors.InsufficientBalance` -- Replace `Clones.ERC1167FailedCreateClone` with `Errors.FailedDeployment` -- Replace `Clones.Create2FailedDeployment` with `Errors.FailedDeployment` - -## 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)) - -## 5.0.1 (2023-12-07) - -- `ERC2771Context` and `Context`: Introduce a `_contextPrefixLength()` getter, used to trim extra information appended to `msg.data`. -- `Multicall`: Make aware of non-canonical context (i.e. `msg.sender` is not `_msgSender()`), allowing compatibility with `ERC2771Context`. - -## 5.0.0 (2023-10-05) - -### Additions Summary - -The following contracts and libraries were added: - -- `AccessManager`: A consolidated system for managing access control in complex systems. - - `AccessManaged`: A module for connecting a contract to an authority in charge of its access control. - - `GovernorTimelockAccess`: An adapter for time-locking governance proposals using an `AccessManager`. - - `AuthorityUtils`: A library of utilities for interacting with authority contracts. -- `GovernorStorage`: A Governor module that stores proposal details in storage. -- `ERC2771Forwarder`: An ERC2771 forwarder for meta transactions. -- `ERC1967Utils`: A library with ERC1967 events, errors and getters. -- `Nonces`: An abstraction for managing account nonces. -- `MessageHashUtils`: A library for producing digests for ECDSA operations. -- `Time`: A library with helpers for manipulating time-related objects. - -### Removals Summary - -The following contracts, libraries, and functions were removed: - -- `Address.isContract` (because of its ambiguous nature and potential for misuse) -- `Checkpoints.History` -- `Counters` -- `ERC20Snapshot` -- `ERC20VotesComp` -- `ERC165Storage` (in favor of inheritance based approach) -- `ERC777` -- `ERC1820Implementer` -- `GovernorVotesComp` -- `GovernorProposalThreshold` (deprecated since 4.4) -- `PaymentSplitter` -- `PullPayment` -- `SafeMath` -- `SignedSafeMath` -- `Timers` -- `TokenTimelock` (in favor of `VestingWallet`) -- All escrow contracts (`Escrow`, `ConditionalEscrow` and `RefundEscrow`) -- All cross-chain contracts, including `AccessControlCrossChain` and all the vendored bridge interfaces -- All presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/) - -These removals were implemented in the following PRs: [#3637](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3637), [#3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880), [#3945](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3945), [#4258](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4258), [#4276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4276), [#4289](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4289) - -### Changes by category - -#### General - -- Replaced revert strings and require statements with custom errors. ([#4261](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4261)) -- Bumped minimum compiler version required to 0.8.20 ([#4288](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4288), [#4489](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4489)) -- Use of `abi.encodeCall` in place of `abi.encodeWithSelector` and `abi.encodeWithSignature` for improved type-checking of parameters ([#4293](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4293)) -- Replaced some uses of `abi.encodePacked` with clearer alternatives (e.g. `bytes.concat`, `string.concat`). ([#4504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4504)) ([#4296](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4296)) -- Overrides are now used internally for a number of functions that were previously hardcoded to their default implementation in certain locations: `ERC1155Supply.totalSupply`, `ERC721.ownerOf`, `ERC721.balanceOf` and `ERC721.totalSupply` in `ERC721Enumerable`, `ERC20.totalSupply` in `ERC20FlashMint`, and `ERC1967._getImplementation` in `ERC1967Proxy`. ([#4299](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4299)) -- Removed the `override` specifier from functions that only override a single interface function. ([#4315](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4315)) -- Switched to using explicit Solidity import statements. Some previously available symbols may now have to be separately imported. ([#4399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4399)) -- `Governor`, `Initializable`, and `UUPSUpgradeable`: Use internal functions in modifiers to optimize bytecode size. ([#4472](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4472)) -- Upgradeable contracts now use namespaced storage (EIP-7201). ([#4534](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4534)) -- Upgradeable contracts no longer transpile interfaces and libraries. ([#4628](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4628)) - -#### Access - -- `Ownable`: Added an `initialOwner` parameter to the constructor, making the ownership initialization explicit. ([#4267](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4267)) -- `Ownable`: Prevent using address(0) as the initial owner. ([#4531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4531)) -- `AccessControl`: Added a boolean return value to the internal `_grantRole` and `_revokeRole` functions indicating whether the role was granted or revoked. ([#4241](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4241)) -- `access`: Moved `AccessControl` extensions to a dedicated directory. ([#4359](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4359)) -- `AccessManager`: Added a new contract for managing access control of complex systems in a consolidated location. ([#4121](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4121)) -- `AccessManager`, `AccessManaged`, `GovernorTimelockAccess`: Ensure that calldata shorter than 4 bytes is not padded to 4 bytes. ([#4624](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4624)) -- `AccessManager`: Use named return parameters in functions that return multiple values. ([#4624](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4624)) -- `AccessManager`: Make `schedule` and `execute` more conservative when delay is 0. ([#4644](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4644)) - -#### Finance - -- `VestingWallet`: Fixed revert during 1 second time window when duration is 0. ([#4502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4502)) -- `VestingWallet`: Use `Ownable` instead of an immutable `beneficiary`. ([#4508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4508)) - -#### Governance - -- `Governor`: Optimized use of storage for proposal data ([#4268](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4268)) -- `Governor`: Added validation in ERC1155 and ERC721 receiver hooks to ensure Governor is the executor. ([#4314](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4314)) -- `Governor`: Refactored internals to implement common queuing logic in the core module of the Governor. Added `queue` and `_queueOperations` functions that act at different levels. Modules that implement queuing via timelocks are expected to override `_queueOperations` to implement the timelock-specific logic. Added `_executeOperations` as the equivalent for execution. ([#4360](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4360)) -- `Governor`: Added `voter` and `nonce` parameters in signed ballots, to avoid forging signatures for random addresses, prevent signature replay, and allow invalidating signatures. Add `voter` as a new parameter in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. ([#4378](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4378)) -- `Governor`: Added support for casting votes with ERC-1271 signatures by using a `bytes memory signature` instead of `r`, `s` and `v` arguments in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. ([#4418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4418)) -- `Governor`: Added a mechanism to restrict the address of the proposer using a suffix in the description. -- `GovernorStorage`: Added a new governor extension that stores the proposal details in storage, with an interface that operates on `proposalId`, as well as proposal enumerability. This replaces the old `GovernorCompatibilityBravo` module. ([#4360](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4360)) -- `GovernorTimelockAccess`: Added a module to connect a governor with an instance of `AccessManager`, allowing the governor to make calls that are delay-restricted by the manager using the normal `queue` workflow. ([#4523](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4523)) -- `GovernorTimelockControl`: Clean up timelock id on execution for gas refund. ([#4118](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4118)) -- `GovernorTimelockControl`: Added the Governor instance address as part of the TimelockController operation `salt` to avoid operation id collisions between governors using the same TimelockController. ([#4432](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4432)) -- `TimelockController`: Changed the role architecture to use `DEFAULT_ADMIN_ROLE` as the admin for all roles, instead of the bespoke `TIMELOCK_ADMIN_ROLE` that was used previously. This aligns with the general recommendation for `AccessControl` and makes the addition of new roles easier. Accordingly, the `admin` parameter and timelock will now be granted `DEFAULT_ADMIN_ROLE` instead of `TIMELOCK_ADMIN_ROLE`. ([#3799](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3799)) -- `TimelockController`: Added a state getter that returns an `OperationState` enum. ([#4358](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4358)) -- `Votes`: Use Trace208 for checkpoints. This enables EIP-6372 clock support for keys but reduces the max supported voting power to uint208. ([#4539](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4539)) - -#### Metatx - -- `ERC2771Forwarder`: Added `deadline` for expiring transactions, batching, and more secure handling of `msg.value`. ([#4346](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4346)) -- `ERC2771Context`: Return the forwarder address whenever the `msg.data` of a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes), as specified by ERC-2771. ([#4481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4481)) -- `ERC2771Context`: Prevent revert in `_msgData()` when a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes). Return the full calldata in that case. ([#4484](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4484)) - -#### Proxy - -- `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) -- `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) -- `ERC1967Utils`: Refactored the `ERC1967Upgrade` abstract contract as a library. ([#4325](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4325)) -- `TransparentUpgradeableProxy`: Admin is now stored in an immutable variable (set during construction) to avoid unnecessary storage reads on every proxy call. This removed the ability to ever change the admin. Transfer of the upgrade capability is exclusively handled through the ownership of the `ProxyAdmin`. ([#4354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4354)) -- Moved the logic to validate ERC-1822 during an upgrade from `ERC1967Utils` to `UUPSUpgradeable`. ([#4356](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4356)) -- `UUPSUpgradeable`, `TransparentUpgradeableProxy` and `ProxyAdmin`: Removed `upgradeTo` and `upgrade` functions, and made `upgradeToAndCall` and `upgradeAndCall` ignore the data argument if it is empty. It is no longer possible to invoke the receive function (or send value with empty data) along with an upgrade. ([#4382](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4382)) -- `BeaconProxy`: Reject value in initialization unless a payable function is explicitly invoked. ([#4382](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4382)) -- `Proxy`: Removed redundant `receive` function. ([#4434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4434)) -- `BeaconProxy`: Use an immutable variable to store the address of the beacon. It is no longer possible for a `BeaconProxy` to upgrade by changing to another beacon. ([#4435](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4435)) -- `Initializable`: Use the namespaced storage pattern to avoid putting critical variables in slot 0. Allow reinitializer versions greater than 256. ([#4460](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4460)) -- `Initializable`: Use intermediate variables to improve readability. ([#4576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4576)) - -#### Token - -- `ERC20`, `ERC721`, `ERC1155`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838), [#3876](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3876), [#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377)) -- `ERC20`: Removed `Approval` event previously emitted in `transferFrom` to indicate that part of the allowance was consumed. With this change, allowances are no longer reconstructible from events. See the code for guidelines on how to re-enable this event if needed. ([#4370](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4370)) -- `ERC20`: Removed the non-standard `increaseAllowance` and `decreaseAllowance` functions. ([#4585](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4585)) -- `ERC20Votes`: Changed internal vote accounting to reusable `Votes` module previously used by `ERC721Votes`. Removed implicit `ERC20Permit` inheritance. Note that the `DOMAIN_SEPARATOR` getter was previously guaranteed to be available for `ERC20Votes` contracts, but is no longer available unless `ERC20Permit` is explicitly used; ERC-5267 support is included in `ERC20Votes` with `EIP712` and is recommended as an alternative. ([#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816)) -- `SafeERC20`: Refactored `safeDecreaseAllowance` and `safeIncreaseAllowance` to support USDT-like tokens. ([#4260](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4260)) -- `SafeERC20`: Removed `safePermit` in favor of documentation-only `permit` recommendations. Based on recommendations from @trust1995 ([#4582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4582)) -- `ERC721`: `_approve` no longer allows approving the owner of the tokenId. ([#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/4377)) `_setApprovalForAll` no longer allows setting address(0) as an operator. ([#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377)) -- `ERC721`: Renamed `_requireMinted` to `_requireOwned` and added a return value with the current owner. Implemented `ownerOf` in terms of `_requireOwned`. ([#4566](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4566)) -- `ERC721Consecutive`: Added a `_firstConsecutiveId` internal function that can be overridden to change the id of the first token minted through `_mintConsecutive`. ([#4097](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4097)) -- `ERC721URIStorage`: Allow setting the token URI prior to minting. ([#4559](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4559)) -- `ERC721URIStorage`, `ERC721Royalty`: Stop resetting token-specific URI and royalties when burning. ([#4561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4561)) -- `ERC1155`: Optimized array allocation. ([#4196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4196)) -- `ERC1155`: Removed check for address zero in `balanceOf`. ([#4263](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4263)) -- `ERC1155`: Optimized array accesses by skipping bounds checking when unnecessary. ([#4300](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4300)) -- `ERC1155`: Bubble errors triggered in the `onERC1155Received` and `onERC1155BatchReceived` hooks. ([#4314](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4314)) -- `ERC1155Supply`: Added a `totalSupply()` function that returns the total amount of token circulating, this change will restrict the total tokens minted across all ids to 2\*\*256-1 . ([#3962](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3962)) -- `ERC1155Receiver`: Removed in favor of `ERC1155Holder`. ([#4450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4450)) - -#### Utils - -- `Address`: Removed the ability to customize error messages. A common custom error is always used if the underlying revert reason cannot be bubbled up. ([#4502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4502)) -- `Arrays`: Added `unsafeMemoryAccess` helpers to read from a memory array without checking the length. ([#4300](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4300)) -- `Arrays`: Optimized `findUpperBound` by removing redundant SLOAD. ([#4442](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4442)) -- `Checkpoints`: Library moved from `utils` to `utils/structs` ([#4275](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4275)) -- `DoubleEndedQueue`: Refactored internal structure to use `uint128` instead of `int128`. This has no effect on the library interface. ([#4150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4150)) -- `ECDSA`: Use unchecked arithmetic for the `tryRecover` function that receives the `r` and `vs` short-signature fields separately. ([#4301](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4301)) -- `EIP712`: Added internal getters for the name and version strings ([#4303](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4303)) -- `Math`: Makes `ceilDiv` to revert on 0 division even if the numerator is 0 ([#4348](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4348)) -- `Math`: Optimized stack operations in `mulDiv`. ([#4494](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4494)) -- `Math`: Renamed members of `Rounding` enum, and added a new rounding mode for "away from zero". ([#4455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4455)) -- `MerkleProof`: Use custom error to report invalid multiproof instead of reverting with overflow panic. ([#4564](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4564)) -- `MessageHashUtils`: Added a new library for creating message digest to be used along with signing or recovery such as ECDSA or ERC-1271. These functions are moved from the `ECDSA` library. ([#4430](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4430)) -- `Nonces`: Added a new contract to keep track of user nonces. Used for signatures in `ERC20Permit`, `ERC20Votes`, and `ERC721Votes`. ([#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816)) -- `ReentrancyGuard`, `Pausable`: Moved to `utils` directory. ([#4551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4551)) -- `Strings`: Renamed `toString(int256)` to `toStringSigned(int256)`. ([#4330](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4330)) -- Optimized `Strings.equal` ([#4262](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4262)) - -### How to migrate from 4.x - -#### ERC20, ERC721, and ERC1155 - -These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Thus, any customization made through those hooks should now be done overriding the new `_update` function instead. - -Minting and burning are implemented by `_update` and customizations should be done by overriding this function as well. `_transfer`, `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies. - -For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have to be changed in the following way. - -```diff --function _beforeTokenTransfer( -+function _update( - address from, - address to, - uint256 amount - ) internal virtual override { -- super._beforeTokenTransfer(from, to, amount); - require(!condition(), "ERC20: wrong condition"); -+ super._update(from, to, amount); - } -``` - -#### More about ERC721 - -In the case of `ERC721`, the `_update` function does not include a `from` parameter, as the sender is implicitly the previous owner of the `tokenId`. The address of this previous owner is returned by the `_update` function, so it can be used for a posteriori checks. In addition to `to` and `tokenId`, a third parameter (`auth`) is present in this function. This parameter enabled an optional check that the caller/spender is approved to do the transfer. This check cannot be performed after the transfer (because the transfer resets the approval), and doing it before `_update` would require a duplicate call to `_ownerOf`. - -In this logic of removing hidden SLOADs, the `_isApprovedOrOwner` function was removed in favor of a new `_isAuthorized` function. Overrides that used to target the `_isApprovedOrOwner` should now be performed on the `_isAuthorized` function. Calls to `_isApprovedOrOwner` that preceded a call to `_transfer`, `_burn` or `_approve` should be removed in favor of using the `auth` argument in `_update` and `_approve`. This is showcased in `ERC721Burnable.burn` and in `ERC721Wrapper.withdrawTo`. - -The `_exists` function was removed. Calls to this function can be replaced by `_ownerOf(tokenId) != address(0)`. - -#### More about ERC1155 - -Batch transfers will now emit `TransferSingle` if the batch consists of a single token, while in previous versions the `TransferBatch` event would be used for all transfers initiated through `safeBatchTransferFrom`. Both behaviors are compliant with the ERC-1155 specification. - -#### ERC165Storage - -Users that were registering EIP-165 interfaces with `_registerInterface` from `ERC165Storage` should instead do so by overriding the `supportsInterface` function as seen below: - -```solidity -function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); -} -``` - -#### SafeMath - -Methods in SafeMath superseded by native overflow checks in Solidity 0.8.0 were removed along with operations providing an interface for revert strings. The remaining methods were moved to `utils/Math.sol`. - -```diff -- import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -+ import "@openzeppelin/contracts/utils/math/Math.sol"; - - function tryOperations(uint256 x, uint256 y) external view { -- (bool overflowsAdd, uint256 resultAdd) = SafeMath.tryAdd(x, y); -+ (bool overflowsAdd, uint256 resultAdd) = Math.tryAdd(x, y); -- (bool overflowsSub, uint256 resultSub) = SafeMath.trySub(x, y); -+ (bool overflowsSub, uint256 resultSub) = Math.trySub(x, y); -- (bool overflowsMul, uint256 resultMul) = SafeMath.tryMul(x, y); -+ (bool overflowsMul, uint256 resultMul) = Math.tryMul(x, y); -- (bool overflowsDiv, uint256 resultDiv) = SafeMath.tryDiv(x, y); -+ (bool overflowsDiv, uint256 resultDiv) = Math.tryDiv(x, y); - // ... - } -``` - -#### Adapting Governor modules - -Custom Governor modules that override internal functions may require modifications if migrated to v5. In particular, the new internal functions `_queueOperations` and `_executeOperations` may need to be used. If assistance with this migration is needed reach out via the [OpenZeppelin Support Forum](https://forum.openzeppelin.com/c/support/contracts/18). - -#### ECDSA and MessageHashUtils - -The `ECDSA` library is now focused on signer recovery. Previously it also included utility methods for producing digests to be used with signing or recovery. These utilities have been moved to the `MessageHashUtils` library and should be imported if needed: - -```diff - import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -+import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; - - contract Verifier { - using ECDSA for bytes32; -+ using MessageHashUtils for bytes32; - - function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) { - return data - .toEthSignedMessageHash() - .recover(signature) == account; - } - } -``` - -#### Interfaces and libraries in upgradeable contracts - -The upgradeable version of the contracts library used to include a variant suffixed with `Upgradeable` for every contract. These variants, which are produced automatically, mainly include changes for dealing with storage that don't apply to libraries and interfaces. - -The upgradeable library no longer includes upgradeable variants for libraries and interfaces. Projects migrating to 5.0 should replace their library and interface imports with their corresponding non-upgradeable version: - -```diff - // Libraries --import {AddressUpgradeable} from '@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol'; -+import {Address} from '@openzeppelin/contracts/utils/Address.sol'; - - // Interfaces --import {IERC20Upgradeable} from '@openzeppelin/contracts-upgradeable/interfaces/IERC20.sol'; -+import {IERC20} from '@openzeppelin/contracts/interfaces/IERC20.sol'; -``` - -#### Offchain Considerations - -Some changes may affect offchain systems if they rely on assumptions that are changed along with these new breaking changes. These cases are: - -##### Relying on revert strings for processing errors - -A concrete example is AccessControl, where it was previously advised to catch revert reasons using the following regex: - -``` -/^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ -``` - -Instead, contracts now revert with custom errors. Systems that interact with smart contracts outside of the network should consider reliance on revert strings and possibly support the new custom errors. - -##### Relying on storage locations for retrieving data - -After 5.0, the storage location of some variables were changed. This is the case for `Initializable` and all the upgradeable contracts since they now use namespaced storaged locations. Any system relying on storage locations for retrieving data or detecting capabilities should be updated to support these new locations. - -## 4.9.6 (2024-02-29) - -- `Base64`: Fix issue where dirty memory located just after the input buffer is affecting the result. ([#4929](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4929)) - -## 4.9.5 (2023-12-08) - -- `Multicall`: Make aware of non-canonical context (i.e. `msg.sender` is not `_msgSender()`), allowing compatibility with `ERC2771Context`. Patch duplicated `Address.functionDelegateCall` in v4.9.4 (removed). - -## 4.9.3 (2023-07-28) - -- `ERC2771Context`: Return the forwarder address whenever the `msg.data` of a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes), as specified by ERC-2771. ([#4481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4481)) -- `ERC2771Context`: Prevent revert in `_msgData()` when a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes). Return the full calldata in that case. ([#4484](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4484)) - -## 4.9.2 (2023-06-16) - -- `MerkleProof`: Fix a bug in `processMultiProof` and `processMultiProofCalldata` that allows proving arbitrary leaves if the tree contains a node with value 0 at depth 1. - -## 4.9.1 (2023-06-07) - -- `Governor`: Add a mechanism to restrict the address of the proposer using a suffix in the description. - -## 4.9.0 (2023-05-23) - -- `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) -- `ERC721Wrapper`: add a new extension of the `ERC721` token which wraps an underlying token. Deposit and withdraw guarantee that the ownership of each token is backed by a corresponding underlying token with the same identifier. ([#3863](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3863)) -- `EnumerableMap`: add a `keys()` function that returns an array containing all the keys. ([#3920](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3920)) -- `Governor`: add a public `cancel(uint256)` function. ([#3983](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3983)) -- `Governor`: Enable timestamp operation for blockchains without a stable block time. This is achieved by connecting a Governor's internal clock to match a voting token's EIP-6372 interface. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934)) -- `Strings`: add `equal` method. ([#3774](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3774)) -- `IERC5313`: Add an interface for EIP-5313 that is now final. ([#4013](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4013)) -- `IERC4906`: Add an interface for ERC-4906 that is now Final. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012)) -- `StorageSlot`: Add support for `string` and `bytes`. ([#4008](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4008)) -- `Votes`, `ERC20Votes`, `ERC721Votes`: support timestamp checkpointing using EIP-6372. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934)) -- `ERC4626`: Add mitigation to the inflation attack through virtual shares and assets. ([#3979](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3979)) -- `Strings`: add `toString` method for signed integers. ([#3773](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3773)) -- `ERC20Wrapper`: Make the `underlying` variable private and add a public accessor. ([#4029](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4029)) -- `EIP712`: add EIP-5267 support for better domain discovery. ([#3969](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3969)) -- `AccessControlDefaultAdminRules`: Add an extension of `AccessControl` with additional security rules for the `DEFAULT_ADMIN_ROLE`. ([#4009](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4009)) -- `SignatureChecker`: Add `isValidERC1271SignatureNow` for checking a signature directly against a smart contract using ERC-1271. ([#3932](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3932)) -- `SafeERC20`: Add a `forceApprove` function to improve compatibility with tokens behaving like USDT. ([#4067](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4067)) -- `ERC1967Upgrade`: removed contract-wide `oz-upgrades-unsafe-allow delegatecall` annotation, replaced by granular annotation in `UUPSUpgradeable`. ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971)) -- `ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitly forbidden. ([#4100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4100)) -- `ECDSA`: optimize bytes32 computation by using assembly instead of `abi.encodePacked`. ([#3853](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3853)) -- `ERC721URIStorage`: Emit ERC-4906 `MetadataUpdate` in `_setTokenURI`. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012)) -- `ShortStrings`: Added a library for handling short strings in a gas efficient way, with fallback to storage for longer strings. ([#4023](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4023)) -- `SignatureChecker`: Allow return data length greater than 32 from EIP-1271 signers. ([#4038](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4038)) -- `UUPSUpgradeable`: added granular `oz-upgrades-unsafe-allow-reachable` annotation to improve upgrade safety checks on latest version of the Upgrades Plugins (starting with `@openzeppelin/upgrades-core@1.21.0`). ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971)) -- `Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787)) -- `Ownable2Step`: make `acceptOwnership` public virtual to enable usecases that require overriding it. ([#3960](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3960)) -- `UUPSUpgradeable.sol`: Change visibility to the functions `upgradeTo ` and `upgradeToAndCall ` from `external` to `public`. ([#3959](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3959)) -- `TimelockController`: Add the `CallSalt` event to emit on operation schedule. ([#4001](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4001)) -- Reformatted codebase with latest version of Prettier Solidity. ([#3898](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3898)) -- `Math`: optimize `log256` rounding check. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) -- `ERC20Votes`: optimize by using unchecked arithmetic. ([#3748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3748)) -- `Multicall`: annotate `multicall` function as upgrade safe to not raise a flag for its delegatecall. ([#3961](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3961)) -- `ERC20Pausable`, `ERC721Pausable`, `ERC1155Pausable`: Add note regarding missing public pausing functionality ([#4007](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4007)) -- `ECDSA`: Add a function `toDataWithIntendedValidatorHash` that encodes data with version 0x00 following EIP-191. ([#4063](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4063)) -- `MerkleProof`: optimize by using unchecked arithmetic. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) - -### Breaking changes - -- `EIP712`: Addition of ERC5267 support requires support for user defined value types, which was released in Solidity version 0.8.8. This requires a pragma change from `^0.8.0` to `^0.8.8`. -- `EIP712`: Optimization of the cache for the upgradeable version affects the way `name` and `version` are set. This is no longer done through an initializer, and is instead part of the implementation's constructor. As a consequence, all proxies using the same implementation will necessarily share the same `name` and `version`. Additionally, an implementation upgrade risks changing the EIP712 domain unless the same `name` and `version` are used when deploying the new implementation contract. - -### Deprecations - -- `ERC20Permit`: Added the file `IERC20Permit.sol` and `ERC20Permit.sol` and deprecated `draft-IERC20Permit.sol` and `draft-ERC20Permit.sol` since [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) is no longer a Draft. Developers are encouraged to update their imports. ([#3793](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3793)) -- `Timers`: The `Timers` library is now deprecated and will be removed in the next major release. ([#4062](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4062)) -- `ERC777`: The `ERC777` token standard is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066)) -- `ERC1820Implementer`: The `ERC1820` pseudo-introspection mechanism is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066)) - -## 4.8.3 (2023-04-13) - -- `GovernorCompatibilityBravo`: Fix encoding of proposal data when signatures are missing. -- `TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata or payable mutability. ([#4154](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4154)) - -## 4.8.2 (2023-03-02) - -- `ERC721Consecutive`: Fixed a bug when `_mintConsecutive` is used for batches of size 1 that could lead to balance overflow. Refer to the breaking changes section in the changelog for a note on the behavior of `ERC721._beforeTokenTransfer`. - -### Breaking changes - -- `ERC721`: The internal function `_beforeTokenTransfer` no longer updates balances, which it previously did when `batchSize` was greater than 1. This change has no consequence unless a custom ERC721 extension is explicitly invoking `_beforeTokenTransfer`. Balance updates in extensions must now be done explicitly using `__unsafe_increaseBalance`, with a name that indicates that there is an invariant that has to be manually verified. - -## 4.8.1 (2023-01-12) - -- `ERC4626`: Use staticcall instead of call when fetching underlying ERC-20 decimals. ([#3943](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3943)) - -## 4.8.0 (2022-11-08) - -- `TimelockController`: Added a new `admin` constructor parameter that is assigned the admin role instead of the deployer account. ([#3722](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3722)) -- `Initializable`: add internal functions `_getInitializedVersion` and `_isInitializing` ([#3598](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3598)) -- `ERC165Checker`: add `supportsERC165InterfaceUnchecked` for consulting individual interfaces without the full ERC165 protocol. ([#3339](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3339)) -- `Address`: optimize `functionCall` by calling `functionCallWithValue` directly. ([#3468](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3468)) -- `Address`: optimize `functionCall` functions by checking contract size only if there is no returned data. ([#3469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3469)) -- `Governor`: make the `relay` function payable, and add support for EOA payments. ([#3730](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3730)) -- `GovernorCompatibilityBravo`: remove unused `using` statements. ([#3506](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3506)) -- `ERC20`: optimize `_transfer`, `_mint` and `_burn` by using `unchecked` arithmetic when possible. ([#3513](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3513)) -- `ERC20Votes`, `ERC721Votes`: optimize `getPastVotes` for looking up recent checkpoints. ([#3673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3673)) -- `ERC20FlashMint`: add an internal `_flashFee` function for overriding. ([#3551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3551)) -- `ERC4626`: use the same `decimals()` as the underlying asset by default (if available). ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639)) -- `ERC4626`: add internal `_initialConvertToShares` and `_initialConvertToAssets` functions to customize empty vaults behavior. ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639)) -- `ERC721`: optimize transfers by making approval clearing implicit instead of emitting an event. ([#3481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3481)) -- `ERC721`: optimize burn by making approval clearing implicit instead of emitting an event. ([#3538](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3538)) -- `ERC721`: Fix balance accounting when a custom `_beforeTokenTransfer` hook results in a transfer of the token under consideration. ([#3611](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3611)) -- `ERC721`: use unchecked arithmetic for balance updates. ([#3524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3524)) -- `ERC721Consecutive`: Implementation of EIP-2309 that allows batch minting of ERC721 tokens during construction. ([#3311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3311)) -- `ReentrancyGuard`: Reduce code size impact of the modifier by using internal functions. ([#3515](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3515)) -- `SafeCast`: optimize downcasting of signed integers. ([#3565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3565)) -- `ECDSA`: Remove redundant check on the `v` value. ([#3591](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3591)) -- `VestingWallet`: add `releasable` getters. ([#3580](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3580)) -- `VestingWallet`: remove unused library `Math.sol`. ([#3605](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3605)) -- `VestingWallet`: make constructor payable. ([#3665](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3665)) -- `Create2`: optimize address computation by using assembly instead of `abi.encodePacked`. ([#3600](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3600)) -- `Clones`: optimized the assembly to use only the scratch space during deployments, and optimized `predictDeterministicAddress` to use fewer operations. ([#3640](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3640)) -- `Checkpoints`: Use procedural generation to support multiple key/value lengths. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) -- `Checkpoints`: Add new lookup mechanisms. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) -- `Arrays`: Add `unsafeAccess` functions that allow reading and writing to an element in a storage array bypassing Solidity's "out-of-bounds" check. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) -- `Strings`: optimize `toString`. ([#3573](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3573)) -- `Ownable2Step`: extension of `Ownable` that makes the ownership transfers a two step process. ([#3620](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3620)) -- `Math` and `SignedMath`: optimize function `max` by using `>` instead of `>=`. ([#3679](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3679)) -- `Math`: Add `log2`, `log10` and `log256`. ([#3670](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3670)) -- Arbitrum: Update the vendored arbitrum contracts to match the nitro upgrade. ([#3692](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3692)) - -### Breaking changes - -- `ERC721`: In order to add support for batch minting via `ERC721Consecutive` it was necessary to make a minor breaking change in the internal interface of `ERC721`. Namely, the hooks `_beforeTokenTransfer` and `_afterTokenTransfer` have one additional argument that may need to be added to overrides: - -```diff - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId, -+ uint256 batchSize - ) internal virtual override -``` - -- `ERC4626`: Conversion from shares to assets (and vice-versa) in an empty vault used to consider the possible mismatch between the underlying asset's and the vault's decimals. This initial conversion rate is now set to 1-to-1 irrespective of decimals, which are meant for usability purposes only. The vault now uses the assets decimals by default, so off-chain the numbers should appear the same. Developers overriding the vault decimals to a value that does not match the underlying asset may want to override the `_initialConvertToShares` and `_initialConvertToAssets` to replicate the previous behavior. - -- `TimelockController`: During deployment, the TimelockController used to grant the `TIMELOCK_ADMIN_ROLE` to the deployer and to the timelock itself. The deployer was then expected to renounce this role once configuration of the timelock is over. Failing to renounce that role allows the deployer to change the timelock permissions (but not to bypass the delay for any time-locked actions). The role is no longer given to the deployer by default. A new parameter `admin` can be set to a non-zero address to grant the admin role during construction (to the deployer or any other address). Just like previously, this admin role should be renounced after configuration. If this param is given `address(0)`, the role is not allocated and doesn't need to be revoked. In any case, the timelock itself continues to have this role. - -### Deprecations - -- `EIP712`: Added the file `EIP712.sol` and deprecated `draft-EIP712.sol` since the EIP is no longer a Draft. Developers are encouraged to update their imports. ([#3621](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3621)) - -```diff --import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; -+import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -``` - -- `ERC721Votes`: Added the file `ERC721Votes.sol` and deprecated `draft-ERC721Votes.sol` since it no longer depends on a Draft EIP (EIP-712). Developers are encouraged to update their imports. ([#3699](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3699)) - -```diff --import "@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol"; -+import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol"; -``` - -### ERC-721 Compatibility Note - -ERC-721 integrators that interpret contract state from events should make sure that they implement the clearing of approval that is implicit in every transfer according to the EIP. Previous versions of OpenZeppelin Contracts emitted an explicit `Approval` event even though it was not required by the specification, and this is no longer the case. - -With the new `ERC721Consecutive` extension, the internal workings of `ERC721` are slightly changed. Custom extensions to ERC721 should be reviewed to ensure they remain correct. The internal functions that should be considered are `_ownerOf` (new), `_beforeTokenTransfer`, and `_afterTokenTransfer`. - -### ERC-4626 Upgrade Note - -Existing `ERC4626` contracts that are upgraded to 4.8 must initialize a new variable that holds the vault token decimals. The recommended way to do this is to use a [reinitializer]: - -[reinitializer]: https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable-reinitializer-uint8- - -```solidity -function migrateToV48() public reinitializer(2) { - __ERC4626_init(IERC20Upgradeable(asset())); -} -``` - -## 4.7.3 (2022-08-10) - -### Breaking changes - -- `ECDSA`: `recover(bytes32,bytes)` and `tryRecover(bytes32,bytes)` no longer accept compact signatures to prevent malleability. Compact signature support remains available using `recover(bytes32,bytes32,bytes32)` and `tryRecover(bytes32,bytes32,bytes32)`. - -## 4.7.2 (2022-07-25) - -- `LibArbitrumL2`, `CrossChainEnabledArbitrumL2`: Fixed detection of cross-chain calls for EOAs. Previously, calls from EOAs would be classified as cross-chain calls. ([#3578](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3578)) -- `GovernorVotesQuorumFraction`: Fixed quorum updates so they do not affect past proposals that failed due to lack of quorum. ([#3561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3561)) -- `ERC165Checker`: Added protection against large returndata. ([#3587](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3587)) - -## 4.7.1 (2022-07-18) - -- `SignatureChecker`: Fix an issue that causes `isValidSignatureNow` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552)) -- `ERC165Checker`: Fix an issue that causes `supportsInterface` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552)) - -## 4.7.0 (2022-06-29) - -- `TimelockController`: Migrate `_call` to `_execute` and allow inheritance and overriding similar to `Governor`. ([#3317](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3317)) -- `CrossChainEnabledPolygonChild`: replace the `require` statement with the custom error `NotCrossChainCall`. ([#3380](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3380)) -- `ERC20FlashMint`: Add customizable flash fee receiver. ([#3327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3327)) -- `ERC4626`: add an extension of `ERC20` that implements the ERC4626 Tokenized Vault Standard. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171)) -- `SafeERC20`: add `safePermit` as mitigation against phantom permit functions. ([#3280](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3280)) -- `Math`: add a `mulDiv` function that can round the result either up or down. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171)) -- `Math`: Add a `sqrt` function to compute square roots of integers, rounding either up or down. ([#3242](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3242)) -- `Strings`: add a new overloaded function `toHexString` that converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. ([#3403](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3403)) -- `EnumerableMap`: add new `UintToUintMap` map type. ([#3338](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3338)) -- `EnumerableMap`: add new `Bytes32ToUintMap` map type. ([#3416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3416)) -- `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245)) -- `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276)) -- `MerkleProof`: add calldata versions of the functions to avoid copying input arrays to memory and save gas. ([#3200](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3200)) -- `ERC721`, `ERC1155`: simplified revert reasons. ([#3254](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3254), ([#3438](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3438))) -- `ERC721`: removed redundant require statement. ([#3434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3434)) -- `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350)) -- `Initializable`: refactored implementation of modifiers for easier understanding. ([#3450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3450)) -- `Proxies`: remove runtime check of ERC1967 storage slots. ([#3455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3455)) - -### Breaking changes - -- `Initializable`: functions decorated with the modifier `reinitializer(1)` may no longer invoke each other. - -## 4.6.0 (2022-04-26) - -- `crosschain`: Add a new set of contracts for cross-chain applications. `CrossChainEnabled` is a base contract with instantiations for several chains and bridges, and `AccessControlCrossChain` is an extension of access control that allows cross-chain operation. ([#3183](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3183)) -- `AccessControl`: add a virtual `_checkRole(bytes32)` function that can be overridden to alter the `onlyRole` modifier behavior. ([#3137](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3137)) -- `EnumerableMap`: add new `AddressToUintMap` map type. ([#3150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3150)) -- `EnumerableMap`: add new `Bytes32ToBytes32Map` map type. ([#3192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3192)) -- `ERC20FlashMint`: support infinite allowance when paying back a flash loan. ([#3226](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3226)) -- `ERC20Wrapper`: the `decimals()` function now tries to fetch the value from the underlying token instance. If that calls revert, then the default value is used. ([#3259](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3259)) -- `draft-ERC20Permit`: replace `immutable` with `constant` for `_PERMIT_TYPEHASH` since the `keccak256` of string literals is treated specially and the hash is evaluated at compile time. ([#3196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3196)) -- `ERC1155`: Add a `_afterTokenTransfer` hook for improved extensibility. ([#3166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3166)) -- `ERC1155URIStorage`: add a new extension that implements a `_setURI` behavior similar to ERC721's `_setTokenURI`. ([#3210](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3210)) -- `DoubleEndedQueue`: a new data structure that supports efficient push and pop to both front and back, useful for FIFO and LIFO queues. ([#3153](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3153)) -- `Governor`: improved security of `onlyGovernance` modifier when using an external executor contract (e.g. a timelock) that can operate without necessarily going through the governance protocol. ([#3147](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3147)) -- `Governor`: Add a way to parameterize votes. This can be used to implement voting systems such as fractionalized voting, ERC721 based voting, or any number of other systems. The `params` argument added to `_countVote` method, and included in the newly added `_getVotes` method, can be used by counting and voting modules respectively for such purposes. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) -- `Governor`: rewording of revert reason for consistency. ([#3275](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3275)) -- `Governor`: fix an inconsistency in data locations that could lead to invalid bytecode being produced. ([#3295](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3295)) -- `Governor`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by governors. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230)) -- `TimelockController`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by timelocks. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230)) -- `TimelockController`: Add a separate canceller role for the ability to cancel. ([#3165](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3165)) -- `Initializable`: add a reinitializer modifier that enables the initialization of new modules, added to already initialized contracts through upgradeability. ([#3232](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3232)) -- `Initializable`: add an Initialized event that tracks initialized version numbers. ([#3294](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3294)) -- `ERC2981`: make `royaltyInfo` public to allow super call in overrides. ([#3305](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3305)) - -### Upgradeability notice - -- `TimelockController`: **(Action needed)** The upgrade from <4.6 to >=4.6 introduces a new `CANCELLER_ROLE` that requires set up to be assignable. After the upgrade, only addresses with this role will have the ability to cancel. Proposers will no longer be able to cancel. Assigning cancellers can be done by an admin (including the timelock itself) once the role admin is set up. To do this, we recommend upgrading to the `TimelockControllerWith46MigrationUpgradeable` contract and then calling the `migrateTo46` function. - -### Breaking changes - -- `Governor`: Adds internal virtual `_getVotes` method that must be implemented; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing voting module extension, rename `getVotes` to `_getVotes` and add a `bytes memory` argument. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) -- `Governor`: Adds `params` parameter to internal virtual `_countVote` method; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing counting module extension, add a `bytes memory` argument to `_countVote`. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) -- `Governor`: Does not emit `VoteCast` event when params data is non-empty; instead emits `VoteCastWithParams` event. To fix this on an integration that consumes the `VoteCast` event, also fetch/monitor `VoteCastWithParams` events. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) -- `Votes`: The internal virtual function `_getVotingUnits` was made `view` (which was accidentally missing). Any overrides should now be updated so they are `view` as well. - -## 4.5.0 (2022-02-09) - -- `ERC2981`: add implementation of the royalty standard, and the respective extensions for `ERC721` and `ERC1155`. ([#3012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3012)) -- `GovernorTimelockControl`: improve the `state()` function to have it reflect cases where a proposal has been canceled directly on the timelock. ([#2977](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2977)) -- Preset contracts are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com). ([#2986](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2986)) -- `Governor`: add a relay function to help recover assets sent to a governor that is not its own executor (e.g. when using a timelock). ([#2926](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2926)) -- `GovernorPreventLateQuorum`: add new module to ensure a minimum voting duration is available after the quorum is reached. ([#2973](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2973)) -- `ERC721`: improved revert reason when transferring from wrong owner. ([#2975](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2975)) -- `Votes`: Added a base contract for vote tracking with delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944)) -- `ERC721Votes`: Added an extension of ERC721 enabled with vote tracking and delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944)) -- `ERC2771Context`: use immutable storage to store the forwarder address, no longer an issue since Solidity >=0.8.8 allows reading immutable variables in the constructor. ([#2917](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2917)) -- `Base64`: add a library to parse bytes into base64 strings using `encode(bytes memory)` function, and provide examples to show how to use to build URL-safe `tokenURIs`. ([#2884](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884)) -- `ERC20`: reduce allowance before triggering transfer. ([#3056](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3056)) -- `ERC20`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085)) -- `ERC20`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) -- `ERC20Burnable`: do not update allowance on `burnFrom` when allowance is `type(uint256).max`. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) -- `ERC777`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085)) -- `ERC777`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) -- `SignedMath`: a new signed version of the Math library with `max`, `min`, and `average`. ([#2686](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2686)) -- `SignedMath`: add an `abs(int256)` method that returns the unsigned absolute value of a signed value. ([#2984](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2984)) -- `ERC1967Upgrade`: Refactor the secure upgrade to use `ERC1822` instead of the previous rollback mechanism. This reduces code complexity and attack surface with similar security guarantees. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021)) -- `UUPSUpgradeable`: Add `ERC1822` compliance to support the updated secure upgrade mechanism. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021)) -- Some more functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior. - -### Breaking changes - -- `ERC1967Upgrade`: The function `_upgradeToAndCallSecure` was renamed to `_upgradeToAndCallUUPS`, along with the change in security mechanism described above. -- `Address`: The Solidity pragma is increased from `^0.8.0` to `^0.8.1`. This is required by the `account.code.length` syntax that replaces inline assembly. This may require users to bump their compiler version from `0.8.0` to `0.8.1` or later. Note that other parts of the code already include stricter requirements. - -## 4.4.2 (2022-01-11) - -### Bugfixes - -- `GovernorCompatibilityBravo`: Fix error in the encoding of calldata for proposals submitted through the compatibility interface with explicit signatures. ([#3100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3100)) - -## 4.4.1 (2021-12-14) - -- `Initializable`: change the existing `initializer` modifier and add a new `onlyInitializing` modifier to prevent reentrancy risk. ([#3006](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3006)) - -### Breaking change - -It is no longer possible to call an `initializer`-protected function from within another `initializer` function outside the context of a constructor. Projects using OpenZeppelin upgradeable proxies should continue to work as is, since in the common case the initializer is invoked in the constructor directly. If this is not the case for you, the suggested change is to use the new `onlyInitializing` modifier in the following way: - -```diff - contract A { -- function initialize() public initializer { ... } -+ function initialize() internal onlyInitializing { ... } - } - contract B is A { - function initialize() public initializer { - A.initialize(); - } - } -``` - -## 4.4.0 (2021-11-25) - -- `Ownable`: add an internal `_transferOwnership(address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) -- `AccessControl`: add internal `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) -- `AccessControl`: mark `_setupRole(bytes32,address)` as deprecated in favor of `_grantRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) -- `AccessControlEnumerable`: hook into `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2946](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2946)) -- `EIP712`: cache `address(this)` to immutable storage to avoid potential issues if a vanilla contract is used in a delegatecall context. ([#2852](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2852)) -- Add internal `_setApprovalForAll` to `ERC721` and `ERC1155`. ([#2834](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2834)) -- `Governor`: shift vote start and end by one block to better match Compound's GovernorBravo and prevent voting at the Governor level if the voting snapshot is not ready. ([#2892](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2892)) -- `GovernorCompatibilityBravo`: consider quorum an inclusive rather than exclusive minimum to match Compound's GovernorBravo. ([#2974](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2974)) -- `GovernorSettings`: a new governor module that manages voting settings updatable through governance actions. ([#2904](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2904)) -- `PaymentSplitter`: now supports ERC20 assets in addition to Ether. ([#2858](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2858)) -- `ECDSA`: add a variant of `toEthSignedMessageHash` for arbitrary length message hashing. ([#2865](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2865)) -- `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) -- `VestingWallet`: new contract that handles the vesting of Ether and ERC20 tokens following a customizable vesting schedule. ([#2748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2748)) -- `Governor`: enable receiving Ether when a Timelock contract is not used. ([#2849](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849)) -- `GovernorTimelockCompound`: fix ability to use Ether stored in the Timelock contract. ([#2849](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849)) - -## 4.3.3 (2021-11-08) - -- `ERC1155Supply`: Handle `totalSupply` changes by hooking into `_beforeTokenTransfer` to ensure consistency of balances and supply during `IERC1155Receiver.onERC1155Received` calls. - -## 4.3.2 (2021-09-14) - -- `UUPSUpgradeable`: Add modifiers to prevent `upgradeTo` and `upgradeToAndCall` being executed on any contract that is not the active ERC1967 proxy. This prevents these functions being called on implementation contracts or minimal ERC1167 clones, in particular. - -## 4.3.1 (2021-08-26) - -- `TimelockController`: Add additional isOperationReady check. - -## 4.3.0 (2021-08-17) - -- `ERC2771Context`: use private variable from storage to store the forwarder address. Fixes issues where `_msgSender()` was not callable from constructors. ([#2754](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2754)) -- `EnumerableSet`: add `values()` functions that returns an array containing all values in a single call. ([#2768](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2768)) -- `Governor`: added a modular system of `Governor` contracts based on `GovernorAlpha` and `GovernorBravo`. ([#2672](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2672)) -- Add an `interfaces` folder containing solidity interfaces to final ERCs. ([#2517](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2517)) -- `ECDSA`: add `tryRecover` functions that will not throw if the signature is invalid, and will return an error flag instead. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661)) -- `SignatureChecker`: Reduce gas usage of the `isValidSignatureNow` function for the "signature by EOA" case. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661)) - -## 4.2.0 (2021-06-30) - -- `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632)) -- `ERC20VotesComp`: Variant of `ERC20Votes` that is compatible with Compound's `Comp` token interface but restricts supply to `uint96`. ([#2706](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2706)) -- `ERC20Wrapper`: add a new extension of the `ERC20` token which wraps an underlying token. Deposit and withdraw guarantee that the total supply is backed by a corresponding amount of underlying token. ([#2633](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2633)) -- Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`. -- Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`. -- `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678)) -- Tokens: Wrap definitely safe subtractions in `unchecked` blocks. -- `Math`: Add a `ceilDiv` method for performing ceiling division. -- `ERC1155Supply`: add a new `ERC1155` extension that keeps track of the totalSupply of each tokenId. ([#2593](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2593)) -- `BitMaps`: add a new `BitMaps` library that provides a storage efficient datastructure for `uint256` to `bool` mapping with contiguous keys. ([#2710](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2710)) - -### Breaking Changes - -- `ERC20FlashMint` is no longer a Draft ERC. ([#2673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2673))) - -**How to update:** Change your import paths by removing the `draft-` prefix from `@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20FlashMint.sol`. - -> See [Releases and Stability: Drafts](https://docs.openzeppelin.com/contracts/4.x/releases-stability#drafts). - -## 4.1.0 (2021-04-29) - -- `IERC20Metadata`: add a new extended interface that includes the optional `name()`, `symbol()` and `decimals()` functions. ([#2561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2561)) -- `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552)) -- `ERC20Permit`: add a `_useNonce` to enable further usage of ERC712 signatures. ([#2565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2565)) -- `ERC20FlashMint`: add an implementation of the ERC3156 extension for flash-minting ERC20 tokens. ([#2543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2543)) -- `SignatureChecker`: add a signature verification library that supports both EOA and ERC1271 compliant contracts as signers. ([#2532](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2532)) -- `Multicall`: add abstract contract with `multicall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608)) -- `ECDSA`: add support for ERC2098 short-signatures. ([#2582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2582)) -- `AccessControl`: add an `onlyRole` modifier to restrict specific function to callers bearing a specific role. ([#2609](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2609)) -- `StorageSlot`: add a library for reading and writing primitive types to specific storage slots. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542)) -- UUPS Proxies: add `UUPSUpgradeable` to implement the UUPS proxy pattern together with `EIP1967Proxy`. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542)) - -### Breaking changes - -This release includes two small breaking changes in `TimelockController`. - -1. The `onlyRole` modifier in this contract was designed to let anyone through if the role was granted to `address(0)`, - allowing the possibility to make a role "open", which can be used for `EXECUTOR_ROLE`. This modifier is now - replaced by `AccessControl.onlyRole`, which does not have this ability. The previous behavior was moved to the - modifier `TimelockController.onlyRoleOrOpenRole`. -2. It was possible to make `PROPOSER_ROLE` an open role (as described in the previous item) if it was granted to - `address(0)`. This would affect the `schedule`, `scheduleBatch`, and `cancel` operations in `TimelockController`. - This ability was removed as it does not make sense to open up the `PROPOSER_ROLE` in the same way that it does for - `EXECUTOR_ROLE`. - -## 4.0.0 (2021-03-23) - -- Now targeting the 0.8.x line of Solidity compilers. For 0.6.x (resp 0.7.x) support, use version 3.4.0 (resp 3.4.0-solc-0.7) of OpenZeppelin. -- `Context`: making `_msgData` return `bytes calldata` instead of `bytes memory` ([#2492](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2492)) -- `ERC20`: removed the `_setDecimals` function and the storage slot associated to decimals. ([#2502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2502)) -- `Strings`: addition of a `toHexString` function. ([#2504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2504)) -- `EnumerableMap`: change implementation to optimize for `key → value` lookups instead of enumeration. ([#2518](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2518)) -- `GSN`: deprecate GSNv1 support in favor of upcoming support for GSNv2. ([#2521](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2521)) -- `ERC165`: remove uses of storage in the base ERC165 implementation. ERC165 based contracts now use storage-less virtual functions. Old behavior remains available in the `ERC165Storage` extension. ([#2505](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2505)) -- `Initializable`: make initializer check stricter during construction. ([#2531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2531)) -- `ERC721`: remove enumerability of tokens from the base implementation. This feature is now provided separately through the `ERC721Enumerable` extension. ([#2511](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2511)) -- `AccessControl`: removed enumerability by default for a more lightweight contract. It is now opt-in through `AccessControlEnumerable`. ([#2512](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2512)) -- Meta Transactions: add `ERC2771Context` and a `MinimalForwarder` for meta-transactions. ([#2508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2508)) -- Overall reorganization of the contract folder to improve clarity and discoverability. ([#2503](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2503)) -- `ERC20Capped`: optimize gas usage by enforcing the check directly in `_mint`. ([#2524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2524)) -- Rename `UpgradeableProxy` to `ERC1967Proxy`. ([#2547](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2547)) -- `ERC777`: optimize the gas costs of the constructor. ([#2551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2551)) -- `ERC721URIStorage`: add a new extension that implements the `_setTokenURI` behavior as it was available in 3.4.0. ([#2555](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2555)) -- `AccessControl`: added ERC165 interface detection. ([#2562](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2562)) -- `ERC1155`: make `uri` public so overloading function can call it using super. ([#2576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2576)) - -### Bug fixes for beta releases - -- `AccessControlEnumerable`: Fixed `renounceRole` not updating enumerable set of addresses for a role. ([#2572](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2572)) - -### How to upgrade from 3.x - -Since this version has moved a few contracts to different directories, users upgrading from a previous version will need to adjust their import statements. To make this easier, the package includes a script that will migrate import statements automatically. After upgrading to the latest version of the package, run: - -``` -npx openzeppelin-contracts-migrate-imports -``` - -Make sure you're using git or another version control system to be able to recover from any potential error in our script. - -### How to upgrade from 4.0-beta.x - -Some further changes have been done between the different beta iterations. Transitions made during this period are configured in the `migrate-imports` script. Consequently, you can upgrade from any previous 4.0-beta.x version using the same script as described in the _How to upgrade from 3.x_ section. - -## 3.4.2 (2021-07-24) - -- `TimelockController`: Add additional isOperationReady check. - -## 3.4.1 (2021-03-03) - -- `ERC721`: made `_approve` an internal function (was private). - -## 3.4.0 (2021-02-02) - -- `BeaconProxy`: added new kind of proxy that allows simultaneous atomic upgrades. ([#2411](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2411)) -- `EIP712`: added helpers to verify EIP712 typed data signatures on chain. ([#2418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2418)) -- `ERC20Permit`: added an implementation of the ERC20 permit extension for gasless token approvals. ([#2237](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237)) -- Presets: added token presets with preminted fixed supply `ERC20PresetFixedSupply` and `ERC777PresetFixedSupply`. ([#2399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2399)) -- `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) -- `Clones`: added a library for deploying EIP 1167 minimal proxies. ([#2449](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2449)) -- `Context`: moved from `contracts/GSN` to `contracts/utils`. ([#2453](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2453)) -- `PaymentSplitter`: replace usage of `.transfer()` with `Address.sendValue` for improved compatibility with smart wallets. ([#2455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2455)) -- `UpgradeableProxy`: bubble revert reasons from initialization calls. ([#2454](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2454)) -- `SafeMath`: fix a memory allocation issue by adding new `SafeMath.tryOp(uint,uint)→(bool,uint)` functions. `SafeMath.op(uint,uint,string)→uint` are now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462)) -- `EnumerableMap`: fix a memory allocation issue by adding new `EnumerableMap.tryGet(uint)→(bool,address)` functions. `EnumerableMap.get(uint)→string` is now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462)) -- `ERC165Checker`: added batch `getSupportedInterfaces`. ([#2469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2469)) -- `RefundEscrow`: `beneficiaryWithdraw` will forward all available gas to the beneficiary. ([#2480](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2480)) -- Many view and pure functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior. - -### Security Fixes - -- `ERC777`: fix potential reentrancy issues for custom extensions to `ERC777`. ([#2483](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483)) - -If you're using our implementation of ERC777 from version 3.3.0 or earlier, and you define a custom `_beforeTokenTransfer` function that writes to a storage variable, you may be vulnerable to a reentrancy attack. If you're affected and would like assistance please write to security@openzeppelin.com. [Read more in the pull request.](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483) - -## 3.3.0 (2020-11-26) - -- Now supports both Solidity 0.6 and 0.7. Compiling with solc 0.7 will result in warnings. Install the `solc-0.7` tag to compile without warnings. -- `Address`: added `functionStaticCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) -- `TimelockController`: added a contract to augment access control schemes with a delay. ([#2354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2354)) -- `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395)) - -## 3.2.2-solc-0.7 (2020-10-28) - -- Resolve warnings introduced by Solidity 0.7.4. ([#2396](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2396)) - -## 3.2.1-solc-0.7 (2020-09-15) - -- `ERC777`: Remove a warning about function state visibility in Solidity 0.7. ([#2327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2327)) - -## 3.2.0 (2020-09-10) - -### New features - -- Proxies: added the proxy contracts from OpenZeppelin SDK. ([#2335](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2335)) - -#### Proxy changes with respect to OpenZeppelin SDK - -Aside from upgrading them from Solidity 0.5 to 0.6, we've changed a few minor things from the proxy contracts as they were found in OpenZeppelin SDK. - -- `UpgradeabilityProxy` was renamed to `UpgradeableProxy`. -- `AdminUpgradeabilityProxy` was renamed to `TransparentUpgradeableProxy`. -- `Proxy._willFallback` was renamed to `Proxy._beforeFallback`. -- `UpgradeabilityProxy._setImplementation` and `AdminUpgradeabilityProxy._setAdmin` were made private. - -### Improvements - -- `Address.isContract`: switched from `extcodehash` to `extcodesize` for less gas usage. ([#2311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2311)) - -### Breaking changes - -- `ERC20Snapshot`: switched to using `_beforeTokenTransfer` hook instead of overriding ERC20 operations. ([#2312](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2312)) - -This small change in the way we implemented `ERC20Snapshot` may affect users who are combining this contract with -other ERC20 flavors, since it no longer overrides `_transfer`, `_mint`, and `_burn`. This can result in having to remove Solidity `override(...)` specifiers in derived contracts for these functions, and to instead have to add it for `_beforeTokenTransfer`. See [Using Hooks](https://docs.openzeppelin.com/contracts/3.x/extending-contracts#using-hooks) in the documentation. - -## 3.1.0 (2020-06-23) - -### New features - -- `SafeCast`: added functions to downcast signed integers (e.g. `toInt32`), improving usability of `SignedSafeMath`. ([#2243](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2243)) -- `functionCall`: new helpers that replicate Solidity's function call semantics, reducing the need to rely on `call`. ([#2264](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2264)) -- `ERC1155`: added support for a base implementation, non-standard extensions and a preset contract. ([#2014](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2014), [#2230](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2230)) - -### Improvements - -- `ReentrancyGuard`: reduced overhead of using the `nonReentrant` modifier. ([#2171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2171)) -- `AccessControl`: added a `RoleAdminChanged` event to `_setAdminRole`. ([#2214](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2214)) -- Made all `public` functions in the token preset contracts `virtual`. ([#2257](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2257)) - -### Deprecations - -- `SafeERC20`: deprecated `safeApprove`. ([#2268](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2268)) - -## 3.0.2 (2020-06-08) - -### Improvements - -- Added SPX license identifier to all contracts. ([#2235](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2235)) - -## 3.0.1 (2020-04-27) - -### Bugfixes - -- `ERC777`: fixed the `_approve` internal function not validating some of their arguments for non-zero addresses. ([#2213](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2213)) - -## 3.0.0 (2020-04-20) - -### New features - -- `AccessControl`: new contract for managing permissions in a system, replacement for `Ownable` and `Roles`. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112)) -- `SafeCast`: new functions to convert to and from signed and unsigned values: `toUint256` and `toInt256`. ([#2123](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2123)) -- `EnumerableMap`: a new data structure for key-value pairs (like `mapping`) that can be iterated over. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160)) - -### Breaking changes - -- `ERC721`: `burn(owner, tokenId)` was removed, use `burn(tokenId)` instead. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) -- `ERC721`: `_checkOnERC721Received` was removed. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) -- `ERC721`: `_transferFrom` and `_safeTransferFrom` were renamed to `_transfer` and `_safeTransfer`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162)) -- `Ownable`: removed `_transferOwnership`. ([#2162](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2162)) -- `PullPayment`, `Escrow`: `withdrawWithGas` was removed. The old `withdraw` function now forwards all gas. ([#2125](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2125)) -- `Roles` was removed, use `AccessControl` as a replacement. ([#2112](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2112)) -- `ECDSA`: when receiving an invalid signature, `recover` now reverts instead of returning the zero address. ([#2114](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2114)) -- `Create2`: added an `amount` argument to `deploy` for contracts with `payable` constructors. ([#2117](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2117)) -- `Pausable`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) -- `Strings`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) -- `Counters`: moved to the `utils` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) -- `SignedSafeMath`: moved to the `math` directory. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) -- `ERC20Snapshot`: moved to the `token/ERC20` directory. `snapshot` was changed into an `internal` function. ([#2122](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2122)) -- `Ownable`: moved to the `access` directory. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) -- `Ownable`: removed `isOwner`. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) -- `Secondary`: removed from the library, use `Ownable` instead. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) -- `Escrow`, `ConditionalEscrow`, `RefundEscrow`: these now use `Ownable` instead of `Secondary`, their external API changed accordingly. ([#2120](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2120)) -- `ERC20`: removed `_burnFrom`. ([#2119](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2119)) -- `Address`: removed `toPayable`, use `payable(address)` instead. ([#2133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2133)) -- `ERC777`: `_send`, `_mint` and `_burn` now use the caller as the operator. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134)) -- `ERC777`: removed `_callsTokensToSend` and `_callTokensReceived`. ([#2134](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2134)) -- `EnumerableSet`: renamed `get` to `at`. ([#2151](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2151)) -- `ERC165Checker`: functions no longer have a leading underscore. ([#2150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2150)) -- `ERC721Metadata`, `ERC721Enumerable`: these contracts were removed, and their functionality merged into `ERC721`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160)) -- `ERC721`: added a constructor for `name` and `symbol`. ([#2160](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2160)) -- `ERC20Detailed`: this contract was removed and its functionality merged into `ERC20`. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) -- `ERC20`: added a constructor for `name` and `symbol`. `decimals` now defaults to 18. ([#2161](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2161)) -- `Strings`: renamed `fromUint256` to `toString` ([#2188](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2188)) - -## 2.5.1 (2020-04-24) - -### Bugfixes - -- `ERC777`: fixed the `_send` and `_approve` internal functions not validating some of their arguments for non-zero addresses. ([#2212](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2212)) - -## 2.5.0 (2020-02-04) - -### New features - -- `SafeCast.toUintXX`: new library for integer downcasting, which allows for safe operation on smaller types (e.g. `uint32`) when combined with `SafeMath`. ([#1926](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1926)) -- `ERC721Metadata`: added `baseURI`, which can be used for dramatic gas savings when all token URIs share a prefix (e.g. `http://api.myapp.com/tokens/`). ([#1970](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1970)) -- `EnumerableSet`: new library for storing enumerable sets of values. Only `AddressSet` is supported in this release. ([#2061](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/2061)) -- `Create2`: simple library to make usage of the `CREATE2` opcode easier. ([#1744](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1744)) - -### Improvements - -- `ERC777`: `_burn` is now internal, providing more flexibility and making it easier to create tokens that deflate. ([#1908](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1908)) -- `ReentrancyGuard`: greatly improved gas efficiency by using the net gas metering mechanism introduced in the Istanbul hardfork. ([#1992](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1992), [#1996](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1996)) -- `ERC777`: improve extensibility by making `_send` and related functions `internal`. ([#2027](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2027)) -- `ERC721`: improved revert reason when transferring tokens to a non-recipient contract. ([#2018](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2018)) - -### Breaking changes - -- `ERC165Checker` now requires a minimum Solidity compiler version of 0.5.10. ([#1829](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1829)) - -## 2.4.0 (2019-10-29) - -### New features - -- `Address.toPayable`: added a helper to convert between address types without having to resort to low-level casting. ([#1773](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1773)) -- Facilities to make metatransaction-enabled contracts through the Gas Station Network. ([#1844](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1844)) -- `Address.sendValue`: added a replacement to Solidity's `transfer`, removing the fixed gas stipend. ([#1962](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1962)) -- Added replacement for functions that don't forward all gas (which have been deprecated): ([#1976](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1976)) - - `PullPayment.withdrawPaymentsWithGas(address payable payee)` - - `Escrow.withdrawWithGas(address payable payee)` -- `SafeMath`: added support for custom error messages to `sub`, `div` and `mod` functions. ([#1828](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1828)) - -### Improvements - -- `Address.isContract`: switched from `extcodesize` to `extcodehash` for less gas usage. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802)) -- `ERC20` and `ERC777` updated to throw custom errors on subtraction overflows. ([#1828](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1828)) - -### Deprecations - -- Deprecated functions that don't forward all gas: ([#1976](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1976)) - - `PullPayment.withdrawPayments(address payable payee)` - - `Escrow.withdraw(address payable payee)` - -### Breaking changes - -- `Address` now requires a minimum Solidity compiler version of 0.5.5. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802)) -- `SignatureBouncer` has been removed from drafts, both to avoid confusions with the GSN and `GSNRecipientSignature` (previously called `GSNBouncerSignature`) and because the API was not very clear. ([#1879](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1879)) - -### How to upgrade from 2.4.0-beta - -The final 2.4.0 release includes a refactor of the GSN contracts that will be a breaking change for 2.4.0-beta users. - -- The default empty implementations of `_preRelayedCall` and `_postRelayedCall` were removed and must now be explicitly implemented always in custom recipients. If your custom recipient didn't include an implementation, you can provide an empty one. -- `GSNRecipient`, `GSNBouncerBase`, and `GSNContext` were all merged into `GSNRecipient`. -- `GSNBouncerSignature` and `GSNBouncerERC20Fee` were renamed to `GSNRecipientSignature` and `GSNRecipientERC20Fee`. -- It is no longer necessary to inherit from `GSNRecipient` when using `GSNRecipientSignature` and `GSNRecipientERC20Fee`. - -For example, a contract using `GSNBouncerSignature` would have to be changed in the following way. - -```diff --contract MyDapp is GSNRecipient, GSNBouncerSignature { -+contract MyDapp is GSNRecipientSignature { -``` - -Refer to the table below to adjust your inheritance list. - -| 2.4.0-beta | 2.4.0 | -| ----------------------------------- | ----------------------- | -| `GSNRecipient, GSNBouncerSignature` | `GSNRecipientSignature` | -| `GSNRecipient, GSNBouncerERC20Fee` | `GSNRecipientERC20Fee` | -| `GSNBouncerBase` | `GSNRecipient` | - -## 2.3.0 (2019-05-27) - -### New features - -- `ERC1820`: added support for interacting with the [ERC1820](https://eips.ethereum.org/EIPS/eip-1820) registry contract (`IERC1820Registry`), as well as base contracts that can be registered as implementers there. ([#1677](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1677)) -- `ERC777`: support for the [ERC777 token](https://eips.ethereum.org/EIPS/eip-777), which has multiple improvements over `ERC20` (but is backwards compatible with it) such as built-in burning, a more straightforward permission system, and optional sender and receiver hooks on transfer (mandatory for contracts!). ([#1684](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1684)) -- All contracts now have revert reason strings, which give insight into error conditions, and help debug failing transactions. ([#1704](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1704)) - -### Improvements - -- Reverted the Solidity version bump done in v2.2.0, setting the minimum compiler version to v0.5.0, to prevent unexpected build breakage. Users are encouraged however to stay on top of new compiler releases, which usually include bugfixes. ([#1729](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1729)) - -### Bugfixes - -- `PostDeliveryCrowdsale`: some validations where skipped when paired with other crowdsale flavors, such as `AllowanceCrowdsale`, or `MintableCrowdsale` and `ERC20Capped`, which could cause buyers to not be able to claim their purchased tokens. ([#1721](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1721)) -- `ERC20._transfer`: the `from` argument was allowed to be the zero address, so it was possible to internally trigger a transfer of 0 tokens from the zero address. This address is not a valid destinatary of transfers, nor can it give or receive allowance, so this behavior was inconsistent. It now reverts. ([#1752](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1752)) - -## 2.2.0 (2019-03-14) - -### New features - -- `ERC20Snapshot`: create snapshots on demand of the token balances and total supply, to later retrieve and e.g. calculate dividends at a past time. ([#1617](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1617)) -- `SafeERC20`: `ERC20` contracts with no return value (i.e. that revert on failure) are now supported. ([#1655](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1655)) -- `ERC20`: added internal `_approve(address owner, address spender, uint256 value)`, allowing derived contracts to set the allowance of arbitrary accounts. ([#1609](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1609)) -- `ERC20Metadata`: added internal `_setTokenURI(string memory tokenURI)`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618)) -- `TimedCrowdsale`: added internal `_extendTime(uint256 newClosingTime)` as well as `TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime)` event allowing to extend the crowdsale, as long as it hasn't already closed. - -### Improvements - -- Upgraded the minimum compiler version to v0.5.2: this removes many Solidity warnings that were false positives. ([#1606](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1606)) -- `ECDSA`: `recover` no longer accepts malleable signatures (those using upper-range values for `s`, or 0/1 for `v`). ([#1622](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1622)) -- `ERC721`'s transfers are now more gas efficient due to removal of unnecessary `SafeMath` calls. ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610)) -- Fixed variable shadowing issues. ([#1606](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1606)) - -### Bugfixes - -- (minor) `SafeERC20`: `safeApprove` wasn't properly checking for a zero allowance when attempting to set a non-zero allowance. ([#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647)) - -### Breaking changes in drafts - -- `TokenMetadata` has been renamed to `ERC20Metadata`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618)) -- The library `Counter` has been renamed to `Counters` and its API has been improved. See an example in `ERC721`, lines [17](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L17) and [204](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L204). ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610)) - -## 2.1.3 (2019-02-26) - -- Backported `SafeERC20.safeApprove` bugfix. ([#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647)) - -## 2.1.2 (2019-01-17) - -- Removed most of the test suite from the npm package, except `PublicRole.behavior.js`, which may be useful to users testing their own `Roles`. - -## 2.1.1 (2019-01-04) - -- Version bump to avoid conflict in the npm registry. - -## 2.1.0 (2019-01-04) - -### New features - -- Now targeting the 0.5.x line of Solidity compilers. For 0.4.24 support, use version 2.0 of OpenZeppelin. -- `WhitelistCrowdsale`: a crowdsale where only whitelisted accounts (`WhitelistedRole`) can purchase tokens. Adding or removing accounts from the whitelist is done by whitelist admins (`WhitelistAdminRole`). Similar to the pre-2.0 `WhitelistedCrowdsale`. ([#1525](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1525), [#1589](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1589)) -- `RefundablePostDeliveryCrowdsale`: replacement for `RefundableCrowdsale` (deprecated, see below) where tokens are only granted once the crowdsale ends (if it meets its goal). ([#1543](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1543)) -- `PausableCrowdsale`: allows for pausers (`PauserRole`) to pause token purchases. Other crowdsale operations (e.g. withdrawals and refunds, if applicable) are not affected. ([#832](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/832)) -- `ERC20`: `transferFrom` and `_burnFrom ` now emit `Approval` events, to represent the token's state comprehensively through events. ([#1524](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1524)) -- `ERC721`: added `_burn(uint256 tokenId)`, replacing the similar deprecated function (see below). ([#1550](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1550)) -- `ERC721`: added `_tokensOfOwner(address owner)`, allowing to internally retrieve the array of an account's owned tokens. ([#1522](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1522)) -- Crowdsales: all constructors are now `public`, meaning it is not necessary to extend these contracts in order to deploy them. The exception is `FinalizableCrowdsale`, since it is meaningless unless extended. ([#1564](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1564)) -- `SignedSafeMath`: added overflow-safe operations for signed integers (`int256`). ([#1559](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1559), [#1588](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1588)) - -### Improvements - -- The compiler version required by `Array` was behind the rest of the library so it was updated to `v0.4.24`. ([#1553](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1553)) -- Now conforming to a 4-space indentation code style. ([1508](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1508)) -- `ERC20`: more gas efficient due to removed redundant `require`s. ([#1409](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1409)) -- `ERC721`: fixed a bug that prevented internal data structures from being properly cleaned, missing potential gas refunds. ([#1539](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1539) and [#1549](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1549)) -- `ERC721`: general gas savings on `transferFrom`, `_mint` and `_burn`, due to redundant `require`s and `SSTORE`s. ([#1549](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1549)) - -### Bugfixes - -### Breaking changes - -### Deprecations - -- `ERC721._burn(address owner, uint256 tokenId)`: due to the `owner` parameter being unnecessary. ([#1550](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1550)) -- `RefundableCrowdsale`: due to trading abuse potential on crowdsales that miss their goal. ([#1543](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1543)) diff --git a/lib_openzeppelin_contracts/CODE_OF_CONDUCT.md b/lib_openzeppelin_contracts/CODE_OF_CONDUCT.md deleted file mode 100644 index cd7770d..0000000 --- a/lib_openzeppelin_contracts/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,73 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at contact@openzeppelin.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org diff --git a/lib_openzeppelin_contracts/CONTRIBUTING.md b/lib_openzeppelin_contracts/CONTRIBUTING.md deleted file mode 100644 index 1a44cc2..0000000 --- a/lib_openzeppelin_contracts/CONTRIBUTING.md +++ /dev/null @@ -1,36 +0,0 @@ -# Contributing Guidelines - -There are many ways to contribute to OpenZeppelin Contracts. - -## Troubleshooting - -You can help other users in the community to solve their smart contract issues in the [OpenZeppelin Forum]. - -[OpenZeppelin Forum]: https://forum.openzeppelin.com/ - -## Opening an issue - -You can [open an issue] to suggest a feature or report a minor bug. For serious bugs please do not open an issue, instead refer to our [security policy] for appropriate steps. - -If you believe your issue may be due to user error and not a problem in the library, consider instead posting a question on the [OpenZeppelin Forum]. - -Before opening an issue, be sure to search through the existing open and closed issues, and consider posting a comment in one of those instead. - -When requesting a new feature, include as many details as you can, especially around the use cases that motivate it. Features are prioritized according to the impact they may have on the ecosystem, so we appreciate information showing that the impact could be high. - -[security policy]: https://github.com/OpenZeppelin/openzeppelin-contracts/security -[open an issue]: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/new/choose - -## Submitting a pull request - -If you would like to contribute code or documentation you may do so by forking the repository and submitting a pull request. - -Any non-trivial code contribution must be first discussed with the maintainers in an issue (see [Opening an issue](#opening-an-issue)). Only very minor changes are accepted without prior discussion. - -Make sure to read and follow the [engineering guidelines](./GUIDELINES.md). Run linter and tests to make sure your pull request is good before submitting it. - -Changelog entries should be added to each pull request by using [Changesets](https://github.com/changesets/changesets/). - -When opening the pull request you will be presented with a template and a series of instructions. Read through it carefully and follow all the steps. Expect a review and feedback from the maintainers afterwards. - -If you're looking for a good place to start, look for issues labelled ["good first issue"](https://github.com/OpenZeppelin/openzeppelin-contracts/labels/good%20first%20issue)! diff --git a/lib_openzeppelin_contracts/FUNDING.json b/lib_openzeppelin_contracts/FUNDING.json deleted file mode 100644 index c672862..0000000 --- a/lib_openzeppelin_contracts/FUNDING.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "drips": { - "ethereum": { - "ownedBy": "0xAeb37910f93486C85A1F8F994b67E8187554d664" - } - } -} diff --git a/lib_openzeppelin_contracts/GUIDELINES.md b/lib_openzeppelin_contracts/GUIDELINES.md deleted file mode 100644 index 97fa729..0000000 --- a/lib_openzeppelin_contracts/GUIDELINES.md +++ /dev/null @@ -1,148 +0,0 @@ -# Engineering Guidelines - -## Testing - -Code must be thoroughly tested with quality unit tests. - -We defer to the [Moloch Testing Guide](https://github.com/MolochVentures/moloch/tree/master/test#readme) for specific recommendations, though not all of it is relevant here. Note the introduction: - -> Tests should be written, not only to verify correctness of the target code, but to be comprehensively reviewed by other programmers. Therefore, for mission critical Solidity code, the quality of the tests are just as important (if not more so) than the code itself, and should be written with the highest standards of clarity and elegance. - -Every addition or change to the code must come with relevant and comprehensive tests. - -Refactors should avoid simultaneous changes to tests. - -Flaky tests are not acceptable. - -The test suite should run automatically for every change in the repository, and in pull requests tests must pass before merging. - -The test suite coverage must be kept as close to 100% as possible, enforced in pull requests. - -In some cases unit tests may be insufficient and complementary techniques should be used: - -1. Property-based tests (aka. fuzzing) for math-heavy code. -2. Formal verification for state machines. - -## Code style - -Solidity code should be written in a consistent format enforced by a linter, following the official [Solidity Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html). See below for further [Solidity Conventions](#solidity-conventions). - -The code should be simple and straightforward, prioritizing readability and understandability. Consistency and predictability should be maintained across the codebase. In particular, this applies to naming, which should be systematic, clear, and concise. - -Sometimes these guidelines may be broken if doing so brings significant efficiency gains, but explanatory comments should be added. - -Modularity should be pursued, but not at the cost of the above priorities. - -## Documentation - -For contributors, project guidelines and processes must be documented publicly. - -For users, features must be abundantly documented. Documentation should include answers to common questions, solutions to common problems, and recommendations for critical decisions that the user may face. - -All changes to the core codebase (excluding tests, auxiliary scripts, etc.) must be documented in a changelog, except for purely cosmetic or documentation changes. - -## Peer review - -All changes must be submitted through pull requests and go through peer code review. - -The review must be approached by the reviewer in a similar way as if it was an audit of the code in question (but importantly it is not a substitute for and should not be considered an audit). - -Reviewers should enforce code and project guidelines. - -External contributions must be reviewed separately by multiple maintainers. - -## Automation - -Automation should be used as much as possible to reduce the possibility of human error and forgetfulness. - -Automations that make use of sensitive credentials must use secure secret management, and must be strengthened against attacks such as [those on GitHub Actions worklows](https://github.com/nikitastupin/pwnhub). - -Some other examples of automation are: - -- Looking for common security vulnerabilities or errors in our code (eg. reentrancy analysis). -- Keeping dependencies up to date and monitoring for vulnerable dependencies. - -## Pull requests - -Pull requests are squash-merged to keep the `master` branch history clean. The title of the pull request becomes the commit message, so it should be written in a consistent format: - -1) Begin with a capital letter. -2) Do not end with a period. -3) Write in the imperative: "Add feature X" and not "Adds feature X" or "Added feature X". - -This repository does not follow conventional commits, so do not prefix the title with "fix:" or "feat:". - -Work in progress pull requests should be submitted as Drafts and should not be prefixed with "WIP:". - -Branch names don't matter, and commit messages within a pull request mostly don't matter either, although they can help the review process. - -# Solidity Conventions - -In addition to the official Solidity Style Guide we have a number of other conventions that must be followed. - -* All state variables should be private. - - Changes to state should be accompanied by events, and in some cases it is not correct to arbitrarily set state. Encapsulating variables as private and only allowing modification via setters enables us to ensure that events and other rules are followed reliably and prevents this kind of user error. - -* Internal or private state variables or functions should have an underscore prefix. - - ```solidity - contract TestContract { - uint256 private _privateVar; - uint256 internal _internalVar; - function _testInternal() internal { ... } - function _testPrivate() private { ... } - } - ``` - -* Functions should be declared virtual, with few exceptions listed below. The - contract logic should be written considering that these functions may be - overridden by developers, e.g. getting a value using an internal getter rather - than reading directly from a state variable. - - If function A is an "alias" of function B, i.e. it invokes function B without - significant additional logic, then function A should not be virtual so that - any user overrides are implemented on B, preventing inconsistencies. - -* Events should generally be emitted immediately after the state change that they - represent, and should be named in the past tense. Some exceptions may be made for gas - efficiency if the result doesn't affect observable ordering of events. - - ```solidity - function _burn(address who, uint256 value) internal { - super._burn(who, value); - emit TokensBurned(who, value); - } - ``` - - Some standards (e.g. ERC-20) use present tense, and in those cases the - standard specification is used. - -* Interface names should have a capital I prefix. - - ```solidity - interface IERC777 { - ``` - -* Contracts not intended to be used standalone should be marked abstract - so they are required to be inherited to other contracts. - - ```solidity - abstract contract AccessControl is ..., { - ``` - -* 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: - - * The domain prefix should be picked in the following order: - 1. Use `ERC` if the error is a violation of an ERC specification. - 2. Use the name of the underlying component where it belongs (eg. `Governor`, `ECDSA`, or `Timelock`). - - * The location of custom errors should be decided in the following order: - 1. Take the errors from their underlying ERCs if they're already defined. - 2. Declare the errors in the underlying interface/library if the error makes sense in its context. - 3. Declare the error in the implementation if the underlying interface/library is not suitable to do so (eg. interface/library already specified in an ERC). - 4. Declare the error in an extension if the error only happens in such extension or child contracts. - - * Custom error names should not be declared twice along the library to avoid duplicated identifier declarations when inheriting from multiple contracts. diff --git a/lib_openzeppelin_contracts/RELEASING.md b/lib_openzeppelin_contracts/RELEASING.md deleted file mode 100644 index 06dd218..0000000 --- a/lib_openzeppelin_contracts/RELEASING.md +++ /dev/null @@ -1,45 +0,0 @@ -# Releasing - -OpenZeppelin Contracts uses a fully automated release process that takes care of compiling, packaging, and publishing the library, all of which is carried out in a clean CI environment (GitHub Actions), implemented in the ([`release-cycle`](.github/workflows/release-cycle.yml)) workflow. This helps to reduce the potential for human error and inconsistencies, and ensures that the release process is ongoing and reliable. - -## Changesets - -[Changesets](https://github.com/changesets/changesets/) is used as part of our release process for `CHANGELOG.md` management. Each change that is relevant for the codebase is expected to include a changeset. - -## Branching model - -The release cycle happens on release branches called `release-vX.Y`. Each of these branches starts as a release candidate (rc) and is eventually promoted to final. - -A release branch can be updated with cherry-picked patches from `master`, or may sometimes be committed to directly in the case of old releases. These commits will lead to a new release candidate or a patch increment depending on the state of the release branch. - -```mermaid - %%{init: {'gitGraph': {'mainBranchName': 'master'}} }%% - gitGraph - commit id: "Feature A" - commit id: "Feature B" - branch release-vX.Y - commit id: "Start release" - commit id: "Release vX.Y.0-rc.0" - - checkout master - commit id: "Feature C" - commit id: "Fix A" - - checkout release-vX.Y - cherry-pick id: "Fix A" tag: "" - commit id: "Release vX.Y.0-rc.1" - commit id: "Release vX.Y.0" - - checkout master - merge release-vX.Y - commit id: "Feature D" - commit id: "Patch B" - - checkout release-vX.Y - cherry-pick id: "Patch B" tag: "" - commit id: "Release vX.Y.1" - - checkout master - merge release-vX.Y - commit id: "Feature E" -``` diff --git a/lib_openzeppelin_contracts/SECURITY.md b/lib_openzeppelin_contracts/SECURITY.md deleted file mode 100644 index 9922c45..0000000 --- a/lib_openzeppelin_contracts/SECURITY.md +++ /dev/null @@ -1,43 +0,0 @@ -# Security Policy - -Security vulnerabilities should be disclosed to the project maintainers through [Immunefi], or alternatively by email to security@openzeppelin.com. - -[Immunefi]: https://immunefi.com/bounty/openzeppelin - -## Bug Bounty - -Responsible disclosure of security vulnerabilities is rewarded through a bug bounty program on [Immunefi]. - -There is a bonus reward for issues introduced in release candidates that are reported before making it into a stable release. Learn more about release candidates at [`RELEASING.md`](./RELEASING.md). - -## Security Patches - -Security vulnerabilities will be patched as soon as responsibly possible, and published as an advisory on this repository (see [advisories]) and on the affected npm packages. - -[advisories]: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories - -Projects that build on OpenZeppelin Contracts are encouraged to clearly state, in their source code and websites, how to be contacted about security issues in the event that a direct notification is considered necessary. We recommend including it in the NatSpec for the contract as `/// @custom:security-contact security@example.com`. - -Additionally, we recommend installing the library through npm and setting up vulnerability alerts such as [Dependabot]. - -[Dependabot]: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security#what-is-dependabot - -### Supported Versions - -Security patches will be released for the latest minor of a given major release. For example, if an issue is found in versions >=4.6.0 and the latest is 4.8.0, the patch will be released only in version 4.8.1. - -Only critical severity bug fixes will be backported to past major releases. - -| Version | Critical security fixes | Other security fixes | -| ------- | ----------------------- | -------------------- | -| 5.x | :white_check_mark: | :white_check_mark: | -| 4.9 | :white_check_mark: | :x: | -| 3.4 | :white_check_mark: | :x: | -| 2.5 | :x: | :x: | -| < 2.0 | :x: | :x: | - -Note as well that the Solidity language itself only guarantees security updates for the latest release. - -## Legal - -Smart contracts are a nascent technology and carry a high level of technical risk and uncertainty. OpenZeppelin Contracts is made available under the MIT License, which disclaims all warranties in relation to the project and which limits the liability of those that contribute and maintain the project, including OpenZeppelin. Your use of the project is also governed by the terms found at www.openzeppelin.com/tos (the "Terms"). As set out in the Terms, you are solely responsible for any use of OpenZeppelin Contracts and you assume all risks associated with any such use. This Security Policy in no way evidences or represents an on-going duty by any contributor, including OpenZeppelin, to correct any flaws or alert you to all or any of the potential risks of utilizing the project. diff --git a/lib_openzeppelin_contracts/audits/2017-03.md b/lib_openzeppelin_contracts/audits/2017-03.md deleted file mode 100644 index 4cd6dbf..0000000 --- a/lib_openzeppelin_contracts/audits/2017-03.md +++ /dev/null @@ -1,292 +0,0 @@ -# OpenZeppelin Audit - -NOTE ON 2021-07-19: This report makes reference to Zeppelin, OpenZeppelin, OpenZeppelin Contracts, the OpenZeppelin team, and OpenZeppelin library. Many of these things have since been renamed and know that this audit applies to what is currently called the OpenZeppelin Contracts which are maintained by the OpenZeppelin Contracts Community. - -March, 2017 -Authored by Dennis Peterson and Peter Vessenes - -# Introduction - -Zeppelin requested that New Alchemy perform an audit of the contracts in their OpenZeppelin library. The OpenZeppelin contracts are a set of contracts intended to be a safe building block for a variety of uses by parties that may not be as sophisticated as the OpenZeppelin team. It is a design goal that the contracts be deployable safely and "as-is". - -The contracts are hosted at: - -https://github.com/OpenZeppelin/zeppelin-solidity - -All the contracts in the "contracts" folder are in scope. - -The git commit hash we evaluated is: -9c5975a706b076b7000e8179f8101e0c61024c87 - -# Disclaimer - -The audit makes no statements or warrantees about utility of the code, safety of the code, suitability of the business model, regulatory regime for the business model, or any other statements about fitness of the contracts to purpose, or their bugfree status. The audit documentation is for discussion purposes only. - -# Executive Summary - -Overall the OpenZeppelin codebase is of reasonably high quality -- it is clean, modular and follows best practices throughout. - -It is still in flux as a codebase, and needs better documentation per file as to expected behavior and future plans. It probably needs more comprehensive and aggressive tests written by people less nice than the current OpenZeppelin team. - -We identified two critical errors and one moderate issue, and would not recommend this commit hash for public use until these bugs are remedied. - -The repository includes a set of Truffle unit tests, a requirement and best practice for smart contracts like these; we recommend these be bulked up. - -# Discussion - -## Big Picture: Is This A Worthwhile Project? - -As soon as a developer touches OpenZeppelin contracts, they will modify something, leaving them in an un-audited state. We do not recommend developers deploy any unaudited code to the Blockchain if it will handle money, information or other things of value. - -> "In accordance with Unix philosophy, Perl gives you enough rope to hang yourself" -> --Larry Wall - -We think this is an incredibly worthwhile project -- aided by the high code quality. Creating a framework that can be easily extended helps increase the average code quality on the Blockchain by charting a course for developers and encouraging containment of modifications to certain sections. - -> "Rust: The language that makes you take the safety off before shooting yourself in the foot" -> -- (@mbrubeck) - -We think much more could be done here, and recommend the OpenZeppelin team keep at this and keep focusing on the design goal of removing rope and adding safety. - -## Solidity Version Updates Recommended - -Most of the code uses Solidity 0.4.11, but some files under `Ownership` are marked 0.4.0. These should be updated. - -Solidity 0.4.10 will add several features which could be useful in these contracts: - -- `assert(condition)`, which throws if the condition is false - -- `revert()`, which rolls back without consuming all remaining gas. - -- `address.transfer(value)`, which is like `send` but automatically propagates exceptions, and supports `.gas()`. See https://github.com/ethereum/solidity/issues/610 for more on this. - -## Error Handling: Throw vs Return False -Solidity standards allow two ways to handle an error -- either calling `throw` or returning `false`. Both have benefits. In particular, a `throw` guarantees a complete wipe of the call stack (up to the preceding external call), whereas `false` allows a function to continue. - -In general we prefer `throw` in our code audits, because it is simpler -- it's less for an engineer to keep track of. Returning `false` and using logic to check results can quickly become a poorly-tracked state machine, and this sort of complexity can cause errors. - -In the OpenZeppelin contracts, both styles are used in different parts of the codebase. `SimpleToken` transfers throw upon failure, while the full ERC20 token returns `false`. Some modifiers `throw`, others just wrap the function body in a conditional, effectively allowing the function to return false if the condition is not met. - -We don't love this, and would usually recommend you stick with one style or the other throughout the codebase. - -In at least one case, these different techniques are combined cleverly (see the Multisig comments, line 65). As a set of contracts intended for general use, we recommend you either strive for more consistency or document explicit design criteria that govern which techniques are used where. - -Note that it may be impossible to use either one in all situations. For example, SafeMath functions pretty much have to throw upon failure, but ERC20 specifies returning booleans. Therefore we make no particular recommendations, but simply point out inconsistencies to consider. - -# Critical Issues - -## Stuck Ether in Crowdsale contract -CrowdsaleToken.sol has no provision for withdrawing the raised ether. We *strongly* recommend a standard `withdraw` function be added. There is no scenario in which someone should deploy this contract as is, whether for testing or live. - -## Recursive Call in MultisigWallet -Line 45 of `MultisigWallet.sol` checks if the amount being sent by `execute` is under a daily limit. - -This function can only be called by the "Owner". As a first angle of attack, it's worth asking what will happen if the multisig wallet owners reset the daily limit by approving a call to `resetSpentToday`. - -If a chain of calls can be constructed in which the owner confirms the `resetSpentToday` function and then withdraws through `execute` in a recursive call, the contract can be drained. In fact, this could be done without a recursive call, just through repeated `execute` calls alternating with the `confirm` calls. - -We are still working through the confirmation protocol in `Shareable.sol`, but we are not convinced that this is impossible, in fact it looks possible. The flexibility any shared owner has in being able to revoke confirmation later is another worrisome angle of approach even if some simple patches are included. - -This bug has a number of causes that need to be addressed: - -1. `resetSpentToday` and `confirm` together do not limit the days on which the function can be called or (it appears) the number of times it can be called. -1. Once a call has been confirmed and `execute`d it appears that it can be re-executed. This is not good. -3. `confirmandCheck` doesn't seem to have logic about whether or not the function in question has been called. -4. Even if it did, `revoke` would need updates and logic to deal with revocation requests after a function call had been completed. - -We do not recommend using the MultisigWallet until these issues are fixed. - -# Moderate to Minor Issues - -## PullPayment -PullPayment.sol needs some work. It has no explicit provision for cancelling a payment. This would be desirable in a number of scenarios; consider a payee losing their wallet, or giving a griefing address, or just an address that requires more than the default gas offered by `send`. - -`asyncSend` has no overflow checking. This is a bad plan. We recommend overflow and underflow checking at the layer closest to the data manipulation. - -`asyncSend` allows more balance to be queued up for sending than the contract holds. This is probably a bad idea, or at the very least should be called something different. If the intent is to allow this, it should have provisions for dealing with race conditions between competing `withdrawPayments` calls. - -It would be nice to see how many payments are pending. This would imply a bit of a rewrite; we recommend this contract get some design time, and that developers don't rely on it in its current state. - -## Shareable Contract - -We do not believe the `Shareable.sol` contract is ready for primetime. It is missing functions, and as written may be vulnerable to a reordering attack -- an attack in which a miner or other party "racing" with a smart contract participant inserts their own information into a list or mapping. - -The confirmation and revocation code needs to be looked over with a very careful eye imagining extraordinarily bad behavior by shared owners before this contract can be called safe. - -No sanity checks on the initial constructor's `required` argument are worrisome as well. - -# Line by Line Comments - -## Lifecycle - -### Killable - -Very simple, allows owner to call selfdestruct, sending funds to owner. No issues. However, note that `selfdestruct` should typically not be used; it is common that a developer may want to access data in a former contract, and they may not understand that `selfdestruct` limits access to the contract. We recommend better documentation about this dynamic, and an alternate function name for `kill` like `completelyDestroy` while `kill` would perhaps merely send funds to the owner. - -Also note that a killable function allows the owner to take funds regardless of other logic. This may be desirable or undesirable depending on the circumstances. Perhaps `Killable` should have a different name as well. - -### Migrations - -I presume that the goal of this contract is to allow and annotate a migration to a new smart contract address. We are not clear here how this would be accomplished by the code; we'd like to review with the OpenZeppelin team. - -### Pausable - -We like these pauses! Note that these allow significant griefing potential by owners, and that this might not be obvious to participants in smart contracts using the OpenZeppelin framework. We would recommend that additional sample logic be added to for instance the TokenContract showing safer use of the pause and resume functions. In particular, we would recommend a timelock after which anyone could unpause the contract. - -The modifiers use the pattern `if(bool){_;}`. This is fine for functions that return false upon failure, but could be problematic for functions expected to throw upon failure. See our comments above on standardizing on `throw` or `return(false)`. - -## Ownership - -### Ownable - -Line 19: Modifier throws if doesn't meet condition, in contrast to some other inheritable modifiers (e.g. in Pausable) that use `if(bool){_;}`. - -### Claimable - -Inherits from Ownable but the existing owner sets a pendingOwner who has to claim ownership. - -Line 17: Another modifier that throws. - -### DelayedClaimable - -Is there any reason to descend from Ownable directly, instead of just Claimable, which descends from Ownable? If not, descending from both just adds confusion. - -### Contactable - -Allows owner to set a public string of contract information. No issues. - -### Shareable - -This needs some work. Doesn't check if `_required <= len(_owners)` for instance, that would be a bummer. What if _required were like `MAX - 1`? - -I have a general concern about the difference between `owners`, `_owners`, and `owner` in `Ownable.sol`. I recommend "Owners" be renamed. In general we do not recomment single character differences in variable names, although a preceding underscore is not uncommon in Solidity code. - -Line 34: "this contract only has six types of events"...actually only two. - -Line 61: Why is `ownerIndex` keyed by addresses hashed to `uint`s? Why not use the addresses directly, so `ownerIndex` is less obscure, and so there's stronger typing? - -Line 62: Do not love `++i) ... owners[2+ i]`. Makes me do math, which is not what I want to do. I want to not have to do math. - -There should probably be a function for adding a new operation, so the developer doesn't have to work directly with the internal data. (This would make the multisig contract even shorter.) - -There's a `revoke` function but not a `propose` function that we can see. - -Beware reordering. If `propose` allows the user to choose a bytes string for their proposal, bad things(TM) will happen as currently written. - - -### Multisig - -Just an interface. Note it allows changing an owner address, but not changing the number of owners. This is somewhat limiting but also simplifies implementation. - -## Payment - -### PullPayment - -Safe from reentrance attack since ether send is at the end, plus it uses `.send()` rather than `.call.value()`. - -There's an argument to be made that `.call.value()` is a better option *if* you're sure that it will be done after all state updates, since `.send` will fail if the recipient has an expensive fallback function. However, in the context of a function meant to be embedded in other contracts, it's probably better to use `.send`. One possible compromise is to add a function which allows only the owner to send ether via `.call.value`. - -If you don't use `call.value` you should implement a `cancel` function in case some value is pending here. - -Line 14: -Doesn't use safeAdd. Although it appears that payout amounts can only be increased, in fact the payer could lower the payout as much as desired via overflow. Also, the payer could add a large non-overflowing amount, causing the payment to exceed the contract balance and therefore fail when withdraw is attempted. - -Recommendation: track the sum of non-withdrawn asyncSends, and don't allow a new one which exceeds the leftover balance. If it's ever desirable to make payments revocable, it should be done explicitly. - -## Tokens - -### ERC20 - -Standard ERC20 interface only. - -There's a security hole in the standard, reported at Edcon: `approve` does not protect against race conditions and simply replaces the current value. An approved spender could wait for the owner to call `approve` again, then attempt to spend the old limit before the new limit is applied. If successful, this attacker could successfully spend the sum of both limits. - -This could be fixed by either (1) including the old limit as a parameter, so the update will fail if some gets spent, or (2) using the value parameter as a delta instead of replacement value. - -This is not fixable while adhering to the current full ERC20 standard, though it would be possible to add a "secureApprove" function. The impact isn't extreme since at least you can only be attacked by addresses you approved. Also, users could mitigate this by always setting spending limits to zero and checking for spends, before setting the new limit. - -Edcon slides: -https://drive.google.com/file/d/0ByMtMw2hul0EN3NCaVFHSFdxRzA/view - -### ERC20Basic - -Simpler interface skipping the Approve function. Note this departs from ERC20 in another way: transfer throws instead of returning false. - -### BasicToken - -Uses `SafeSub` and `SafeMath`, so transfer `throw`s instead of returning false. This complies with ERC20Basic but not the actual ERC20 standard. - -### StandardToken - -Implementation of full ERC20 token. - -Transfer() and transferFrom() use SafeMath functions, which will cause them to throw instead of returning false. Not a security issue but departs from standard. - -### SimpleToken - -Sample instantiation of StandardToken. Note that in this sample, decimals is 18 and supply only 10,000, so the supply is a small fraction of a single nominal token. - -### CrowdsaleToken - -StandardToken which mints tokens at a fixed price when sent ether. - -There's no provision for owner withdrawing the ether. As a sample for crowdsales it should be Ownable and allow the owner to withdraw ether, rather than stranding the ether in the contract. - -Note: an alternative pattern is a mint() function which is only callable from a separate crowdsale contract, so any sort of rules can be added without modifying the token itself. - -### VestedToken - -Lines 23, 27: -Functions `transfer()` and `transferFrom()` have a modifier canTransfer which throws if not enough tokens are available. However, transfer() returns a boolean success. Inconsistent treatment of failure conditions may cause problems for other contracts using the token. (Note that transferableTokens() relies on safeSub(), so will also throw if there's insufficient balance.) - -Line 64: -Delete not actually necessary since the value is overwritten in the next line anyway. - -## Root level - -### Bounty - -Avoids potential race condition by having each researcher deploy a separate contract for attack; if a research manages to break his associated contract, other researchers can't immediately claim the reward, they have to reproduce the attack in their own contracts. - -A developer could subvert this intent by implementing `deployContract()` to always return the same address. However, this would break the `researchers` mapping, updating the researcher address associated with the contract. This could be prevented by blocking rewrites in `researchers`. - -### DayLimit - -The modifier `limitedDaily` calls `underLimit`, which both checks that the spend is below the daily limit, and adds the input value to the daily spend. This is fine if all functions throw upon failure. However, not all OpenZeppelin functions do this; there are functions that returns false, and modifiers that wrap the function body in `if (bool) {_;}`. In these cases, `_value` will be added to `spentToday`, but ether may not actually be sent because other preconditions were not met. (However in the OpenZeppelin multisig this is not a problem.) - -Lines 4, 11: -Comment claims that `DayLimit` is multiowned, and Shareable is imported, but DayLimit does not actually inherit from Shareable. The intent may be for child contracts to inherit from Shareable (as Multisig does); in this case the import should be removed and the comment altered. - -Line 46: -Manual overflow check instead of using safeAdd. Since this is called from a function that throws upon failure anyway, there's no real downside to using safeAdd. - -### LimitBalance - -No issues. - -### MultisigWallet - -Lines 28, 76, 80: -`kill`, `setDailyLimit`, and `resetSpentToday` only happen with multisig approval, and hashes for these actions are logged by Shareable. However, they should probably post their own events for easy reading. - -Line 45: -This call to underLimit will reduce the daily limit, and then either throw or return 0. So in this case there's no danger that the limit will be reduced without the operation going through. - -Line 65: -Shareable's onlyManyOwners will take the user's confirmation, and execute the function body if and only if enough users have confirmed. Whole thing throws if the send fails, which will roll back the confirmation. Confirm returns false if not enough have confirmed yet, true if the whole thing succeeds, and throws only in the exceptional circumstance that the designated transaction unexpectedly fails. Elegant design. - -Line 68: -Throw here is good but note this function can fail either by returning false or by throwing. - -Line 92: -A bit odd to split `clearPending()` between this contract and Shareable. However this does allow contracts inheriting from Shareable to use custom structs for pending transactions. - - -### SafeMath - -Another interesting comment from the same Edcon presentation was that the overflow behavior of Solidity is undocumented, so in theory, source code that relies on it could break with a future revision. - -However, compiled code should be fine, and in the unlikely event that the compiler is revised in this way, there should be plenty of warning. (But this is an argument for keeping overflow checks isolated in SafeMath.) - -Aside from that small caveat, these are fine. - diff --git a/lib_openzeppelin_contracts/audits/2018-10.pdf b/lib_openzeppelin_contracts/audits/2018-10.pdf deleted file mode 100644 index d5bf127..0000000 Binary files a/lib_openzeppelin_contracts/audits/2018-10.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/audits/2022-10-Checkpoints.pdf b/lib_openzeppelin_contracts/audits/2022-10-Checkpoints.pdf deleted file mode 100644 index 7e0d083..0000000 Binary files a/lib_openzeppelin_contracts/audits/2022-10-Checkpoints.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/audits/2022-10-ERC4626.pdf b/lib_openzeppelin_contracts/audits/2022-10-ERC4626.pdf deleted file mode 100644 index 154fe75..0000000 Binary files a/lib_openzeppelin_contracts/audits/2022-10-ERC4626.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/audits/2023-05-v4.9.pdf b/lib_openzeppelin_contracts/audits/2023-05-v4.9.pdf deleted file mode 100644 index 21cd7f5..0000000 Binary files a/lib_openzeppelin_contracts/audits/2023-05-v4.9.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/audits/2023-10-v5.0.pdf b/lib_openzeppelin_contracts/audits/2023-10-v5.0.pdf deleted file mode 100644 index 991026e..0000000 Binary files a/lib_openzeppelin_contracts/audits/2023-10-v5.0.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/audits/README.md b/lib_openzeppelin_contracts/audits/README.md deleted file mode 100644 index 369d064..0000000 --- a/lib_openzeppelin_contracts/audits/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Audits - -| Date | Version | Commit | Auditor | Scope | Links | -| ------------ | ------- | --------- | ------------ | -------------------- | ----------------------------------------------------------- | -| October 2023 | v5.0.0 | `b5a3e69` | OpenZeppelin | v5.0 Changes | [🔗](./2023-10-v5.0.pdf) | -| May 2023 | v4.9.0 | `91df66c` | OpenZeppelin | v4.9 Changes | [🔗](./2023-05-v4.9.pdf) | -| October 2022 | v4.8.0 | `14f98db` | OpenZeppelin | ERC4626, Checkpoints | [🔗](./2022-10-ERC4626.pdf) [🔗](./2022-10-Checkpoints.pdf) | -| October 2018 | v2.0.0 | `dac5bcc` | LevelK | Everything | [🔗](./2018-10.pdf) | -| March 2017 | v1.0.4 | `9c5975a` | New Alchemy | Everything | [🔗](./2017-03.md) | - -# Formal Verification - -| Date | Version | Commit | Tool | Scope | Links | -| ------------ | ------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ | -| May 2022 | v4.7.0 | `109778c` | Certora | Initializable, GovernorPreventLateQuorum, ERC1155Burnable, ERC1155Pausable, ERC1155Supply, ERC1155Holder, ERC1155Receiver | [🔗](../certora/reports/2022-05.pdf) | -| March 2022 | v4.4.0 | `4088540` | Certora | ERC20Votes, ERC20FlashMint, ERC20Wrapper, TimelockController, ERC721Votes, Votes, AccessControl, ERC1155 | [🔗](../certora/reports/2022-03.pdf) | -| October 2021 | v4.4.0 | `4088540` | Certora | Governor, GovernorCountingSimple, GovernorProposalThreshold, GovernorTimelockControl, GovernorVotes, GovernorVotesQuorumFraction | [🔗](../certora/reports/2021-10.pdf) | diff --git a/lib_openzeppelin_contracts/certora/.gitignore b/lib_openzeppelin_contracts/certora/.gitignore deleted file mode 100644 index 893adcd..0000000 --- a/lib_openzeppelin_contracts/certora/.gitignore +++ /dev/null @@ -1 +0,0 @@ -patched diff --git a/lib_openzeppelin_contracts/certora/Makefile b/lib_openzeppelin_contracts/certora/Makefile deleted file mode 100644 index d57d2ff..0000000 --- a/lib_openzeppelin_contracts/certora/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -default: help - -SRC := ../contracts -DST := patched -DIFF := diff -SRCS := $(shell find $(SRC) -type f) -DSTS := $(shell find $(DST) -type f) -DIFFS := $(shell find $(DIFF) -type f) - -############################################################################### -# Apply all patches in the $DIFF folder to the $DST folder -apply: $(DST) $(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$(DIFFS))) - -# Reset the $DST folder -$(DST): FORCE - @rm -rf $@ - @cp -r $(SRC) $@ - -# Update a solidity file in the $DST directory using the corresponding patch -$(DST)/%.sol: FORCE | $(DST) - @echo Applying patch to $@ - @patch -p0 -d $(DST) < $(patsubst $(DST)_%,$(DIFF)/%.patch,$(subst /,_,$@)) - -############################################################################### -# Record all difference between $SRC and $DST in patches -record: $(DIFF) $(patsubst %,$(DIFF)/%.patch,$(subst /,_,$(subst $(SRC)/,,$(SRCS)) $(subst $(DST)/,,$(DSTS)))) - -# Create the $DIFF folder -$(DIFF): FORCE - @rm -rf $@ - @mkdir $@ - -# Create the patch file by comparing the source and the destination -$(DIFF)/%.patch: FORCE | $(DIFF) - @echo Generating patch $@ - @diff -ruN \ - $(patsubst $(DIFF)/%.patch,$(SRC)/%,$(subst _,/,$@)) \ - $(patsubst $(DIFF)/%.patch,$(DST)/%,$(subst _,/,$@)) \ - | sed 's+$(SRC)/++g' \ - | sed 's+$(DST)/++g' \ - > $@ - @[ -s $@ ] || rm $@ - -############################################################################### -help: - @echo "usage:" - @echo " make apply: create $(DST) directory by applying the patches to $(SRC)" - @echo " make record: record the patches capturing the differences between $(SRC) and $(DST)" - @echo " make clean: remove all generated files (those ignored by git)" - -clean: - git clean -fdX - -FORCE: ; diff --git a/lib_openzeppelin_contracts/certora/README.md b/lib_openzeppelin_contracts/certora/README.md deleted file mode 100644 index cd85ba3..0000000 --- a/lib_openzeppelin_contracts/certora/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Running the certora verification tool - -These instructions detail the process for running Certora Verification Tool on OpenZeppelin Contracts. - -Documentation for CVT and the specification language are available [here](https://certora.atlassian.net/wiki/spaces/CPD/overview). - -## Prerequisites - -Follow the [Certora installation guide](https://docs.certora.com/en/latest/docs/user-guide/getting-started/install.html) in order to get the Certora Prover Package and the `solc` executable folder in your path. - -> **Note** -> An API Key is required for local testing. Although the prover will run on a Github Actions' CI environment on selected Pull Requests. - -## Running the verification - -The Certora Verification Tool proves specs for contracts, which are defined by the `./specs.json` file along with their pre-configured options. - -The verification script `./run.js` is used to submit verification jobs to the Certora Verification service. - -You can run it from the root of the repository with the following command: - -```bash -node certora/run.js [[CONTRACT_NAME:]SPEC_NAME] [OPTIONS...] -``` - -Where: - -- `CONTRACT_NAME` matches the `contract` key in the `./spec.json` file and may be empty. It will run all matching contracts if not provided. -- `SPEC_NAME` refers to a `spec` key from the `./specs.json` file. It will run every spec if not provided. -- `OPTIONS` extend the [Certora Prover CLI options](https://docs.certora.com/en/latest/docs/prover/cli/options.html#certora-prover-cli-options) and will respect the preconfigured options in the `specs.json` file. - -> **Note** -> A single spec may be configured to run for multiple contracts, whereas a single contract may run multiple specs. - -Example usage: - -```bash -node certora/run.js AccessControl # Run the AccessControl spec against every contract implementing it -``` - -## Adapting to changes in the contracts - -Some of our rules require the code to be simplified in various ways. Our primary tool for performing these simplifications is to run verification on a contract that extends the original contracts and overrides some of the methods. These "harness" contracts can be found in the `certora/harness` directory. - -This pattern does require some modifications to the original code: some methods need to be made virtual or public, for example. These changes are handled by applying a patch -to the code before verification by running: - -```bash -make -C certora apply -``` - -Before running the `certora/run.js` script, it's required to apply the corresponding patches to the `contracts` directory, placing the output in the `certora/patched` directory. Then, the contracts are verified by running the verification for the `certora/patched` directory. - -If the original contracts change, it is possible to create a conflict with the patch. In this case, the verify scripts will report an error message and output rejected changes in the `patched` directory. After merging the changes, run `make record` in the `certora` directory; this will regenerate the patch file, which can then be checked into git. - -For more information about the `make` scripts available, run: - -```bash -make -C certora help -``` diff --git a/lib_openzeppelin_contracts/certora/diff/access_manager_AccessManager.sol.patch b/lib_openzeppelin_contracts/certora/diff/access_manager_AccessManager.sol.patch deleted file mode 100644 index 29ff923..0000000 --- a/lib_openzeppelin_contracts/certora/diff/access_manager_AccessManager.sol.patch +++ /dev/null @@ -1,97 +0,0 @@ ---- access/manager/AccessManager.sol 2023-10-05 12:17:09.694051809 -0300 -+++ access/manager/AccessManager.sol 2023-10-05 12:26:18.498688718 -0300 -@@ -6,7 +6,6 @@ - import {IAccessManaged} from "./IAccessManaged.sol"; - import {Address} from "../../utils/Address.sol"; - import {Context} from "../../utils/Context.sol"; --import {Multicall} from "../../utils/Multicall.sol"; - import {Math} from "../../utils/math/Math.sol"; - import {Time} from "../../utils/types/Time.sol"; - -@@ -57,7 +56,8 @@ - * mindful of the danger associated with functions such as {{Ownable-renounceOwnership}} or - * {{AccessControl-renounceRole}}. - */ --contract AccessManager is Context, Multicall, IAccessManager { -+// NOTE: The FV version of this contract doesn't include Multicall because CVL HAVOCs on any `delegatecall`. -+contract AccessManager is Context, IAccessManager { - using Time for *; - - // Structure that stores the details for a target contract. -@@ -105,7 +105,7 @@ - - // Used to identify operations that are currently being executed via {execute}. - // This should be transient storage when supported by the EVM. -- bytes32 private _executionId; -+ bytes32 internal _executionId; // private → internal for FV - - /** - * @dev Check that the caller is authorized to perform the operation, following the restrictions encoded in -@@ -253,6 +253,11 @@ - _setGrantDelay(roleId, newDelay); - } - -+ // Exposed for FV -+ function _getTargetAdminDelayFull(address target) internal view virtual returns (uint32, uint32, uint48) { -+ return _targets[target].adminDelay.getFull(); -+ } -+ - /** - * @dev Internal version of {grantRole} without access control. Returns true if the role was newly granted. - * -@@ -287,6 +292,11 @@ - return newMember; - } - -+ // Exposed for FV -+ function _getRoleGrantDelayFull(uint64 roleId) internal view virtual returns (uint32, uint32, uint48) { -+ return _roles[roleId].grantDelay.getFull(); -+ } -+ - /** - * @dev Internal version of {revokeRole} without access control. This logic is also used by {renounceRole}. - * Returns true if the role was previously granted. -@@ -586,7 +596,7 @@ - /** - * @dev Check if the current call is authorized according to admin logic. - */ -- function _checkAuthorized() private { -+ function _checkAuthorized() internal virtual { // private → internal virtual for FV - address caller = _msgSender(); - (bool immediate, uint32 delay) = _canCallSelf(caller, _msgData()); - if (!immediate) { -@@ -609,7 +619,7 @@ - */ - function _getAdminRestrictions( - bytes calldata data -- ) private view returns (bool restricted, uint64 roleAdminId, uint32 executionDelay) { -+ ) internal view returns (bool restricted, uint64 roleAdminId, uint32 executionDelay) { // private → internal for FV - if (data.length < 4) { - return (false, 0, 0); - } -@@ -662,7 +672,7 @@ - address caller, - address target, - bytes calldata data -- ) private view returns (bool immediate, uint32 delay) { -+ ) internal view returns (bool immediate, uint32 delay) { // private → internal for FV - if (target == address(this)) { - return _canCallSelf(caller, data); - } else { -@@ -716,14 +726,14 @@ - /** - * @dev Extracts the selector from calldata. Panics if data is not at least 4 bytes - */ -- function _checkSelector(bytes calldata data) private pure returns (bytes4) { -+ function _checkSelector(bytes calldata data) internal pure returns (bytes4) { // private → internal for FV - return bytes4(data[0:4]); - } - - /** - * @dev Hashing function for execute protection - */ -- function _hashExecutionId(address target, bytes4 selector) private pure returns (bytes32) { -+ function _hashExecutionId(address target, bytes4 selector) internal pure returns (bytes32) { // private → internal for FV - return keccak256(abi.encode(target, selector)); - } - } diff --git a/lib_openzeppelin_contracts/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol deleted file mode 100644 index e96883f..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AccessControlDefaultAdminRules} from "../patched/access/extensions/AccessControlDefaultAdminRules.sol"; - -contract AccessControlDefaultAdminRulesHarness is AccessControlDefaultAdminRules { - uint48 private _delayIncreaseWait; - - constructor( - uint48 initialDelay, - address initialDefaultAdmin, - uint48 delayIncreaseWait - ) AccessControlDefaultAdminRules(initialDelay, initialDefaultAdmin) { - _delayIncreaseWait = delayIncreaseWait; - } - - // FV - function pendingDefaultAdmin_() external view returns (address) { - (address newAdmin, ) = pendingDefaultAdmin(); - return newAdmin; - } - - function pendingDefaultAdminSchedule_() external view returns (uint48) { - (, uint48 schedule) = pendingDefaultAdmin(); - return schedule; - } - - function pendingDelay_() external view returns (uint48) { - (uint48 newDelay, ) = pendingDefaultAdminDelay(); - return newDelay; - } - - function pendingDelaySchedule_() external view returns (uint48) { - (, uint48 schedule) = pendingDefaultAdminDelay(); - return schedule; - } - - function delayChangeWait_(uint48 newDelay) external view returns (uint48) { - return _delayChangeWait(newDelay); - } - - // Overrides - function defaultAdminDelayIncreaseWait() public view override returns (uint48) { - return _delayIncreaseWait; - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/AccessControlHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/AccessControlHarness.sol deleted file mode 100644 index e862d3e..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/AccessControlHarness.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AccessControl} from "../patched/access/AccessControl.sol"; - -contract AccessControlHarness is AccessControl {} diff --git a/lib_openzeppelin_contracts/certora/harnesses/AccessManagedHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/AccessManagedHarness.sol deleted file mode 100644 index 50be23a..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/AccessManagedHarness.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "../patched/access/manager/IAccessManager.sol"; -import "../patched/access/manager/AccessManaged.sol"; - -contract AccessManagedHarness is AccessManaged { - bytes internal SOME_FUNCTION_CALLDATA = abi.encodeCall(this.someFunction, ()); - - constructor(address initialAuthority) AccessManaged(initialAuthority) {} - - function someFunction() public restricted() { - // Sanity for FV: the msg.data when calling this function should be the same as the data used when checking - // the schedule. This is a reformulation of `msg.data == SOME_FUNCTION_CALLDATA` that focuses on the operation - // hash for this call. - require( - IAccessManager(authority()).hashOperation(_msgSender(), address(this), msg.data) - == - IAccessManager(authority()).hashOperation(_msgSender(), address(this), SOME_FUNCTION_CALLDATA) - ); - } - - function authority_canCall_immediate(address caller) public view returns (bool result) { - (result,) = AuthorityUtils.canCallWithDelay(authority(), caller, address(this), this.someFunction.selector); - } - - function authority_canCall_delay(address caller) public view returns (uint32 result) { - (,result) = AuthorityUtils.canCallWithDelay(authority(), caller, address(this), this.someFunction.selector); - } - - function authority_getSchedule(address caller) public view returns (uint48) { - IAccessManager manager = IAccessManager(authority()); - return manager.getSchedule(manager.hashOperation(caller, address(this), SOME_FUNCTION_CALLDATA)); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/AccessManagerHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/AccessManagerHarness.sol deleted file mode 100644 index 69295d4..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/AccessManagerHarness.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "../patched/access/manager/AccessManager.sol"; - -contract AccessManagerHarness is AccessManager { - // override with a storage slot that can basically take any value. - uint32 private _minSetback; - - constructor(address initialAdmin) AccessManager(initialAdmin) {} - - // FV - function minSetback() public view override returns (uint32) { - return _minSetback; - } - - function canCall_immediate(address caller, address target, bytes4 selector) external view returns (bool result) { - (result,) = canCall(caller, target, selector); - } - - function canCall_delay(address caller, address target, bytes4 selector) external view returns (uint32 result) { - (,result) = canCall(caller, target, selector); - } - - function canCallExtended(address caller, address target, bytes calldata data) external view returns (bool, uint32) { - return _canCallExtended(caller, target, data); - } - - function canCallExtended_immediate(address caller, address target, bytes calldata data) external view returns (bool result) { - (result,) = _canCallExtended(caller, target, data); - } - - function canCallExtended_delay(address caller, address target, bytes calldata data) external view returns (uint32 result) { - (,result) = _canCallExtended(caller, target, data); - } - - function getAdminRestrictions_restricted(bytes calldata data) external view returns (bool result) { - (result,,) = _getAdminRestrictions(data); - } - - function getAdminRestrictions_roleAdminId(bytes calldata data) external view returns (uint64 result) { - (,result,) = _getAdminRestrictions(data); - } - - function getAdminRestrictions_executionDelay(bytes calldata data) external view returns (uint32 result) { - (,,result) = _getAdminRestrictions(data); - } - - function hasRole_isMember(uint64 roleId, address account) external view returns (bool result) { - (result,) = hasRole(roleId, account); - } - - function hasRole_executionDelay(uint64 roleId, address account) external view returns (uint32 result) { - (,result) = hasRole(roleId, account); - } - - function getAccess_since(uint64 roleId, address account) external view returns (uint48 result) { - (result,,,) = getAccess(roleId, account); - } - - function getAccess_currentDelay(uint64 roleId, address account) external view returns (uint32 result) { - (,result,,) = getAccess(roleId, account); - } - - function getAccess_pendingDelay(uint64 roleId, address account) external view returns (uint32 result) { - (,,result,) = getAccess(roleId, account); - } - - function getAccess_effect(uint64 roleId, address account) external view returns (uint48 result) { - (,,,result) = getAccess(roleId, account); - } - - function getTargetAdminDelay_after(address target) public view virtual returns (uint32 result) { - (,result,) = _getTargetAdminDelayFull(target); - } - - function getTargetAdminDelay_effect(address target) public view virtual returns (uint48 result) { - (,,result) = _getTargetAdminDelayFull(target); - } - - function getRoleGrantDelay_after(uint64 roleId) public view virtual returns (uint32 result) { - (,result,) = _getRoleGrantDelayFull(roleId); - } - - function getRoleGrantDelay_effect(uint64 roleId) public view virtual returns (uint48 result) { - (,,result) = _getRoleGrantDelayFull(roleId); - } - - function hashExecutionId(address target, bytes4 selector) external pure returns (bytes32) { - return _hashExecutionId(target, selector); - } - - function executionId() external view returns (bytes32) { - return _executionId; - } - - // Pad with zeros (and don't revert) if data is too short. - function getSelector(bytes calldata data) external pure returns (bytes4) { - return bytes4(data); - } - - function getFirstArgumentAsAddress(bytes calldata data) external pure returns (address) { - return abi.decode(data[0x04:0x24], (address)); - } - - function getFirstArgumentAsUint64(bytes calldata data) external pure returns (uint64) { - return abi.decode(data[0x04:0x24], (uint64)); - } - - function _checkAuthorized() internal override { - // We need this hack otherwise certora will assume _checkSelector(_msgData()) can return anything :/ - require(msg.sig == _checkSelector(_msgData())); - super._checkAuthorized(); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/DoubleEndedQueueHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/DoubleEndedQueueHarness.sol deleted file mode 100644 index d684c73..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/DoubleEndedQueueHarness.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {DoubleEndedQueue} from "../patched/utils/structs/DoubleEndedQueue.sol"; - -contract DoubleEndedQueueHarness { - using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; - - DoubleEndedQueue.Bytes32Deque private _deque; - - function pushFront(bytes32 value) external { - _deque.pushFront(value); - } - - function pushBack(bytes32 value) external { - _deque.pushBack(value); - } - - function popFront() external returns (bytes32 value) { - return _deque.popFront(); - } - - function popBack() external returns (bytes32 value) { - return _deque.popBack(); - } - - function clear() external { - _deque.clear(); - } - - function begin() external view returns (uint128) { - return _deque._begin; - } - - function end() external view returns (uint128) { - return _deque._end; - } - - function length() external view returns (uint256) { - return _deque.length(); - } - - function empty() external view returns (bool) { - return _deque.empty(); - } - - function front() external view returns (bytes32 value) { - return _deque.front(); - } - - function back() external view returns (bytes32 value) { - return _deque.back(); - } - - function at_(uint256 index) external view returns (bytes32 value) { - return _deque.at(index); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/ERC20FlashMintHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/ERC20FlashMintHarness.sol deleted file mode 100644 index 2f989b2..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/ERC20FlashMintHarness.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "../patched/token/ERC20/ERC20.sol"; -import "../patched/token/ERC20/extensions/ERC20Permit.sol"; -import "../patched/token/ERC20/extensions/ERC20FlashMint.sol"; - -contract ERC20FlashMintHarness is ERC20, ERC20Permit, ERC20FlashMint { - uint256 someFee; - address someFeeReceiver; - - constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } - - // public accessor - function flashFeeReceiver() public view returns (address) { - return someFeeReceiver; - } - - // internal hook - function _flashFee(address, uint256) internal view override returns (uint256) { - return someFee; - } - - function _flashFeeReceiver() internal view override returns (address) { - return someFeeReceiver; - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/ERC20PermitHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/ERC20PermitHarness.sol deleted file mode 100644 index 08113f4..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/ERC20PermitHarness.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20Permit, ERC20} from "../patched/token/ERC20/extensions/ERC20Permit.sol"; - -contract ERC20PermitHarness is ERC20Permit { - constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} - - function mint(address account, uint256 amount) external { - _mint(account, amount); - } - - function burn(address account, uint256 amount) external { - _burn(account, amount); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/ERC20WrapperHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/ERC20WrapperHarness.sol deleted file mode 100644 index ca183ad..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/ERC20WrapperHarness.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {ERC20Permit} from "../patched/token/ERC20/extensions/ERC20Permit.sol"; -import {ERC20Wrapper, IERC20, ERC20} from "../patched/token/ERC20/extensions/ERC20Wrapper.sol"; - -contract ERC20WrapperHarness is ERC20Permit, ERC20Wrapper { - constructor( - IERC20 _underlying, - string memory _name, - string memory _symbol - ) ERC20(_name, _symbol) ERC20Permit(_name) ERC20Wrapper(_underlying) {} - - function underlyingTotalSupply() public view returns (uint256) { - return underlying().totalSupply(); - } - - function underlyingBalanceOf(address account) public view returns (uint256) { - return underlying().balanceOf(account); - } - - function underlyingAllowanceToThis(address account) public view returns (uint256) { - return underlying().allowance(account, address(this)); - } - - function recover(address account) public returns (uint256) { - return _recover(account); - } - - function decimals() public view override(ERC20Wrapper, ERC20) returns (uint8) { - return super.decimals(); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/ERC3156FlashBorrowerHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/ERC3156FlashBorrowerHarness.sol deleted file mode 100644 index 1c76da2..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/ERC3156FlashBorrowerHarness.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -import {IERC3156FlashBorrower} from "../patched/interfaces/IERC3156FlashBorrower.sol"; - -pragma solidity ^0.8.20; - -contract ERC3156FlashBorrowerHarness is IERC3156FlashBorrower { - bytes32 somethingToReturn; - - function onFlashLoan(address, address, uint256, uint256, bytes calldata) external view override returns (bytes32) { - return somethingToReturn; - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/ERC721Harness.sol b/lib_openzeppelin_contracts/certora/harnesses/ERC721Harness.sol deleted file mode 100644 index 69c4c20..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/ERC721Harness.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {ERC721} from "../patched/token/ERC721/ERC721.sol"; - -contract ERC721Harness is ERC721 { - constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - - function mint(address account, uint256 tokenId) external { - _mint(account, tokenId); - } - - function safeMint(address to, uint256 tokenId) external { - _safeMint(to, tokenId); - } - - function safeMint(address to, uint256 tokenId, bytes memory data) external { - _safeMint(to, tokenId, data); - } - - function burn(uint256 tokenId) external { - _burn(tokenId); - } - - function unsafeOwnerOf(uint256 tokenId) external view returns (address) { - return _ownerOf(tokenId); - } - - function unsafeGetApproved(uint256 tokenId) external view returns (address) { - return _getApproved(tokenId); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/ERC721ReceiverHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/ERC721ReceiverHarness.sol deleted file mode 100644 index 3843ef4..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/ERC721ReceiverHarness.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "../patched/interfaces/IERC721Receiver.sol"; - -contract ERC721ReceiverHarness is IERC721Receiver { - function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { - return this.onERC721Received.selector; - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/EnumerableMapHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/EnumerableMapHarness.sol deleted file mode 100644 index 6155193..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/EnumerableMapHarness.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {EnumerableMap} from "../patched/utils/structs/EnumerableMap.sol"; - -contract EnumerableMapHarness { - using EnumerableMap for EnumerableMap.Bytes32ToBytes32Map; - - EnumerableMap.Bytes32ToBytes32Map private _map; - - function set(bytes32 key, bytes32 value) public returns (bool) { - return _map.set(key, value); - } - - function remove(bytes32 key) public returns (bool) { - return _map.remove(key); - } - - function contains(bytes32 key) public view returns (bool) { - return _map.contains(key); - } - - function length() public view returns (uint256) { - return _map.length(); - } - - function key_at(uint256 index) public view returns (bytes32) { - (bytes32 key,) = _map.at(index); - return key; - } - - function value_at(uint256 index) public view returns (bytes32) { - (,bytes32 value) = _map.at(index); - return value; - } - - function tryGet_contains(bytes32 key) public view returns (bool) { - (bool contained,) = _map.tryGet(key); - return contained; - } - - function tryGet_value(bytes32 key) public view returns (bytes32) { - (,bytes32 value) = _map.tryGet(key); - return value; - } - - function get(bytes32 key) public view returns (bytes32) { - return _map.get(key); - } - - function _positionOf(bytes32 key) public view returns (uint256) { - return _map._keys._inner._positions[key]; - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/EnumerableSetHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/EnumerableSetHarness.sol deleted file mode 100644 index 09246de..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/EnumerableSetHarness.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {EnumerableSet} from "../patched/utils/structs/EnumerableSet.sol"; - -contract EnumerableSetHarness { - using EnumerableSet for EnumerableSet.Bytes32Set; - - EnumerableSet.Bytes32Set private _set; - - function add(bytes32 value) public returns (bool) { - return _set.add(value); - } - - function remove(bytes32 value) public returns (bool) { - return _set.remove(value); - } - - function contains(bytes32 value) public view returns (bool) { - return _set.contains(value); - } - - function length() public view returns (uint256) { - return _set.length(); - } - - function at_(uint256 index) public view returns (bytes32) { - return _set.at(index); - } - - function _positionOf(bytes32 value) public view returns (uint256) { - return _set._inner._positions[value]; - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/InitializableHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/InitializableHarness.sol deleted file mode 100644 index 743d677..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/InitializableHarness.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Initializable} from "../patched/proxy/utils/Initializable.sol"; - -contract InitializableHarness is Initializable { - function initialize() public initializer {} - function reinitialize(uint64 n) public reinitializer(n) {} - function disable() public { _disableInitializers(); } - - function nested_init_init() public initializer { initialize(); } - function nested_init_reinit(uint64 m) public initializer { reinitialize(m); } - function nested_reinit_init(uint64 n) public reinitializer(n) { initialize(); } - function nested_reinit_reinit(uint64 n, uint64 m) public reinitializer(n) { reinitialize(m); } - - function version() public view returns (uint64) { - return _getInitializedVersion(); - } - - function initializing() public view returns (bool) { - return _isInitializing(); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/NoncesHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/NoncesHarness.sol deleted file mode 100644 index beea5fd..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/NoncesHarness.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Nonces} from "../patched/utils/Nonces.sol"; - -contract NoncesHarness is Nonces { - function useNonce(address account) external returns (uint256) { - return _useNonce(account); - } - - function useCheckedNonce(address account, uint256 nonce) external { - _useCheckedNonce(account, nonce); - } -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/Ownable2StepHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/Ownable2StepHarness.sol deleted file mode 100644 index 09a5faa..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/Ownable2StepHarness.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Ownable2Step, Ownable} from "../patched/access/Ownable2Step.sol"; - -contract Ownable2StepHarness is Ownable2Step { - constructor(address initialOwner) Ownable(initialOwner) {} - - function restricted() external onlyOwner {} -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/OwnableHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/OwnableHarness.sol deleted file mode 100644 index 79b4b1b..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/OwnableHarness.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Ownable} from "../patched/access/Ownable.sol"; - -contract OwnableHarness is Ownable { - constructor(address initialOwner) Ownable(initialOwner) {} - - function restricted() external onlyOwner {} -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/PausableHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/PausableHarness.sol deleted file mode 100644 index 5977b92..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/PausableHarness.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Pausable} from "../patched/utils/Pausable.sol"; - -contract PausableHarness is Pausable { - function pause() external { - _pause(); - } - - function unpause() external { - _unpause(); - } - - function onlyWhenPaused() external whenPaused {} - - function onlyWhenNotPaused() external whenNotPaused {} -} diff --git a/lib_openzeppelin_contracts/certora/harnesses/TimelockControllerHarness.sol b/lib_openzeppelin_contracts/certora/harnesses/TimelockControllerHarness.sol deleted file mode 100644 index 95ae406..0000000 --- a/lib_openzeppelin_contracts/certora/harnesses/TimelockControllerHarness.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {TimelockController} from "../patched/governance/TimelockController.sol"; - -contract TimelockControllerHarness is TimelockController { - constructor( - uint256 minDelay, - address[] memory proposers, - address[] memory executors, - address admin - ) TimelockController(minDelay, proposers, executors, admin) {} -} diff --git a/lib_openzeppelin_contracts/certora/reports/2021-10.pdf b/lib_openzeppelin_contracts/certora/reports/2021-10.pdf deleted file mode 100644 index 22df9c6..0000000 Binary files a/lib_openzeppelin_contracts/certora/reports/2021-10.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/certora/reports/2022-03.pdf b/lib_openzeppelin_contracts/certora/reports/2022-03.pdf deleted file mode 100644 index b6ff81d..0000000 Binary files a/lib_openzeppelin_contracts/certora/reports/2022-03.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/certora/reports/2022-05.pdf b/lib_openzeppelin_contracts/certora/reports/2022-05.pdf deleted file mode 100644 index f24a449..0000000 Binary files a/lib_openzeppelin_contracts/certora/reports/2022-05.pdf and /dev/null differ diff --git a/lib_openzeppelin_contracts/certora/run.js b/lib_openzeppelin_contracts/certora/run.js deleted file mode 100755 index 7b65534..0000000 --- a/lib_openzeppelin_contracts/certora/run.js +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env node - -// USAGE: -// node certora/run.js [[CONTRACT_NAME:]SPEC_NAME]* [--all] [--options OPTIONS...] [--specs PATH] -// EXAMPLES: -// node certora/run.js --all -// node certora/run.js AccessControl -// node certora/run.js AccessControlHarness:AccessControl - -const proc = require('child_process'); -const { PassThrough } = require('stream'); -const events = require('events'); - -const argv = require('yargs') - .env('') - .options({ - all: { - alias: 'a', - type: 'boolean', - }, - spec: { - alias: 's', - type: 'string', - default: __dirname + '/specs.json', - }, - parallel: { - alias: 'p', - type: 'number', - default: 4, - }, - verbose: { - alias: 'v', - type: 'count', - default: 0, - }, - options: { - alias: 'o', - type: 'array', - default: [], - }, - }).argv; - -function match(entry, request) { - const [reqSpec, reqContract] = request.split(':').reverse(); - return entry.spec == reqSpec && (!reqContract || entry.contract == reqContract); -} - -const specs = require(argv.spec).filter(s => argv.all || argv._.some(r => match(s, r))); -const limit = require('p-limit')(argv.parallel); - -if (argv._.length == 0 && !argv.all) { - console.error(`Warning: No specs requested. Did you forgot to toggle '--all'?`); -} - -for (const r of argv._) { - if (!specs.some(s => match(s, r))) { - console.error(`Error: Requested spec '${r}' not found in ${argv.spec}`); - process.exitCode = 1; - } -} - -if (process.exitCode) { - process.exit(process.exitCode); -} - -for (const { spec, contract, files, options = [] } of specs) { - limit( - runCertora, - spec, - contract, - files, - [...options, ...argv.options].flatMap(opt => opt.split(' ')), - ); -} - -// Run certora, aggregate the output and print it at the end -async function runCertora(spec, contract, files, options = []) { - const args = [...files, '--verify', `${contract}:certora/specs/${spec}.spec`, ...options]; - if (argv.verbose) { - console.log('Running:', args.join(' ')); - } - const child = proc.spawn('certoraRun', args); - - const stream = new PassThrough(); - const output = collect(stream); - - child.stdout.pipe(stream, { end: false }); - child.stderr.pipe(stream, { end: false }); - - // as soon as we have a job id, print the output link - stream.on('data', function logStatusUrl(data) { - const { '-DjobId': jobId, '-DuserId': userId } = Object.fromEntries( - data - .toString('utf8') - .match(/-D\S+=\S+/g) - ?.map(s => s.split('=')) || [], - ); - - if (jobId && userId) { - console.error(`[${spec}] https://prover.certora.com/output/${userId}/${jobId}/`); - stream.off('data', logStatusUrl); - } - }); - - // wait for process end - const [code, signal] = await events.once(child, 'exit'); - - // error - if (code || signal) { - console.error(`[${spec}] Exited with code ${code || signal}`); - process.exitCode = 1; - } - - // get all output - stream.end(); - - // write results in markdown format - writeEntry(spec, contract, code || signal, (await output).match(/https:\/\/prover.certora.com\/output\/\S*/)?.[0]); - - // write all details - console.error(`+ certoraRun ${args.join(' ')}\n` + (await output)); -} - -// Collects stream data into a string -async function collect(stream) { - const buffers = []; - for await (const data of stream) { - const buf = Buffer.isBuffer(data) ? data : Buffer.from(data); - buffers.push(buf); - } - return Buffer.concat(buffers).toString('utf8'); -} - -// Formatting -let hasHeader = false; - -function formatRow(...array) { - return ['', ...array, ''].join(' | '); -} - -function writeHeader() { - console.log(formatRow('spec', 'contract', 'result', 'status', 'output')); - console.log(formatRow('-', '-', '-', '-', '-')); -} - -function writeEntry(spec, contract, success, url) { - if (!hasHeader) { - hasHeader = true; - writeHeader(); - } - console.log( - formatRow( - spec, - contract, - success ? ':x:' : ':heavy_check_mark:', - url ? `[link](${url?.replace('/output/', '/jobStatus/')})` : 'error', - url ? `[link](${url})` : 'error', - ), - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs.json b/lib_openzeppelin_contracts/certora/specs.json deleted file mode 100644 index a894190..0000000 --- a/lib_openzeppelin_contracts/certora/specs.json +++ /dev/null @@ -1,110 +0,0 @@ -[ - { - "spec": "Pausable", - "contract": "PausableHarness", - "files": ["certora/harnesses/PausableHarness.sol"] - }, - { - "spec": "AccessControl", - "contract": "AccessControlHarness", - "files": ["certora/harnesses/AccessControlHarness.sol"] - }, - { - "spec": "AccessControlDefaultAdminRules", - "contract": "AccessControlDefaultAdminRulesHarness", - "files": ["certora/harnesses/AccessControlDefaultAdminRulesHarness.sol"] - }, - { - "spec": "AccessManager", - "contract": "AccessManagerHarness", - "files": ["certora/harnesses/AccessManagerHarness.sol"], - "options": ["--optimistic_hashing", "--optimistic_loop"] - }, - { - "spec": "AccessManaged", - "contract": "AccessManagedHarness", - "files": [ - "certora/harnesses/AccessManagedHarness.sol", - "certora/harnesses/AccessManagerHarness.sol" - ], - "options": [ - "--optimistic_hashing", - "--optimistic_loop", - "--link AccessManagedHarness:_authority=AccessManagerHarness" - ] - }, - { - "spec": "DoubleEndedQueue", - "contract": "DoubleEndedQueueHarness", - "files": ["certora/harnesses/DoubleEndedQueueHarness.sol"] - }, - { - "spec": "Ownable", - "contract": "OwnableHarness", - "files": ["certora/harnesses/OwnableHarness.sol"] - }, - { - "spec": "Ownable2Step", - "contract": "Ownable2StepHarness", - "files": ["certora/harnesses/Ownable2StepHarness.sol"] - }, - { - "spec": "ERC20", - "contract": "ERC20PermitHarness", - "files": ["certora/harnesses/ERC20PermitHarness.sol"], - "options": ["--optimistic_loop"] - }, - { - "spec": "ERC20FlashMint", - "contract": "ERC20FlashMintHarness", - "files": [ - "certora/harnesses/ERC20FlashMintHarness.sol", - "certora/harnesses/ERC3156FlashBorrowerHarness.sol" - ], - "options": ["--optimistic_loop"] - }, - { - "spec": "ERC20Wrapper", - "contract": "ERC20WrapperHarness", - "files": [ - "certora/harnesses/ERC20PermitHarness.sol", - "certora/harnesses/ERC20WrapperHarness.sol" - ], - "options": [ - "--link ERC20WrapperHarness:_underlying=ERC20PermitHarness", - "--optimistic_loop" - ] - }, - { - "spec": "ERC721", - "contract": "ERC721Harness", - "files": ["certora/harnesses/ERC721Harness.sol", "certora/harnesses/ERC721ReceiverHarness.sol"], - "options": ["--optimistic_loop"] - }, - { - "spec": "Initializable", - "contract": "InitializableHarness", - "files": ["certora/harnesses/InitializableHarness.sol"] - }, - { - "spec": "EnumerableSet", - "contract": "EnumerableSetHarness", - "files": ["certora/harnesses/EnumerableSetHarness.sol"] - }, - { - "spec": "EnumerableMap", - "contract": "EnumerableMapHarness", - "files": ["certora/harnesses/EnumerableMapHarness.sol"] - }, - { - "spec": "TimelockController", - "contract": "TimelockControllerHarness", - "files": ["certora/harnesses/TimelockControllerHarness.sol"], - "options": ["--optimistic_hashing", "--optimistic_loop"] - }, - { - "spec": "Nonces", - "contract": "NoncesHarness", - "files": ["certora/harnesses/NoncesHarness.sol"] - } -] diff --git a/lib_openzeppelin_contracts/certora/specs/AccessControl.spec b/lib_openzeppelin_contracts/certora/specs/AccessControl.spec deleted file mode 100644 index 70b0672..0000000 --- a/lib_openzeppelin_contracts/certora/specs/AccessControl.spec +++ /dev/null @@ -1,119 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IAccessControl.spec"; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Identify entrypoints: only grantRole, revokeRole and renounceRole can alter permissions │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyGrantCanGrant(env e, method f, bytes32 role, address account) { - calldataarg args; - - bool hasRoleBefore = hasRole(role, account); - f(e, args); - bool hasRoleAfter = hasRole(role, account); - - assert ( - !hasRoleBefore && - hasRoleAfter - ) => ( - f.selector == sig:grantRole(bytes32, address).selector - ); - - assert ( - hasRoleBefore && - !hasRoleAfter - ) => ( - f.selector == sig:revokeRole(bytes32, address).selector || - f.selector == sig:renounceRole(bytes32, address).selector - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: grantRole only affects the specified user/role combo │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule grantRoleEffect(env e, bytes32 role) { - require nonpayable(e); - - bytes32 otherRole; - address account; - address otherAccount; - - bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); - bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); - - grantRole@withrevert(e, role, account); - bool success = !lastReverted; - - bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); - - // liveness - assert success <=> isCallerAdmin; - - // effect - assert success => hasRole(role, account); - - // no side effect - assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: revokeRole only affects the specified user/role combo │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule revokeRoleEffect(env e, bytes32 role) { - require nonpayable(e); - - bytes32 otherRole; - address account; - address otherAccount; - - bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); - bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); - - revokeRole@withrevert(e, role, account); - bool success = !lastReverted; - - bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); - - // liveness - assert success <=> isCallerAdmin; - - // effect - assert success => !hasRole(role, account); - - // no side effect - assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: renounceRole only affects the specified user/role combo │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule renounceRoleEffect(env e, bytes32 role) { - require nonpayable(e); - - bytes32 otherRole; - address account; - address otherAccount; - - bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); - - renounceRole@withrevert(e, role, account); - bool success = !lastReverted; - - bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); - - // liveness - assert success <=> account == e.msg.sender; - - // effect - assert success => !hasRole(role, account); - - // no side effect - assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount); -} diff --git a/lib_openzeppelin_contracts/certora/specs/AccessControlDefaultAdminRules.spec b/lib_openzeppelin_contracts/certora/specs/AccessControlDefaultAdminRules.spec deleted file mode 100644 index 2f5bb9d..0000000 --- a/lib_openzeppelin_contracts/certora/specs/AccessControlDefaultAdminRules.spec +++ /dev/null @@ -1,464 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IAccessControlDefaultAdminRules.spec"; -import "methods/IAccessControl.spec"; -import "AccessControl.spec"; - -use rule onlyGrantCanGrant filtered { - f -> f.selector != sig:acceptDefaultAdminTransfer().selector -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Definitions │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -definition timeSanity(env e) returns bool = - e.block.timestamp > 0 && e.block.timestamp + defaultAdminDelay(e) < max_uint48; - -definition delayChangeWaitSanity(env e, uint48 newDelay) returns bool = - e.block.timestamp + delayChangeWait_(e, newDelay) < max_uint48; - -definition isSet(uint48 schedule) returns bool = - schedule != 0; - -definition hasPassed(env e, uint48 schedule) returns bool = - assert_uint256(schedule) < e.block.timestamp; - -definition increasingDelaySchedule(env e, uint48 newDelay) returns mathint = - e.block.timestamp + min(newDelay, defaultAdminDelayIncreaseWait()); - -definition decreasingDelaySchedule(env e, uint48 newDelay) returns mathint = - e.block.timestamp + defaultAdminDelay(e) - newDelay; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: defaultAdmin holds the DEFAULT_ADMIN_ROLE │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant defaultAdminConsistency(address account) - (account == defaultAdmin() && account != 0) <=> hasRole(DEFAULT_ADMIN_ROLE(), account) - { - preserved with (env e) { - require nonzerosender(e); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: Only one account holds the DEFAULT_ADMIN_ROLE │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant singleDefaultAdmin(address account, address another) - hasRole(DEFAULT_ADMIN_ROLE(), account) && hasRole(DEFAULT_ADMIN_ROLE(), another) => another == account - { - preserved { - requireInvariant defaultAdminConsistency(account); - requireInvariant defaultAdminConsistency(another); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: DEFAULT_ADMIN_ROLE's admin is always DEFAULT_ADMIN_ROLE │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant defaultAdminRoleAdminConsistency() - getRoleAdmin(DEFAULT_ADMIN_ROLE()) == DEFAULT_ADMIN_ROLE(); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: owner is the defaultAdmin │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant ownerConsistency() - defaultAdmin() == owner(); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: revokeRole only affects the specified user/role combo │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule revokeRoleEffect(env e, bytes32 role) { - require nonpayable(e); - - bytes32 otherRole; - address account; - address otherAccount; - - bool isCallerAdmin = hasRole(getRoleAdmin(role), e.msg.sender); - bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); - - revokeRole@withrevert(e, role, account); - bool success = !lastReverted; - - bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); - - // liveness - assert success <=> isCallerAdmin && role != DEFAULT_ADMIN_ROLE(), - "roles can only be revoked by their owner except for the default admin role"; - - // effect - assert success => !hasRole(role, account), - "role is revoked"; - - // no side effect - assert hasOtherRoleBefore != hasOtherRoleAfter => (role == otherRole && account == otherAccount), - "no other role is affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: renounceRole only affects the specified user/role combo │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule renounceRoleEffect(env e, bytes32 role) { - require nonpayable(e); - - bytes32 otherRole; - address account; - address otherAccount; - - bool hasOtherRoleBefore = hasRole(otherRole, otherAccount); - address adminBefore = defaultAdmin(); - address pendingAdminBefore = pendingDefaultAdmin_(); - uint48 scheduleBefore = pendingDefaultAdminSchedule_(); - - renounceRole@withrevert(e, role, account); - bool success = !lastReverted; - - bool hasOtherRoleAfter = hasRole(otherRole, otherAccount); - address adminAfter = defaultAdmin(); - address pendingAdminAfter = pendingDefaultAdmin_(); - uint48 scheduleAfter = pendingDefaultAdminSchedule_(); - - // liveness - assert success <=> ( - account == e.msg.sender && - ( - role != DEFAULT_ADMIN_ROLE() || - account != adminBefore || - ( - pendingAdminBefore == 0 && - isSet(scheduleBefore) && - hasPassed(e, scheduleBefore) - ) - ) - ), - "an account only can renounce by itself with a delay for the default admin role"; - - // effect - assert success => !hasRole(role, account), - "role is renounced"; - - assert success => ( - ( - role == DEFAULT_ADMIN_ROLE() && - account == adminBefore - ) ? ( - adminAfter == 0 && - pendingAdminAfter == 0 && - scheduleAfter == 0 - ) : ( - adminAfter == adminBefore && - pendingAdminAfter == pendingAdminBefore && - scheduleAfter == scheduleBefore - ) - ), - "renouncing default admin role cleans state iff called by previous admin"; - - // no side effect - assert hasOtherRoleBefore != hasOtherRoleAfter => ( - role == otherRole && - account == otherAccount - ), - "no other role is affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: defaultAdmin is only affected by accepting an admin transfer or renoucing │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noDefaultAdminChange(env e, method f, calldataarg args) { - address adminBefore = defaultAdmin(); - f(e, args); - address adminAfter = defaultAdmin(); - - assert adminBefore != adminAfter => ( - f.selector == sig:acceptDefaultAdminTransfer().selector || - f.selector == sig:renounceRole(bytes32,address).selector - ), - "default admin is only affected by accepting an admin transfer or renoucing"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: pendingDefaultAdmin is only affected by beginning, completing (accept or renounce), or canceling an admin │ -│ transfer │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noPendingDefaultAdminChange(env e, method f, calldataarg args) { - address pendingAdminBefore = pendingDefaultAdmin_(); - uint48 scheduleBefore = pendingDefaultAdminSchedule_(); - f(e, args); - address pendingAdminAfter = pendingDefaultAdmin_(); - uint48 scheduleAfter = pendingDefaultAdminSchedule_(); - - assert ( - pendingAdminBefore != pendingAdminAfter || - scheduleBefore != scheduleAfter - ) => ( - f.selector == sig:beginDefaultAdminTransfer(address).selector || - f.selector == sig:acceptDefaultAdminTransfer().selector || - f.selector == sig:cancelDefaultAdminTransfer().selector || - f.selector == sig:renounceRole(bytes32,address).selector - ), - "pending admin and its schedule is only affected by beginning, completing, or cancelling an admin transfer"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: defaultAdminDelay can't be changed atomically by any function │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noDefaultAdminDelayChange(env e, method f, calldataarg args) { - uint48 delayBefore = defaultAdminDelay(e); - f(e, args); - uint48 delayAfter = defaultAdminDelay(e); - - assert delayBefore == delayAfter, - "delay can't be changed atomically by any function"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: pendingDefaultAdminDelay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noPendingDefaultAdminDelayChange(env e, method f, calldataarg args) { - uint48 pendingDelayBefore = pendingDelay_(e); - f(e, args); - uint48 pendingDelayAfter = pendingDelay_(e); - - assert pendingDelayBefore != pendingDelayAfter => ( - f.selector == sig:changeDefaultAdminDelay(uint48).selector || - f.selector == sig:rollbackDefaultAdminDelay().selector - ), - "pending delay is only affected by changeDefaultAdminDelay or rollbackDefaultAdminDelay"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: defaultAdminDelayIncreaseWait can't be changed atomically by any function │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noDefaultAdminDelayIncreaseWaitChange(env e, method f, calldataarg args) { - uint48 delayIncreaseWaitBefore = defaultAdminDelayIncreaseWait(); - f(e, args); - uint48 delayIncreaseWaitAfter = defaultAdminDelayIncreaseWait(); - - assert delayIncreaseWaitBefore == delayIncreaseWaitAfter, - "delay increase wait can't be changed atomically by any function"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: beginDefaultAdminTransfer sets a pending default admin and its schedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule beginDefaultAdminTransfer(env e, address newAdmin) { - require timeSanity(e); - require nonpayable(e); - require nonzerosender(e); - requireInvariant defaultAdminConsistency(e.msg.sender); - - beginDefaultAdminTransfer@withrevert(e, newAdmin); - bool success = !lastReverted; - - // liveness - assert success <=> e.msg.sender == defaultAdmin(), - "only the current default admin can begin a transfer"; - - // effect - assert success => pendingDefaultAdmin_() == newAdmin, - "pending default admin is set"; - assert success => to_mathint(pendingDefaultAdminSchedule_()) == e.block.timestamp + defaultAdminDelay(e), - "pending default admin delay is set"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: A default admin can't change in less than the applied schedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pendingDefaultAdminDelayEnforced(env e1, env e2, method f, calldataarg args, address newAdmin) { - require e1.block.timestamp <= e2.block.timestamp; - - uint48 delayBefore = defaultAdminDelay(e1); - address adminBefore = defaultAdmin(); - - // There might be a better way to generalize this without requiring `beginDefaultAdminTransfer`, but currently - // it's the only way in which we can attest that only `delayBefore` has passed before a change. - beginDefaultAdminTransfer(e1, newAdmin); - f(e2, args); - - address adminAfter = defaultAdmin(); - - // change can only happen towards the newAdmin, with the delay - assert adminAfter != adminBefore => ( - adminAfter == newAdmin && - to_mathint(e2.block.timestamp) >= e1.block.timestamp + delayBefore - ), - "The admin can only change after the enforced delay and to the previously scheduled new admin"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: acceptDefaultAdminTransfer updates defaultAdmin resetting the pending admin and its schedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule acceptDefaultAdminTransfer(env e) { - require nonpayable(e); - - address pendingAdminBefore = pendingDefaultAdmin_(); - uint48 scheduleBefore = pendingDefaultAdminSchedule_(); - - acceptDefaultAdminTransfer@withrevert(e); - bool success = !lastReverted; - - // liveness - assert success <=> ( - e.msg.sender == pendingAdminBefore && - isSet(scheduleBefore) && - hasPassed(e, scheduleBefore) - ), - "only the pending default admin can accept the role after the schedule has been set and passed"; - - // effect - assert success => defaultAdmin() == pendingAdminBefore, - "Default admin is set to the previous pending default admin"; - assert success => pendingDefaultAdmin_() == 0, - "Pending default admin is reset"; - assert success => pendingDefaultAdminSchedule_() == 0, - "Pending default admin delay is reset"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: cancelDefaultAdminTransfer resets pending default admin and its schedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule cancelDefaultAdminTransfer(env e) { - require nonpayable(e); - require nonzerosender(e); - requireInvariant defaultAdminConsistency(e.msg.sender); - - cancelDefaultAdminTransfer@withrevert(e); - bool success = !lastReverted; - - // liveness - assert success <=> e.msg.sender == defaultAdmin(), - "only the current default admin can cancel a transfer"; - - // effect - assert success => pendingDefaultAdmin_() == 0, - "Pending default admin is reset"; - assert success => pendingDefaultAdminSchedule_() == 0, - "Pending default admin delay is reset"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: changeDefaultAdminDelay sets a pending default admin delay and its schedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule changeDefaultAdminDelay(env e, uint48 newDelay) { - require timeSanity(e); - require nonpayable(e); - require nonzerosender(e); - require delayChangeWaitSanity(e, newDelay); - requireInvariant defaultAdminConsistency(e.msg.sender); - - uint48 delayBefore = defaultAdminDelay(e); - - changeDefaultAdminDelay@withrevert(e, newDelay); - bool success = !lastReverted; - - // liveness - assert success <=> e.msg.sender == defaultAdmin(), - "only the current default admin can begin a delay change"; - - // effect - assert success => pendingDelay_(e) == newDelay, - "pending delay is set"; - - assert success => ( - assert_uint256(pendingDelaySchedule_(e)) > e.block.timestamp || - delayBefore == newDelay || // Interpreted as decreasing, x - x = 0 - defaultAdminDelayIncreaseWait() == 0 - ), - "pending delay schedule is set in the future unless accepted edge cases"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: A delay can't change in less than the applied schedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pendingDelayWaitEnforced(env e1, env e2, method f, calldataarg args, uint48 newDelay) { - require e1.block.timestamp <= e2.block.timestamp; - - uint48 delayBefore = defaultAdminDelay(e1); - - changeDefaultAdminDelay(e1, newDelay); - f(e2, args); - - uint48 delayAfter = defaultAdminDelay(e2); - - mathint delayWait = newDelay > delayBefore ? increasingDelaySchedule(e1, newDelay) : decreasingDelaySchedule(e1, newDelay); - - assert delayAfter != delayBefore => ( - delayAfter == newDelay && - to_mathint(e2.block.timestamp) >= delayWait - ), - "A delay can only change after the applied schedule"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: pending delay wait is set depending on increasing or decreasing the delay │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pendingDelayWait(env e, uint48 newDelay) { - uint48 oldDelay = defaultAdminDelay(e); - changeDefaultAdminDelay(e, newDelay); - - assert newDelay > oldDelay => to_mathint(pendingDelaySchedule_(e)) == increasingDelaySchedule(e, newDelay), - "Delay wait is the minimum between the new delay and a threshold when the delay is increased"; - assert newDelay <= oldDelay => to_mathint(pendingDelaySchedule_(e)) == decreasingDelaySchedule(e, newDelay), - "Delay wait is the difference between the current and the new delay when the delay is decreased"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: rollbackDefaultAdminDelay resets the delay and its schedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule rollbackDefaultAdminDelay(env e) { - require nonpayable(e); - require nonzerosender(e); - requireInvariant defaultAdminConsistency(e.msg.sender); - - rollbackDefaultAdminDelay@withrevert(e); - bool success = !lastReverted; - - // liveness - assert success <=> e.msg.sender == defaultAdmin(), - "only the current default admin can rollback a delay change"; - - // effect - assert success => pendingDelay_(e) == 0, - "Pending default admin is reset"; - assert success => pendingDelaySchedule_(e) == 0, - "Pending default admin delay is reset"; -} diff --git a/lib_openzeppelin_contracts/certora/specs/AccessManaged.spec b/lib_openzeppelin_contracts/certora/specs/AccessManaged.spec deleted file mode 100644 index adcb859..0000000 --- a/lib_openzeppelin_contracts/certora/specs/AccessManaged.spec +++ /dev/null @@ -1,34 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IAccessManaged.spec"; - -methods { - // FV - function someFunction() external; - function authority_canCall_immediate(address) external returns (bool); - function authority_canCall_delay(address) external returns (uint32); - function authority_getSchedule(address) external returns (uint48); -} - -invariant isConsumingScheduledOpClean() - isConsumingScheduledOp() == to_bytes4(0); - -rule callRestrictedFunction(env e) { - bool immediate = authority_canCall_immediate(e, e.msg.sender); - uint32 delay = authority_canCall_delay(e, e.msg.sender); - uint48 scheduleBefore = authority_getSchedule(e, e.msg.sender); - - someFunction@withrevert(e); - bool success = !lastReverted; - - uint48 scheduleAfter = authority_getSchedule(e, e.msg.sender); - - // can only call if immediate, or (with delay) by consuming a scheduled op - assert success => ( - immediate || - ( - delay > 0 && - isSetAndPast(e, scheduleBefore) && - scheduleAfter == 0 - ) - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs/AccessManager.spec b/lib_openzeppelin_contracts/certora/specs/AccessManager.spec deleted file mode 100644 index cc4b013..0000000 --- a/lib_openzeppelin_contracts/certora/specs/AccessManager.spec +++ /dev/null @@ -1,826 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IAccessManager.spec"; - -methods { - // FV - function canCall_immediate(address,address,bytes4) external returns (bool); - function canCall_delay(address,address,bytes4) external returns (uint32); - function canCallExtended(address,address,bytes) external returns (bool,uint32); - function canCallExtended_immediate(address,address,bytes) external returns (bool); - function canCallExtended_delay(address,address,bytes) external returns (uint32); - function getAdminRestrictions_restricted(bytes) external returns (bool); - function getAdminRestrictions_roleAdminId(bytes) external returns (uint64); - function getAdminRestrictions_executionDelay(bytes) external returns (uint32); - function hasRole_isMember(uint64,address) external returns (bool); - function hasRole_executionDelay(uint64,address) external returns (uint32); - function getAccess_since(uint64,address) external returns (uint48); - function getAccess_currentDelay(uint64,address) external returns (uint32); - function getAccess_pendingDelay(uint64,address) external returns (uint32); - function getAccess_effect(uint64,address) external returns (uint48); - function getTargetAdminDelay_after(address target) external returns (uint32); - function getTargetAdminDelay_effect(address target) external returns (uint48); - function getRoleGrantDelay_after(uint64 roleId) external returns (uint32); - function getRoleGrantDelay_effect(uint64 roleId) external returns (uint48); - function hashExecutionId(address,bytes4) external returns (bytes32) envfree; - function executionId() external returns (bytes32) envfree; - function getSelector(bytes) external returns (bytes4) envfree; - function getFirstArgumentAsAddress(bytes) external returns (address) envfree; - function getFirstArgumentAsUint64(bytes) external returns (uint64) envfree; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helpers │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -definition isOnlyAuthorized(bytes4 selector) returns bool = - selector == to_bytes4(sig:labelRole(uint64,string).selector ) || - selector == to_bytes4(sig:setRoleAdmin(uint64,uint64).selector ) || - selector == to_bytes4(sig:setRoleGuardian(uint64,uint64).selector ) || - selector == to_bytes4(sig:setGrantDelay(uint64,uint32).selector ) || - selector == to_bytes4(sig:setTargetAdminDelay(address,uint32).selector ) || - selector == to_bytes4(sig:updateAuthority(address,address).selector ) || - selector == to_bytes4(sig:setTargetClosed(address,bool).selector ) || - selector == to_bytes4(sig:setTargetFunctionRole(address,bytes4[],uint64).selector) || - selector == to_bytes4(sig:grantRole(uint64,address,uint32).selector ) || - selector == to_bytes4(sig:revokeRole(uint64,address).selector ); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: executionId must be clean when not in the middle of a call │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant cleanExecutionId() - executionId() == to_bytes32(0); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: public role │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant publicRole(env e, address account) - hasRole_isMember(e, PUBLIC_ROLE(), account) && - hasRole_executionDelay(e, PUBLIC_ROLE(), account) == 0 && - getAccess_since(e, PUBLIC_ROLE(), account) == 0 && - getAccess_currentDelay(e, PUBLIC_ROLE(), account) == 0 && - getAccess_pendingDelay(e, PUBLIC_ROLE(), account) == 0 && - getAccess_effect(e, PUBLIC_ROLE(), account) == 0; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: hasRole is consistent with getAccess │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant hasRoleGetAccessConsistency(env e, uint64 roleId, address account) - hasRole_isMember(e, roleId, account) == (roleId == PUBLIC_ROLE() || isSetAndPast(e, getAccess_since(e, roleId, account))) && - hasRole_executionDelay(e, roleId, account) == getAccess_currentDelay(e, roleId, account); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Functions: canCall, canCallExtended, getAccess, hasRole, isTargetClosed and getTargetFunctionRole do NOT revert │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noRevert(env e) { - require nonpayable(e); - require sanity(e); - - address caller; - address target; - bytes data; - bytes4 selector; - uint64 roleId; - - canCall@withrevert(e, caller, target, selector); - assert !lastReverted; - - // require data.length <= max_uint64; - // - // canCallExtended@withrevert(e, caller, target, data); - // assert !lastReverted; - - getAccess@withrevert(e, roleId, caller); - assert !lastReverted; - - hasRole@withrevert(e, roleId, caller); - assert !lastReverted; - - isTargetClosed@withrevert(target); - assert !lastReverted; - - getTargetFunctionRole@withrevert(target, selector); - assert !lastReverted; - - // Not covered: - // - getAdminRestrictions (_1, _2 & _3) - // - getSelector -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Functions: admin restrictions are correct │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getAdminRestrictions(env e, bytes data) { - bool restricted = getAdminRestrictions_restricted(e, data); - uint64 roleId = getAdminRestrictions_roleAdminId(e, data); - uint32 delay = getAdminRestrictions_executionDelay(e, data); - bytes4 selector = getSelector(data); - - if (data.length < 4) { - assert restricted == false; - assert roleId == 0; - assert delay == 0; - } else { - assert restricted == - isOnlyAuthorized(selector); - - assert roleId == ( - (restricted && selector == to_bytes4(sig:grantRole(uint64,address,uint32).selector)) || - (restricted && selector == to_bytes4(sig:revokeRole(uint64,address).selector )) - ? getRoleAdmin(getFirstArgumentAsUint64(data)) - : ADMIN_ROLE() - ); - - assert delay == ( - (restricted && selector == to_bytes4(sig:updateAuthority(address,address).selector )) || - (restricted && selector == to_bytes4(sig:setTargetClosed(address,bool).selector )) || - (restricted && selector == to_bytes4(sig:setTargetFunctionRole(address,bytes4[],uint64).selector)) - ? getTargetAdminDelay(e, getFirstArgumentAsAddress(data)) - : 0 - ); - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Functions: canCall │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule canCall(env e) { - address caller; - address target; - bytes4 selector; - - // Get relevant values - bool immediate = canCall_immediate(e, caller, target, selector); - uint32 delay = canCall_delay(e, caller, target, selector); - bool closed = isTargetClosed(target); - uint64 roleId = getTargetFunctionRole(target, selector); - bool isMember = hasRole_isMember(e, roleId, caller); - uint32 currentDelay = hasRole_executionDelay(e, roleId, caller); - - // Can only execute without delay in specific cases: - // - target not closed - // - if self-execution: `executionId` must match - // - if third party execution: must be member with no delay - assert immediate <=> ( - !closed && - ( - (caller == currentContract && executionId() == hashExecutionId(target, selector)) - || - (caller != currentContract && isMember && currentDelay == 0) - ) - ); - - // Can only execute with delay in specific cases: - // - target not closed - // - third party execution - // - caller is a member and has an execution delay - assert delay > 0 <=> ( - !closed && - caller != currentContract && - isMember && - currentDelay > 0 - ); - - // If there is a delay, then it must be the caller's execution delay - assert delay > 0 => delay == currentDelay; - - // Immediate execute means no delayed execution - assert immediate => delay == 0; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Functions: canCallExtended │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule canCallExtended(env e) { - address caller; - address target; - bytes data; - bytes4 selector = getSelector(data); - - bool immediate = canCallExtended_immediate(e, caller, target, data); - uint32 delay = canCallExtended_delay(e, caller, target, data); - bool enabled = getAdminRestrictions_restricted(e, data); - uint64 roleId = getAdminRestrictions_roleAdminId(e, data); - uint32 operationDelay = getAdminRestrictions_executionDelay(e, data); - bool inRole = hasRole_isMember(e, roleId, caller); - uint32 executionDelay = hasRole_executionDelay(e, roleId, caller); - - if (target == currentContract) { - // Can only execute without delay in the specific cases: - // - caller is the AccessManager and the executionId is set - // or - // - data matches an admin restricted function - // - caller has the necessary role - // - operation delay is not set - // - execution delay is not set - assert immediate <=> ( - ( - caller == currentContract && - data.length >= 4 && - executionId() == hashExecutionId(target, selector) - ) || ( - caller != currentContract && - enabled && - inRole && - operationDelay == 0 && - executionDelay == 0 - ) - ); - - // Immediate execute means no delayed execution - // This is equivalent to "delay > 0 => !immediate" - assert immediate => delay == 0; - - // Can only execute with delay in specific cases: - // - caller is a third party - // - data matches an admin restricted function - // - caller has the necessary role - // -operation delay or execution delay is set - assert delay > 0 <=> ( - caller != currentContract && - enabled && - inRole && - (operationDelay > 0 || executionDelay > 0) - ); - - // If there is a delay, then it must be the maximum of caller's execution delay and the operation delay - assert delay > 0 => to_mathint(delay) == max(operationDelay, executionDelay); - } else if (data.length < 4) { - assert immediate == false; - assert delay == 0; - } else { - // results are equivalent when targeting third party contracts - assert immediate == canCall_immediate(e, caller, target, selector); - assert delay == canCall_delay(e, caller, target, selector); - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: getAccess │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getAccessChangeTime(uint64 roleId, address account) { - env e1; - env e2; - - // values before - mathint getAccess1Before = getAccess_since(e1, roleId, account); - mathint getAccess2Before = getAccess_currentDelay(e1, roleId, account); - mathint getAccess3Before = getAccess_pendingDelay(e1, roleId, account); - mathint getAccess4Before = getAccess_effect(e1, roleId, account); - - // time pass: e1 → e2 - require clock(e1) <= clock(e2); - - // values after - mathint getAccess1After = getAccess_since(e2, roleId, account); - mathint getAccess2After = getAccess_currentDelay(e2, roleId, account); - mathint getAccess3After = getAccess_pendingDelay(e2, roleId, account); - mathint getAccess4After = getAccess_effect(e2, roleId, account); - - // member "since" cannot change as a consequence of time passing - assert getAccess1Before == getAccess1After; - - // any change of any other value should be a consequence of the effect timepoint being reached - assert ( - getAccess2Before != getAccess2After || - getAccess3Before != getAccess3After || - getAccess4Before != getAccess4After - ) => ( - getAccess4Before != 0 && - getAccess4Before > clock(e1) && - getAccess4Before <= clock(e2) && - getAccess2After == getAccess3Before && - getAccess3After == 0 && - getAccess4After == 0 - ); -} - -rule getAccessChangeCall(uint64 roleId, address account) { - env e; - - // sanity - require sanity(e); - - // values before - mathint getAccess1Before = getAccess_since(e, roleId, account); - mathint getAccess2Before = getAccess_currentDelay(e, roleId, account); - mathint getAccess3Before = getAccess_pendingDelay(e, roleId, account); - mathint getAccess4Before = getAccess_effect(e, roleId, account); - - // arbitrary function call - method f; calldataarg args; f(e, args); - - // values before - mathint getAccess1After = getAccess_since(e, roleId, account); - mathint getAccess2After = getAccess_currentDelay(e, roleId, account); - mathint getAccess3After = getAccess_pendingDelay(e, roleId, account); - mathint getAccess4After = getAccess_effect(e, roleId, account); - - // transitions - assert ( - getAccess1Before != getAccess1After || - getAccess2Before != getAccess2After || - getAccess3Before != getAccess3After || - getAccess4Before != getAccess4After - ) => ( - ( - f.selector == sig:grantRole(uint64,address,uint32).selector && - getAccess1After > 0 - ) || ( - ( - f.selector == sig:revokeRole(uint64,address).selector || - f.selector == sig:renounceRole(uint64,address).selector - ) && - getAccess1After == 0 && - getAccess2After == 0 && - getAccess3After == 0 && - getAccess4After == 0 - ) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: isTargetClosed │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule isTargetClosedChangeTime(address target) { - env e1; - env e2; - - // values before - bool isClosedBefore = isTargetClosed(e1, target); - - // time pass: e1 → e2 - require clock(e1) <= clock(e2); - - // values after - bool isClosedAfter = isTargetClosed(e2, target); - - // transitions - assert isClosedBefore == isClosedAfter; -} - -rule isTargetClosedChangeCall(address target) { - env e; - - // values before - bool isClosedBefore = isTargetClosed(e, target); - - // arbitrary function call - method f; calldataarg args; f(e, args); - - // values after - bool isClosedAfter = isTargetClosed(e, target); - - // transitions - assert isClosedBefore != isClosedAfter => ( - f.selector == sig:setTargetClosed(address,bool).selector - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: getTargetFunctionRole │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getTargetFunctionRoleChangeTime(address target, bytes4 selector) { - env e1; - env e2; - - // values before - mathint roleIdBefore = getTargetFunctionRole(e1, target, selector); - - // time pass: e1 → e2 - require clock(e1) <= clock(e2); - - // values after - mathint roleIdAfter = getTargetFunctionRole(e2, target, selector); - - // transitions - assert roleIdBefore == roleIdAfter; -} - -rule getTargetFunctionRoleChangeCall(address target, bytes4 selector) { - env e; - - // values before - mathint roleIdBefore = getTargetFunctionRole(e, target, selector); - - // arbitrary function call - method f; calldataarg args; f(e, args); - - // values after - mathint roleIdAfter = getTargetFunctionRole(e, target, selector); - - // transitions - assert roleIdBefore != roleIdAfter => ( - f.selector == sig:setTargetFunctionRole(address,bytes4[],uint64).selector - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: getTargetAdminDelay │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getTargetAdminDelayChangeTime(address target) { - env e1; - env e2; - - // values before - mathint delayBefore = getTargetAdminDelay(e1, target); - mathint delayPendingBefore = getTargetAdminDelay_after(e1, target); - mathint delayEffectBefore = getTargetAdminDelay_effect(e1, target); - - // time pass: e1 → e2 - require clock(e1) <= clock(e2); - - // values after - mathint delayAfter = getTargetAdminDelay(e2, target); - mathint delayPendingAfter = getTargetAdminDelay_after(e2, target); - mathint delayEffectAfter = getTargetAdminDelay_effect(e2, target); - - assert ( - delayBefore != delayAfter || - delayPendingBefore != delayPendingAfter || - delayEffectBefore != delayEffectAfter - ) => ( - delayEffectBefore > clock(e1) && - delayEffectBefore <= clock(e2) && - delayAfter == delayPendingBefore && - delayPendingAfter == 0 && - delayEffectAfter == 0 - ); -} - -rule getTargetAdminDelayChangeCall(address target) { - env e; - - // values before - mathint delayBefore = getTargetAdminDelay(e, target); - mathint delayPendingBefore = getTargetAdminDelay_after(e, target); - mathint delayEffectBefore = getTargetAdminDelay_effect(e, target); - - // arbitrary function call - method f; calldataarg args; f(e, args); - - // values after - mathint delayAfter = getTargetAdminDelay(e, target); - mathint delayPendingAfter = getTargetAdminDelay_after(e, target); - mathint delayEffectAfter = getTargetAdminDelay_effect(e, target); - - // if anything changed ... - assert ( - delayBefore != delayAfter || - delayPendingBefore != delayPendingAfter || - delayEffectBefore != delayEffectAfter - ) => ( - ( - // ... it was the consequence of a call to setTargetAdminDelay - f.selector == sig:setTargetAdminDelay(address,uint32).selector - ) && ( - // ... delay cannot decrease instantly - delayAfter >= delayBefore - ) && ( - // ... if setback is not 0, value cannot change instantly - minSetback() > 0 => ( - delayBefore == delayAfter - ) - ) && ( - // ... if the value did not change and there is a minSetback, there must be something scheduled in the future - delayAfter == delayBefore && minSetback() > 0 => ( - delayEffectAfter >= clock(e) + minSetback() - ) - // note: if there is no minSetback, and if the caller "confirms" the current value, - // then this as immediate effect and nothing is scheduled - ) && ( - // ... if the value changed, then no further change should be scheduled - delayAfter != delayBefore => ( - delayPendingAfter == 0 && - delayEffectAfter == 0 - ) - ) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: getRoleGrantDelay │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getRoleGrantDelayChangeTime(uint64 roleId) { - env e1; - env e2; - - // values before - mathint delayBefore = getRoleGrantDelay(e1, roleId); - mathint delayPendingBefore = getRoleGrantDelay_after(e1, roleId); - mathint delayEffectBefore = getRoleGrantDelay_effect(e1, roleId); - - // time pass: e1 → e2 - require clock(e1) <= clock(e2); - - // values after - mathint delayAfter = getRoleGrantDelay(e2, roleId); - mathint delayPendingAfter = getRoleGrantDelay_after(e2, roleId); - mathint delayEffectAfter = getRoleGrantDelay_effect(e2, roleId); - - assert ( - delayBefore != delayAfter || - delayPendingBefore != delayPendingAfter || - delayEffectBefore != delayEffectAfter - ) => ( - delayEffectBefore > clock(e1) && - delayEffectBefore <= clock(e2) && - delayAfter == delayPendingBefore && - delayPendingAfter == 0 && - delayEffectAfter == 0 - ); -} - -rule getRoleGrantDelayChangeCall(uint64 roleId) { - env e; - - // values before - mathint delayBefore = getRoleGrantDelay(e, roleId); - mathint delayPendingBefore = getRoleGrantDelay_after(e, roleId); - mathint delayEffectBefore = getRoleGrantDelay_effect(e, roleId); - - // arbitrary function call - method f; calldataarg args; f(e, args); - - // values after - mathint delayAfter = getRoleGrantDelay(e, roleId); - mathint delayPendingAfter = getRoleGrantDelay_after(e, roleId); - mathint delayEffectAfter = getRoleGrantDelay_effect(e, roleId); - - // if anything changed ... - assert ( - delayBefore != delayAfter || - delayPendingBefore != delayPendingAfter || - delayEffectBefore != delayEffectAfter - ) => ( - ( - // ... it was the consequence of a call to setTargetAdminDelay - f.selector == sig:setGrantDelay(uint64,uint32).selector - ) && ( - // ... delay cannot decrease instantly - delayAfter >= delayBefore - ) && ( - // ... if setback is not 0, value cannot change instantly - minSetback() > 0 => ( - delayBefore == delayAfter - ) - ) && ( - // ... if the value did not change and there is a minSetback, there must be something scheduled in the future - delayAfter == delayBefore && minSetback() > 0 => ( - delayEffectAfter >= clock(e) + minSetback() - ) - // note: if there is no minSetback, and if the caller "confirms" the current value, - // then this as immediate effect and nothing is scheduled - ) && ( - // ... if the value changed, then no further change should be scheduled - delayAfter != delayBefore => ( - delayPendingAfter == 0 && - delayEffectAfter == 0 - ) - ) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: getRoleAdmin & getRoleGuardian │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getRoleAdminChangeCall(uint64 roleId) { - // values before - mathint adminIdBefore = getRoleAdmin(roleId); - - // arbitrary function call - env e; method f; calldataarg args; f(e, args); - - // values after - mathint adminIdAfter = getRoleAdmin(roleId); - - // transitions - assert adminIdBefore != adminIdAfter => f.selector == sig:setRoleAdmin(uint64,uint64).selector; -} - -rule getRoleGuardianChangeCall(uint64 roleId) { - // values before - mathint guardianIdBefore = getRoleGuardian(roleId); - - // arbitrary function call - env e; method f; calldataarg args; f(e, args); - - // values after - mathint guardianIdAfter = getRoleGuardian(roleId); - - // transitions - assert guardianIdBefore != guardianIdAfter => ( - f.selector == sig:setRoleGuardian(uint64,uint64).selector - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: getNonce │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getNonceChangeCall(bytes32 operationId) { - // values before - mathint nonceBefore = getNonce(operationId); - - // reasonable assumption - require nonceBefore < max_uint32; - - // arbitrary function call - env e; method f; calldataarg args; f(e, args); - - // values after - mathint nonceAfter = getNonce(operationId); - - // transitions - assert nonceBefore != nonceAfter => ( - f.selector == sig:schedule(address,bytes,uint48).selector && - nonceAfter == nonceBefore + 1 - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ State transitions: getSchedule │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getScheduleChangeTime(bytes32 operationId) { - env e1; - env e2; - - // values before - mathint scheduleBefore = getSchedule(e1, operationId); - - // time pass: e1 → e2 - require clock(e1) <= clock(e2); - - // values after - mathint scheduleAfter = getSchedule(e2, operationId); - - // transition - assert scheduleBefore != scheduleAfter => ( - scheduleBefore + expiration() > clock(e1) && - scheduleBefore + expiration() <= clock(e2) && - scheduleAfter == 0 - ); -} - -rule getScheduleChangeCall(bytes32 operationId) { - env e; - - // values before - mathint scheduleBefore = getSchedule(e, operationId); - - // arbitrary function call - method f; calldataarg args; f(e, args); - - // values after - mathint scheduleAfter = getSchedule(e, operationId); - - // transitions - assert scheduleBefore != scheduleAfter => ( - (f.selector == sig:schedule(address,bytes,uint48).selector && scheduleAfter >= clock(e)) || - (f.selector == sig:execute(address,bytes).selector && scheduleAfter == 0 ) || - (f.selector == sig:cancel(address,address,bytes).selector && scheduleAfter == 0 ) || - (f.selector == sig:consumeScheduledOp(address,bytes).selector && scheduleAfter == 0 ) || - (isOnlyAuthorized(to_bytes4(f.selector)) && scheduleAfter == 0 ) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Functions: restricted functions can only be called by owner │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule restrictedFunctions(env e) { - require nonpayable(e); - require sanity(e); - - method f; - calldataarg args; - - f(e,args); - - assert ( - f.selector == sig:labelRole(uint64,string).selector || - f.selector == sig:setRoleAdmin(uint64,uint64).selector || - f.selector == sig:setRoleGuardian(uint64,uint64).selector || - f.selector == sig:setGrantDelay(uint64,uint32).selector || - f.selector == sig:setTargetAdminDelay(address,uint32).selector || - f.selector == sig:updateAuthority(address,address).selector || - f.selector == sig:setTargetClosed(address,bool).selector || - f.selector == sig:setTargetFunctionRole(address,bytes4[],uint64).selector - ) => ( - hasRole_isMember(e, ADMIN_ROLE(), e.msg.sender) || e.msg.sender == currentContract - ); -} - -rule restrictedFunctionsGrantRole(env e) { - require nonpayable(e); - require sanity(e); - - uint64 roleId; - address account; - uint32 executionDelay; - - // We want to check that the caller has the admin role before we possibly grant it. - bool hasAdminRoleBefore = hasRole_isMember(e, getRoleAdmin(roleId), e.msg.sender); - - grantRole(e, roleId, account, executionDelay); - - assert hasAdminRoleBefore || e.msg.sender == currentContract; -} - -rule restrictedFunctionsRevokeRole(env e) { - require nonpayable(e); - require sanity(e); - - uint64 roleId; - address account; - - // This is needed if roleId is self-administered, the `revokeRole` call could target - // e.msg.sender and remove the very role that is necessary for authorizing the call. - bool hasAdminRoleBefore = hasRole_isMember(e, getRoleAdmin(roleId), e.msg.sender); - - revokeRole(e, roleId, account); - - assert hasAdminRoleBefore || e.msg.sender == currentContract; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Functions: canCall delay is enforced for calls to execute (only for others target) │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -// getScheduleChangeCall proves that only {schedule} can set an operation schedule to a non 0 value -rule callDelayEnforce_scheduleInTheFuture(env e) { - address target; - bytes data; - uint48 when; - - // Condition: calling a third party with a delay - mathint delay = canCallExtended_delay(e, e.msg.sender, target, data); - require delay > 0; - - // Schedule - schedule(e, target, data, when); - - // Get operation schedule - mathint timepoint = getSchedule(e, hashOperation(e.msg.sender, target, data)); - - // Schedule is far enough in the future - assert timepoint == max(clock(e) + delay, when); -} - -rule callDelayEnforce_executeAfterDelay(env e) { - address target; - bytes data; - - // Condition: calling a third party with a delay - mathint delay = canCallExtended_delay(e, e.msg.sender, target, data); - - // Get operation schedule before - mathint scheduleBefore = getSchedule(e, hashOperation(e.msg.sender, target, data)); - - // Do call - execute@withrevert(e, target, data); - bool success = !lastReverted; - - // Get operation schedule after - mathint scheduleAfter = getSchedule(e, hashOperation(e.msg.sender, target, data)); - - // Can only execute if delay is set and has passed - assert success => ( - delay > 0 => ( - scheduleBefore != 0 && - scheduleBefore <= clock(e) - ) && - scheduleAfter == 0 - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs/DoubleEndedQueue.spec b/lib_openzeppelin_contracts/certora/specs/DoubleEndedQueue.spec deleted file mode 100644 index 3b71bb4..0000000 --- a/lib_openzeppelin_contracts/certora/specs/DoubleEndedQueue.spec +++ /dev/null @@ -1,300 +0,0 @@ -import "helpers/helpers.spec"; - -methods { - function pushFront(bytes32) external envfree; - function pushBack(bytes32) external envfree; - function popFront() external returns (bytes32) envfree; - function popBack() external returns (bytes32) envfree; - function clear() external envfree; - - // exposed for FV - function begin() external returns (uint128) envfree; - function end() external returns (uint128) envfree; - - // view - function length() external returns (uint256) envfree; - function empty() external returns (bool) envfree; - function front() external returns (bytes32) envfree; - function back() external returns (bytes32) envfree; - function at_(uint256) external returns (bytes32) envfree; // at is a reserved word -} - -definition full() returns bool = length() == max_uint128; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: empty() is length 0 and no element exists │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant emptiness() - empty() <=> length() == 0 - filtered { f -> !f.isView } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: front points to the first index and back points to the last one │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant queueFront() - at_(0) == front() - filtered { f -> !f.isView } - -invariant queueBack() - at_(require_uint256(length() - 1)) == back() - filtered { f -> !f.isView } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: pushFront adds an element at the beginning of the queue │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pushFront(bytes32 value) { - uint256 lengthBefore = length(); - bool fullBefore = full(); - - pushFront@withrevert(value); - bool success = !lastReverted; - - // liveness - assert success <=> !fullBefore, "never revert if not previously full"; - - // effect - assert success => front() == value, "front set to value"; - assert success => to_mathint(length()) == lengthBefore + 1, "queue extended"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: pushFront preserves the previous values in the queue with a +1 offset │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pushFrontConsistency(uint256 index) { - bytes32 beforeAt = at_(index); - - bytes32 value; - pushFront(value); - - // try to read value - bytes32 afterAt = at_@withrevert(require_uint256(index + 1)); - - assert !lastReverted, "value still there"; - assert afterAt == beforeAt, "data is preserved"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: pushBack adds an element at the end of the queue │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pushBack(bytes32 value) { - uint256 lengthBefore = length(); - bool fullBefore = full(); - - pushBack@withrevert(value); - bool success = !lastReverted; - - // liveness - assert success <=> !fullBefore, "never revert if not previously full"; - - // effect - assert success => back() == value, "back set to value"; - assert success => to_mathint(length()) == lengthBefore + 1, "queue increased"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: pushBack preserves the previous values in the queue │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pushBackConsistency(uint256 index) { - bytes32 beforeAt = at_(index); - - bytes32 value; - pushBack(value); - - // try to read value - bytes32 afterAt = at_@withrevert(index); - - assert !lastReverted, "value still there"; - assert afterAt == beforeAt, "data is preserved"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: popFront removes an element from the beginning of the queue │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule popFront { - uint256 lengthBefore = length(); - bytes32 frontBefore = front@withrevert(); - - bytes32 popped = popFront@withrevert(); - bool success = !lastReverted; - - // liveness - assert success <=> lengthBefore != 0, "never reverts if not previously empty"; - - // effect - assert success => frontBefore == popped, "previous front is returned"; - assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: at(x) is preserved and offset to at(x - 1) after calling popFront | -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule popFrontConsistency(uint256 index) { - // Read (any) value that is not the front (this asserts the value exists / the queue is long enough) - require index > 1; - bytes32 before = at_(index); - - popFront(); - - // try to read value - bytes32 after = at_@withrevert(require_uint256(index - 1)); - - assert !lastReverted, "value still exists in the queue"; - assert before == after, "values are offset and not modified"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: popBack removes an element from the end of the queue │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule popBack { - uint256 lengthBefore = length(); - bytes32 backBefore = back@withrevert(); - - bytes32 popped = popBack@withrevert(); - bool success = !lastReverted; - - // liveness - assert success <=> lengthBefore != 0, "never reverts if not previously empty"; - - // effect - assert success => backBefore == popped, "previous back is returned"; - assert success => to_mathint(length()) == lengthBefore - 1, "queue decreased"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: at(x) is preserved after calling popBack | -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule popBackConsistency(uint256 index) { - // Read (any) value that is not the back (this asserts the value exists / the queue is long enough) - require to_mathint(index) < length() - 1; - bytes32 before = at_(index); - - popBack(); - - // try to read value - bytes32 after = at_@withrevert(index); - - assert !lastReverted, "value still exists in the queue"; - assert before == after, "values are offset and not modified"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: clear sets length to 0 │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule clear { - clear@withrevert(); - - // liveness - assert !lastReverted, "never reverts"; - - // effect - assert length() == 0, "sets length to 0"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: front/back access reverts only if the queue is empty or querying out of bounds │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyEmptyOrFullRevert(env e) { - require nonpayable(e); - - method f; - calldataarg args; - - bool emptyBefore = empty(); - bool fullBefore = full(); - - f@withrevert(e, args); - - assert lastReverted => ( - (f.selector == sig:front().selector && emptyBefore) || - (f.selector == sig:back().selector && emptyBefore) || - (f.selector == sig:popFront().selector && emptyBefore) || - (f.selector == sig:popBack().selector && emptyBefore) || - (f.selector == sig:pushFront(bytes32).selector && fullBefore ) || - (f.selector == sig:pushBack(bytes32).selector && fullBefore ) || - f.selector == sig:at_(uint256).selector // revert conditions are verified in onlyOutOfBoundsRevert - ), "only revert if empty or out of bounds"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: at(index) only reverts if index is out of bounds | -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyOutOfBoundsRevert(uint256 index) { - at_@withrevert(index); - - assert lastReverted <=> index >= length(), "only reverts if index is out of bounds"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: only clear/push/pop operations can change the length of the queue │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noLengthChange(env e) { - method f; - calldataarg args; - - uint256 lengthBefore = length(); - f(e, args); - uint256 lengthAfter = length(); - - assert lengthAfter != lengthBefore => ( - (f.selector == sig:pushFront(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) || - (f.selector == sig:pushBack(bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) || - (f.selector == sig:popBack().selector && to_mathint(lengthAfter) == lengthBefore - 1) || - (f.selector == sig:popFront().selector && to_mathint(lengthAfter) == lengthBefore - 1) || - (f.selector == sig:clear().selector && lengthAfter == 0) - ), "length is only affected by clear/pop/push operations"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: only push/pop can change values bounded in the queue (outside values aren't cleared) │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noDataChange(env e) { - method f; - calldataarg args; - - uint256 index; - bytes32 atBefore = at_(index); - f(e, args); - bytes32 atAfter = at_@withrevert(index); - bool atAfterSuccess = !lastReverted; - - assert !atAfterSuccess <=> ( - (f.selector == sig:clear().selector ) || - (f.selector == sig:popBack().selector && index == length()) || - (f.selector == sig:popFront().selector && index == length()) - ), "indexes of the queue are only removed by clear or pop"; - - assert atAfterSuccess && atAfter != atBefore => ( - f.selector == sig:popFront().selector || - f.selector == sig:pushFront(bytes32).selector - ), "values of the queue are only changed by popFront or pushFront"; -} diff --git a/lib_openzeppelin_contracts/certora/specs/ERC20.spec b/lib_openzeppelin_contracts/certora/specs/ERC20.spec deleted file mode 100644 index 21a0335..0000000 --- a/lib_openzeppelin_contracts/certora/specs/ERC20.spec +++ /dev/null @@ -1,352 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IERC20.spec"; -import "methods/IERC2612.spec"; - -methods { - // exposed for FV - function mint(address,uint256) external; - function burn(address,uint256) external; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Ghost & hooks: sum of all balances │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -ghost mathint sumOfBalances { - init_state axiom sumOfBalances == 0; -} - -// Because `balance` has a uint256 type, any balance addition in CVL1 behaved as a `require_uint256()` casting, -// leaving out the possibility of overflow. This is not the case in CVL2 where casting became more explicit. -// A counterexample in CVL2 is having an initial state where Alice initial balance is larger than totalSupply, which -// overflows Alice's balance when receiving a transfer. This is not possible unless the contract is deployed into an -// already used address (or upgraded from corrupted state). -// We restrict such behavior by making sure no balance is greater than the sum of balances. -hook Sload uint256 balance _balances[KEY address addr] STORAGE { - require sumOfBalances >= to_mathint(balance); -} - -hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE { - sumOfBalances = sumOfBalances - oldValue + newValue; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: totalSupply is the sum of all balances │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant totalSupplyIsSumOfBalances() - to_mathint(totalSupply()) == sumOfBalances; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: balance of address(0) is 0 │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant zeroAddressNoBalance() - balanceOf(0) == 0; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: only mint and burn can change total supply │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noChangeTotalSupply(env e) { - requireInvariant totalSupplyIsSumOfBalances(); - - method f; - calldataarg args; - - uint256 totalSupplyBefore = totalSupply(); - f(e, args); - uint256 totalSupplyAfter = totalSupply(); - - assert totalSupplyAfter > totalSupplyBefore => f.selector == sig:mint(address,uint256).selector; - assert totalSupplyAfter < totalSupplyBefore => f.selector == sig:burn(address,uint256).selector; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: only the token holder or an approved third party can reduce an account's balance │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyAuthorizedCanTransfer(env e) { - requireInvariant totalSupplyIsSumOfBalances(); - - method f; - calldataarg args; - address account; - - uint256 allowanceBefore = allowance(account, e.msg.sender); - uint256 balanceBefore = balanceOf(account); - f(e, args); - uint256 balanceAfter = balanceOf(account); - - assert ( - balanceAfter < balanceBefore - ) => ( - f.selector == sig:burn(address,uint256).selector || - e.msg.sender == account || - balanceBefore - balanceAfter <= to_mathint(allowanceBefore) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: only the token holder (or a permit) can increase allowance. The spender can decrease it by using it │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyHolderOfSpenderCanChangeAllowance(env e) { - requireInvariant totalSupplyIsSumOfBalances(); - - method f; - calldataarg args; - address holder; - address spender; - - uint256 allowanceBefore = allowance(holder, spender); - f(e, args); - uint256 allowanceAfter = allowance(holder, spender); - - assert ( - allowanceAfter > allowanceBefore - ) => ( - (f.selector == sig:approve(address,uint256).selector && e.msg.sender == holder) || - (f.selector == sig:permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector) - ); - - assert ( - allowanceAfter < allowanceBefore - ) => ( - (f.selector == sig:transferFrom(address,address,uint256).selector && e.msg.sender == spender) || - (f.selector == sig:approve(address,uint256).selector && e.msg.sender == holder ) || - (f.selector == sig:permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: mint behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule mint(env e) { - requireInvariant totalSupplyIsSumOfBalances(); - require nonpayable(e); - - address to; - address other; - uint256 amount; - - // cache state - uint256 toBalanceBefore = balanceOf(to); - uint256 otherBalanceBefore = balanceOf(other); - uint256 totalSupplyBefore = totalSupply(); - - // run transaction - mint@withrevert(e, to, amount); - - // check outcome - if (lastReverted) { - assert to == 0 || totalSupplyBefore + amount > max_uint256; - } else { - // updates balance and totalSupply - assert to_mathint(balanceOf(to)) == toBalanceBefore + amount; - assert to_mathint(totalSupply()) == totalSupplyBefore + amount; - - // no other balance is modified - assert balanceOf(other) != otherBalanceBefore => other == to; - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: burn behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule burn(env e) { - requireInvariant totalSupplyIsSumOfBalances(); - require nonpayable(e); - - address from; - address other; - uint256 amount; - - // cache state - uint256 fromBalanceBefore = balanceOf(from); - uint256 otherBalanceBefore = balanceOf(other); - uint256 totalSupplyBefore = totalSupply(); - - // run transaction - burn@withrevert(e, from, amount); - - // check outcome - if (lastReverted) { - assert from == 0 || fromBalanceBefore < amount; - } else { - // updates balance and totalSupply - assert to_mathint(balanceOf(from)) == fromBalanceBefore - amount; - assert to_mathint(totalSupply()) == totalSupplyBefore - amount; - - // no other balance is modified - assert balanceOf(other) != otherBalanceBefore => other == from; - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: transfer behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule transfer(env e) { - requireInvariant totalSupplyIsSumOfBalances(); - require nonpayable(e); - - address holder = e.msg.sender; - address recipient; - address other; - uint256 amount; - - // cache state - uint256 holderBalanceBefore = balanceOf(holder); - uint256 recipientBalanceBefore = balanceOf(recipient); - uint256 otherBalanceBefore = balanceOf(other); - - // run transaction - transfer@withrevert(e, recipient, amount); - - // check outcome - if (lastReverted) { - assert holder == 0 || recipient == 0 || amount > holderBalanceBefore; - } else { - // balances of holder and recipient are updated - assert to_mathint(balanceOf(holder)) == holderBalanceBefore - (holder == recipient ? 0 : amount); - assert to_mathint(balanceOf(recipient)) == recipientBalanceBefore + (holder == recipient ? 0 : amount); - - // no other balance is modified - assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient); - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: transferFrom behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule transferFrom(env e) { - requireInvariant totalSupplyIsSumOfBalances(); - require nonpayable(e); - - address spender = e.msg.sender; - address holder; - address recipient; - address other; - uint256 amount; - - // cache state - uint256 allowanceBefore = allowance(holder, spender); - uint256 holderBalanceBefore = balanceOf(holder); - uint256 recipientBalanceBefore = balanceOf(recipient); - uint256 otherBalanceBefore = balanceOf(other); - - // run transaction - transferFrom@withrevert(e, holder, recipient, amount); - - // check outcome - if (lastReverted) { - assert holder == 0 || recipient == 0 || spender == 0 || amount > holderBalanceBefore || amount > allowanceBefore; - } else { - // allowance is valid & updated - assert allowanceBefore >= amount; - assert to_mathint(allowance(holder, spender)) == (allowanceBefore == max_uint256 ? max_uint256 : allowanceBefore - amount); - - // balances of holder and recipient are updated - assert to_mathint(balanceOf(holder)) == holderBalanceBefore - (holder == recipient ? 0 : amount); - assert to_mathint(balanceOf(recipient)) == recipientBalanceBefore + (holder == recipient ? 0 : amount); - - // no other balance is modified - assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient); - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: approve behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule approve(env e) { - require nonpayable(e); - - address holder = e.msg.sender; - address spender; - address otherHolder; - address otherSpender; - uint256 amount; - - // cache state - uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender); - - // run transaction - approve@withrevert(e, spender, amount); - - // check outcome - if (lastReverted) { - assert holder == 0 || spender == 0; - } else { - // allowance is updated - assert allowance(holder, spender) == amount; - - // other allowances are untouched - assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender); - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: permit behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule permit(env e) { - require nonpayable(e); - - address holder; - address spender; - uint256 amount; - uint256 deadline; - uint8 v; - bytes32 r; - bytes32 s; - - address account1; - address account2; - address account3; - - // cache state - uint256 nonceBefore = nonces(holder); - uint256 otherNonceBefore = nonces(account1); - uint256 otherAllowanceBefore = allowance(account2, account3); - - // sanity: nonce overflow, which possible in theory, is assumed to be impossible in practice - require nonceBefore < max_uint256; - require otherNonceBefore < max_uint256; - - // run transaction - permit@withrevert(e, holder, spender, amount, deadline, v, r, s); - - // check outcome - if (lastReverted) { - // Without formally checking the signature, we can't verify exactly the revert causes - assert true; - } else { - // allowance and nonce are updated - assert allowance(holder, spender) == amount; - assert to_mathint(nonces(holder)) == nonceBefore + 1; - - // deadline was respected - assert deadline >= e.block.timestamp; - - // no other allowance or nonce is modified - assert nonces(account1) != otherNonceBefore => account1 == holder; - assert allowance(account2, account3) != otherAllowanceBefore => (account2 == holder && account3 == spender); - } -} diff --git a/lib_openzeppelin_contracts/certora/specs/ERC20FlashMint.spec b/lib_openzeppelin_contracts/certora/specs/ERC20FlashMint.spec deleted file mode 100644 index 4071052..0000000 --- a/lib_openzeppelin_contracts/certora/specs/ERC20FlashMint.spec +++ /dev/null @@ -1,55 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IERC20.spec"; -import "methods/IERC3156FlashLender.spec"; -import "methods/IERC3156FlashBorrower.spec"; - -methods { - // non standard ERC-3156 functions - function flashFeeReceiver() external returns (address) envfree; - - // function summaries below - function _._update(address from, address to, uint256 amount) internal => specUpdate(from, to, amount) expect void ALL; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Ghost: track mint and burns in the CVL │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -ghost mapping(address => mathint) trackedMintAmount; -ghost mapping(address => mathint) trackedBurnAmount; -ghost mapping(address => mapping(address => mathint)) trackedTransferedAmount; - -function specUpdate(address from, address to, uint256 amount) { - if (from == 0 && to == 0) { assert(false); } // defensive - - if (from == 0) { - trackedMintAmount[to] = amount; - } else if (to == 0) { - trackedBurnAmount[from] = amount; - } else { - trackedTransferedAmount[from][to] = amount; - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: When doing a flashLoan, "amount" is minted and burnt, additionally, the fee is either burnt │ -│ (if the fee recipient is 0) or transferred (if the fee recipient is not 0) │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule checkMintAndBurn(env e) { - address receiver; - address token; - uint256 amount; - bytes data; - - uint256 fees = flashFee(token, amount); - address recipient = flashFeeReceiver(); - - flashLoan(e, receiver, token, amount, data); - - assert trackedMintAmount[receiver] == to_mathint(amount); - assert trackedBurnAmount[receiver] == amount + to_mathint(recipient == 0 ? fees : 0); - assert (fees > 0 && recipient != 0) => trackedTransferedAmount[receiver][recipient] == to_mathint(fees); -} diff --git a/lib_openzeppelin_contracts/certora/specs/ERC20Wrapper.spec b/lib_openzeppelin_contracts/certora/specs/ERC20Wrapper.spec deleted file mode 100644 index 04e6704..0000000 --- a/lib_openzeppelin_contracts/certora/specs/ERC20Wrapper.spec +++ /dev/null @@ -1,198 +0,0 @@ -import "helpers/helpers.spec"; -import "ERC20.spec"; - -methods { - function underlying() external returns(address) envfree; - function underlyingTotalSupply() external returns(uint256) envfree; - function underlyingBalanceOf(address) external returns(uint256) envfree; - function underlyingAllowanceToThis(address) external returns(uint256) envfree; - - function depositFor(address, uint256) external returns(bool); - function withdrawTo(address, uint256) external returns(bool); - function recover(address) external returns(uint256); -} - -use invariant totalSupplyIsSumOfBalances; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -definition underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool = - underlyingBalanceOf(a) <= underlyingTotalSupply(); - -definition sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool = - a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= to_mathint(underlyingTotalSupply()); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant totalSupplyIsSmallerThanUnderlyingBalance() - totalSupply() <= underlyingBalanceOf(currentContract) && - underlyingBalanceOf(currentContract) <= underlyingTotalSupply() && - underlyingTotalSupply() <= max_uint256 - { - preserved { - requireInvariant totalSupplyIsSumOfBalances; - require underlyingBalancesLowerThanUnderlyingSupply(currentContract); - } - preserved depositFor(address account, uint256 amount) with (env e) { - require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract); - } - } - -invariant noSelfWrap() - currentContract != underlying(); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: depositFor liveness and effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule depositFor(env e) { - require nonpayable(e); - - address sender = e.msg.sender; - address receiver; - address other; - uint256 amount; - - // sanity - requireInvariant noSelfWrap; - requireInvariant totalSupplyIsSumOfBalances; - requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; - require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender); - - uint256 balanceBefore = balanceOf(receiver); - uint256 supplyBefore = totalSupply(); - uint256 senderUnderlyingBalanceBefore = underlyingBalanceOf(sender); - uint256 senderUnderlyingAllowanceBefore = underlyingAllowanceToThis(sender); - uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract); - uint256 underlyingSupplyBefore = underlyingTotalSupply(); - - uint256 otherBalanceBefore = balanceOf(other); - uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); - - depositFor@withrevert(e, receiver, amount); - bool success = !lastReverted; - - // liveness - assert success <=> ( - sender != currentContract && // invalid sender - sender != 0 && // invalid sender - receiver != currentContract && // invalid receiver - receiver != 0 && // invalid receiver - amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance - amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance - ); - - // effects - assert success => ( - to_mathint(balanceOf(receiver)) == balanceBefore + amount && - to_mathint(totalSupply()) == supplyBefore + amount && - to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore + amount && - to_mathint(underlyingBalanceOf(sender)) == senderUnderlyingBalanceBefore - amount - ); - - // no side effect - assert underlyingTotalSupply() == underlyingSupplyBefore; - assert balanceOf(other) != otherBalanceBefore => other == receiver; - assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: withdrawTo liveness and effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule withdrawTo(env e) { - require nonpayable(e); - - address sender = e.msg.sender; - address receiver; - address other; - uint256 amount; - - // sanity - requireInvariant noSelfWrap; - requireInvariant totalSupplyIsSumOfBalances; - requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; - require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver); - - uint256 balanceBefore = balanceOf(sender); - uint256 supplyBefore = totalSupply(); - uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver); - uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract); - uint256 underlyingSupplyBefore = underlyingTotalSupply(); - - uint256 otherBalanceBefore = balanceOf(other); - uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); - - withdrawTo@withrevert(e, receiver, amount); - bool success = !lastReverted; - - // liveness - assert success <=> ( - sender != 0 && // invalid sender - receiver != currentContract && // invalid receiver - receiver != 0 && // invalid receiver - amount <= balanceBefore // withdraw doesn't exceed balance - ); - - // effects - assert success => ( - to_mathint(balanceOf(sender)) == balanceBefore - amount && - to_mathint(totalSupply()) == supplyBefore - amount && - to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) && - to_mathint(underlyingBalanceOf(receiver)) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0) - ); - - // no side effect - assert underlyingTotalSupply() == underlyingSupplyBefore; - assert balanceOf(other) != otherBalanceBefore => other == sender; - assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: recover liveness and effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule recover(env e) { - require nonpayable(e); - - address receiver; - address other; - - // sanity - requireInvariant noSelfWrap; - requireInvariant totalSupplyIsSumOfBalances; - requireInvariant totalSupplyIsSmallerThanUnderlyingBalance; - - mathint value = underlyingBalanceOf(currentContract) - totalSupply(); - uint256 supplyBefore = totalSupply(); - uint256 balanceBefore = balanceOf(receiver); - - uint256 otherBalanceBefore = balanceOf(other); - uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other); - - recover@withrevert(e, receiver); - bool success = !lastReverted; - - // liveness - assert success <=> receiver != 0; - - // effect - assert success => ( - to_mathint(balanceOf(receiver)) == balanceBefore + value && - to_mathint(totalSupply()) == supplyBefore + value && - totalSupply() == underlyingBalanceOf(currentContract) - ); - - // no side effect - assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore; - assert balanceOf(other) != otherBalanceBefore => other == receiver; -} diff --git a/lib_openzeppelin_contracts/certora/specs/ERC721.spec b/lib_openzeppelin_contracts/certora/specs/ERC721.spec deleted file mode 100644 index bad4c47..0000000 --- a/lib_openzeppelin_contracts/certora/specs/ERC721.spec +++ /dev/null @@ -1,679 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IERC721.spec"; -import "methods/IERC721Receiver.spec"; - -methods { - // exposed for FV - function mint(address,uint256) external; - function safeMint(address,uint256) external; - function safeMint(address,uint256,bytes) external; - function burn(uint256) external; - - function unsafeOwnerOf(uint256) external returns (address) envfree; - function unsafeGetApproved(uint256) external returns (address) envfree; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helpers │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ - -definition authSanity(env e) returns bool = e.msg.sender != 0; - -// Could be broken in theory, but not in practice -definition balanceLimited(address account) returns bool = balanceOf(account) < max_uint256; - -function helperTransferWithRevert(env e, method f, address from, address to, uint256 tokenId) { - if (f.selector == sig:transferFrom(address,address,uint256).selector) { - transferFrom@withrevert(e, from, to, tokenId); - } else if (f.selector == sig:safeTransferFrom(address,address,uint256).selector) { - safeTransferFrom@withrevert(e, from, to, tokenId); - } else if (f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector) { - bytes params; - require params.length < 0xffff; - safeTransferFrom@withrevert(e, from, to, tokenId, params); - } else { - calldataarg args; - f@withrevert(e, args); - } -} - -function helperMintWithRevert(env e, method f, address to, uint256 tokenId) { - if (f.selector == sig:mint(address,uint256).selector) { - mint@withrevert(e, to, tokenId); - } else if (f.selector == sig:safeMint(address,uint256).selector) { - safeMint@withrevert(e, to, tokenId); - } else if (f.selector == sig:safeMint(address,uint256,bytes).selector) { - bytes params; - require params.length < 0xffff; - safeMint@withrevert(e, to, tokenId, params); - } else { - require false; - } -} - -function helperSoundFnCall(env e, method f) { - if (f.selector == sig:mint(address,uint256).selector) { - address to; uint256 tokenId; - require balanceLimited(to); - requireInvariant notMintedUnset(tokenId); - mint(e, to, tokenId); - } else if (f.selector == sig:safeMint(address,uint256).selector) { - address to; uint256 tokenId; - require balanceLimited(to); - requireInvariant notMintedUnset(tokenId); - safeMint(e, to, tokenId); - } else if (f.selector == sig:safeMint(address,uint256,bytes).selector) { - address to; uint256 tokenId; bytes data; - require data.length < 0xffff; - require balanceLimited(to); - requireInvariant notMintedUnset(tokenId); - safeMint(e, to, tokenId, data); - } else if (f.selector == sig:burn(uint256).selector) { - uint256 tokenId; - requireInvariant ownerHasBalance(tokenId); - requireInvariant notMintedUnset(tokenId); - burn(e, tokenId); - } else if (f.selector == sig:transferFrom(address,address,uint256).selector) { - address from; address to; uint256 tokenId; - require balanceLimited(to); - requireInvariant ownerHasBalance(tokenId); - requireInvariant notMintedUnset(tokenId); - transferFrom(e, from, to, tokenId); - } else if (f.selector == sig:safeTransferFrom(address,address,uint256).selector) { - address from; address to; uint256 tokenId; - require balanceLimited(to); - requireInvariant ownerHasBalance(tokenId); - requireInvariant notMintedUnset(tokenId); - safeTransferFrom(e, from, to, tokenId); - } else if (f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector) { - address from; address to; uint256 tokenId; bytes data; - require data.length < 0xffff; - require balanceLimited(to); - requireInvariant ownerHasBalance(tokenId); - requireInvariant notMintedUnset(tokenId); - safeTransferFrom(e, from, to, tokenId, data); - } else { - calldataarg args; - f(e, args); - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Ghost & hooks: ownership count │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -ghost mathint _ownedTotal { - init_state axiom _ownedTotal == 0; -} - -ghost mapping(address => mathint) _ownedByUser { - init_state axiom forall address a. _ownedByUser[a] == 0; -} - -hook Sstore _owners[KEY uint256 tokenId] address newOwner (address oldOwner) STORAGE { - _ownedByUser[newOwner] = _ownedByUser[newOwner] + to_mathint(newOwner != 0 ? 1 : 0); - _ownedByUser[oldOwner] = _ownedByUser[oldOwner] - to_mathint(oldOwner != 0 ? 1 : 0); - _ownedTotal = _ownedTotal + to_mathint(newOwner != 0 ? 1 : 0) - to_mathint(oldOwner != 0 ? 1 : 0); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Ghost & hooks: sum of all balances │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -ghost mathint _supply { - init_state axiom _supply == 0; -} - -ghost mapping(address => mathint) _balances { - init_state axiom forall address a. _balances[a] == 0; -} - -hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE { - _supply = _supply - oldValue + newValue; -} - -// TODO: This used to not be necessary. We should try to remove it. In order to do so, we will probably need to add -// many "preserved" directive that require the "balanceOfConsistency" invariant on the accounts involved. -hook Sload uint256 value _balances[KEY address user] STORAGE { - require _balances[user] == to_mathint(value); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: number of owned tokens is the sum of all balances │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant ownedTotalIsSumOfBalances() - _ownedTotal == _supply - { - preserved mint(address to, uint256 tokenId) with (env e) { - require balanceLimited(to); - } - preserved safeMint(address to, uint256 tokenId) with (env e) { - require balanceLimited(to); - } - preserved safeMint(address to, uint256 tokenId, bytes data) with (env e) { - require balanceLimited(to); - } - preserved burn(uint256 tokenId) with (env e) { - requireInvariant ownerHasBalance(tokenId); - requireInvariant balanceOfConsistency(ownerOf(tokenId)); - } - preserved transferFrom(address from, address to, uint256 tokenId) with (env e) { - require balanceLimited(to); - requireInvariant ownerHasBalance(tokenId); - requireInvariant balanceOfConsistency(from); - requireInvariant balanceOfConsistency(to); - } - preserved safeTransferFrom(address from, address to, uint256 tokenId) with (env e) { - require balanceLimited(to); - requireInvariant ownerHasBalance(tokenId); - requireInvariant balanceOfConsistency(from); - requireInvariant balanceOfConsistency(to); - } - preserved safeTransferFrom(address from, address to, uint256 tokenId, bytes data) with (env e) { - require balanceLimited(to); - requireInvariant ownerHasBalance(tokenId); - requireInvariant balanceOfConsistency(from); - requireInvariant balanceOfConsistency(to); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: balanceOf is the number of tokens owned │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant balanceOfConsistency(address user) - to_mathint(balanceOf(user)) == _ownedByUser[user] && - to_mathint(balanceOf(user)) == _balances[user] - { - preserved { - require balanceLimited(user); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: owner of a token must have some balance │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant ownerHasBalance(uint256 tokenId) - balanceOf(ownerOf(tokenId)) > 0 - { - preserved { - requireInvariant balanceOfConsistency(ownerOf(tokenId)); - require balanceLimited(ownerOf(tokenId)); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: balance of address(0) is 0 │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule zeroAddressBalanceRevert() { - balanceOf@withrevert(0); - assert lastReverted; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: address(0) has no authorized operator │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant zeroAddressHasNoApprovedOperator(address a) - !isApprovedForAll(0, a) - { - preserved with (env e) { - require nonzerosender(e); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: tokens that do not exist are not owned and not approved │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant notMintedUnset(uint256 tokenId) - unsafeOwnerOf(tokenId) == 0 => unsafeGetApproved(tokenId) == 0; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: unsafeOwnerOf and unsafeGetApproved don't revert + ownerOf and getApproved revert if token does not exist │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule notMintedRevert(uint256 tokenId) { - requireInvariant notMintedUnset(tokenId); - - address _owner = unsafeOwnerOf@withrevert(tokenId); - assert !lastReverted; - - address _approved = unsafeGetApproved@withrevert(tokenId); - assert !lastReverted; - - address owner = ownerOf@withrevert(tokenId); - assert lastReverted <=> _owner == 0; - assert !lastReverted => _owner == owner; - - address approved = getApproved@withrevert(tokenId); - assert lastReverted <=> _owner == 0; - assert !lastReverted => _approved == approved; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: total supply can only change through mint and burn │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule supplyChange(env e) { - require nonzerosender(e); - requireInvariant zeroAddressHasNoApprovedOperator(e.msg.sender); - - mathint supplyBefore = _supply; - method f; helperSoundFnCall(e, f); - mathint supplyAfter = _supply; - - assert supplyAfter > supplyBefore => ( - supplyAfter == supplyBefore + 1 && - ( - f.selector == sig:mint(address,uint256).selector || - f.selector == sig:safeMint(address,uint256).selector || - f.selector == sig:safeMint(address,uint256,bytes).selector - ) - ); - assert supplyAfter < supplyBefore => ( - supplyAfter == supplyBefore - 1 && - f.selector == sig:burn(uint256).selector - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: balanceOf can only change through mint, burn or transfers. balanceOf cannot change by more than 1. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule balanceChange(env e, address account) { - requireInvariant balanceOfConsistency(account); - require balanceLimited(account); - - mathint balanceBefore = balanceOf(account); - method f; helperSoundFnCall(e, f); - mathint balanceAfter = balanceOf(account); - - // balance can change by at most 1 - assert balanceBefore != balanceAfter => ( - balanceAfter == balanceBefore - 1 || - balanceAfter == balanceBefore + 1 - ); - - // only selected function can change balances - assert balanceBefore != balanceAfter => ( - f.selector == sig:transferFrom(address,address,uint256).selector || - f.selector == sig:safeTransferFrom(address,address,uint256).selector || - f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector || - f.selector == sig:mint(address,uint256).selector || - f.selector == sig:safeMint(address,uint256).selector || - f.selector == sig:safeMint(address,uint256,bytes).selector || - f.selector == sig:burn(uint256).selector - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: ownership can only change through mint, burn or transfers. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule ownershipChange(env e, uint256 tokenId) { - require nonzerosender(e); - requireInvariant zeroAddressHasNoApprovedOperator(e.msg.sender); - - address ownerBefore = unsafeOwnerOf(tokenId); - method f; helperSoundFnCall(e, f); - address ownerAfter = unsafeOwnerOf(tokenId); - - assert ownerBefore == 0 && ownerAfter != 0 => ( - f.selector == sig:mint(address,uint256).selector || - f.selector == sig:safeMint(address,uint256).selector || - f.selector == sig:safeMint(address,uint256,bytes).selector - ); - - assert ownerBefore != 0 && ownerAfter == 0 => ( - f.selector == sig:burn(uint256).selector - ); - - assert (ownerBefore != ownerAfter && ownerBefore != 0 && ownerAfter != 0) => ( - f.selector == sig:transferFrom(address,address,uint256).selector || - f.selector == sig:safeTransferFrom(address,address,uint256).selector || - f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: token approval can only change through approve or transfers (implicitly). │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule approvalChange(env e, uint256 tokenId) { - address approvalBefore = unsafeGetApproved(tokenId); - method f; helperSoundFnCall(e, f); - address approvalAfter = unsafeGetApproved(tokenId); - - // approve can set any value, other functions reset - assert approvalBefore != approvalAfter => ( - f.selector == sig:approve(address,uint256).selector || - ( - ( - f.selector == sig:transferFrom(address,address,uint256).selector || - f.selector == sig:safeTransferFrom(address,address,uint256).selector || - f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector || - f.selector == sig:burn(uint256).selector - ) && approvalAfter == 0 - ) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: approval for all tokens can only change through isApprovedForAll. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule approvedForAllChange(env e, address owner, address spender) { - bool approvedForAllBefore = isApprovedForAll(owner, spender); - method f; helperSoundFnCall(e, f); - bool approvedForAllAfter = isApprovedForAll(owner, spender); - - assert approvedForAllBefore != approvedForAllAfter => f.selector == sig:setApprovalForAll(address,bool).selector; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: transferFrom behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule transferFrom(env e, address from, address to, uint256 tokenId) { - require nonpayable(e); - require authSanity(e); - - address operator = e.msg.sender; - uint256 otherTokenId; - address otherAccount; - - requireInvariant ownerHasBalance(tokenId); - require balanceLimited(to); - - uint256 balanceOfFromBefore = balanceOf(from); - uint256 balanceOfToBefore = balanceOf(to); - uint256 balanceOfOtherBefore = balanceOf(otherAccount); - address ownerBefore = unsafeOwnerOf(tokenId); - address otherOwnerBefore = unsafeOwnerOf(otherTokenId); - address approvalBefore = unsafeGetApproved(tokenId); - address otherApprovalBefore = unsafeGetApproved(otherTokenId); - - transferFrom@withrevert(e, from, to, tokenId); - bool success = !lastReverted; - - // liveness - assert success <=> ( - from == ownerBefore && - from != 0 && - to != 0 && - (operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator)) - ); - - // effect - assert success => ( - to_mathint(balanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1 : 0) && - to_mathint(balanceOf(to)) == balanceOfToBefore + assert_uint256(from != to ? 1 : 0) && - unsafeOwnerOf(tokenId) == to && - unsafeGetApproved(tokenId) == 0 - ); - - // no side effect - assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to); - assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; - assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: safeTransferFrom behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule safeTransferFrom(env e, method f, address from, address to, uint256 tokenId) filtered { f -> - f.selector == sig:safeTransferFrom(address,address,uint256).selector || - f.selector == sig:safeTransferFrom(address,address,uint256,bytes).selector -} { - require nonpayable(e); - require authSanity(e); - - address operator = e.msg.sender; - uint256 otherTokenId; - address otherAccount; - - requireInvariant ownerHasBalance(tokenId); - require balanceLimited(to); - - uint256 balanceOfFromBefore = balanceOf(from); - uint256 balanceOfToBefore = balanceOf(to); - uint256 balanceOfOtherBefore = balanceOf(otherAccount); - address ownerBefore = unsafeOwnerOf(tokenId); - address otherOwnerBefore = unsafeOwnerOf(otherTokenId); - address approvalBefore = unsafeGetApproved(tokenId); - address otherApprovalBefore = unsafeGetApproved(otherTokenId); - - helperTransferWithRevert(e, f, from, to, tokenId); - bool success = !lastReverted; - - assert success <=> ( - from == ownerBefore && - from != 0 && - to != 0 && - (operator == from || operator == approvalBefore || isApprovedForAll(ownerBefore, operator)) - ); - - // effect - assert success => ( - to_mathint(balanceOf(from)) == balanceOfFromBefore - assert_uint256(from != to ? 1: 0) && - to_mathint(balanceOf(to)) == balanceOfToBefore + assert_uint256(from != to ? 1: 0) && - unsafeOwnerOf(tokenId) == to && - unsafeGetApproved(tokenId) == 0 - ); - - // no side effect - assert balanceOf(otherAccount) != balanceOfOtherBefore => (otherAccount == from || otherAccount == to); - assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; - assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: mint behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule mint(env e, address to, uint256 tokenId) { - require nonpayable(e); - requireInvariant notMintedUnset(tokenId); - - uint256 otherTokenId; - address otherAccount; - - require balanceLimited(to); - - mathint supplyBefore = _supply; - uint256 balanceOfToBefore = balanceOf(to); - uint256 balanceOfOtherBefore = balanceOf(otherAccount); - address ownerBefore = unsafeOwnerOf(tokenId); - address otherOwnerBefore = unsafeOwnerOf(otherTokenId); - - mint@withrevert(e, to, tokenId); - bool success = !lastReverted; - - // liveness - assert success <=> ( - ownerBefore == 0 && - to != 0 - ); - - // effect - assert success => ( - _supply == supplyBefore + 1 && - to_mathint(balanceOf(to)) == balanceOfToBefore + 1 && - unsafeOwnerOf(tokenId) == to - ); - - // no side effect - assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to; - assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: safeMint behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule safeMint(env e, method f, address to, uint256 tokenId) filtered { f -> - f.selector == sig:safeMint(address,uint256).selector || - f.selector == sig:safeMint(address,uint256,bytes).selector -} { - require nonpayable(e); - requireInvariant notMintedUnset(tokenId); - - uint256 otherTokenId; - address otherAccount; - - require balanceLimited(to); - - mathint supplyBefore = _supply; - uint256 balanceOfToBefore = balanceOf(to); - uint256 balanceOfOtherBefore = balanceOf(otherAccount); - address ownerBefore = unsafeOwnerOf(tokenId); - address otherOwnerBefore = unsafeOwnerOf(otherTokenId); - - helperMintWithRevert(e, f, to, tokenId); - bool success = !lastReverted; - - assert success <=> ( - ownerBefore == 0 && - to != 0 - ); - - // effect - assert success => ( - _supply == supplyBefore + 1 && - to_mathint(balanceOf(to)) == balanceOfToBefore + 1 && - unsafeOwnerOf(tokenId) == to - ); - - // no side effect - assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == to; - assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: burn behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule burn(env e, uint256 tokenId) { - require nonpayable(e); - - address from = unsafeOwnerOf(tokenId); - uint256 otherTokenId; - address otherAccount; - - requireInvariant ownerHasBalance(tokenId); - - mathint supplyBefore = _supply; - uint256 balanceOfFromBefore = balanceOf(from); - uint256 balanceOfOtherBefore = balanceOf(otherAccount); - address ownerBefore = unsafeOwnerOf(tokenId); - address otherOwnerBefore = unsafeOwnerOf(otherTokenId); - address otherApprovalBefore = unsafeGetApproved(otherTokenId); - - burn@withrevert(e, tokenId); - bool success = !lastReverted; - - // liveness - assert success <=> ( - ownerBefore != 0 - ); - - // effect - assert success => ( - _supply == supplyBefore - 1 && - to_mathint(balanceOf(from)) == balanceOfFromBefore - 1 && - unsafeOwnerOf(tokenId) == 0 && - unsafeGetApproved(tokenId) == 0 - ); - - // no side effect - assert balanceOf(otherAccount) != balanceOfOtherBefore => otherAccount == from; - assert unsafeOwnerOf(otherTokenId) != otherOwnerBefore => otherTokenId == tokenId; - assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: approve behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule approve(env e, address spender, uint256 tokenId) { - require nonpayable(e); - require authSanity(e); - - address caller = e.msg.sender; - address owner = unsafeOwnerOf(tokenId); - uint256 otherTokenId; - - address otherApprovalBefore = unsafeGetApproved(otherTokenId); - - approve@withrevert(e, spender, tokenId); - bool success = !lastReverted; - - // liveness - assert success <=> ( - owner != 0 && - (owner == caller || isApprovedForAll(owner, caller)) - ); - - // effect - assert success => unsafeGetApproved(tokenId) == spender; - - // no side effect - assert unsafeGetApproved(otherTokenId) != otherApprovalBefore => otherTokenId == tokenId; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: setApprovalForAll behavior and side effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule setApprovalForAll(env e, address operator, bool approved) { - require nonpayable(e); - - address owner = e.msg.sender; - address otherOwner; - address otherOperator; - - bool otherIsApprovedForAllBefore = isApprovedForAll(otherOwner, otherOperator); - - setApprovalForAll@withrevert(e, operator, approved); - bool success = !lastReverted; - - // liveness - assert success <=> operator != 0; - - // effect - assert success => isApprovedForAll(owner, operator) == approved; - - // no side effect - assert isApprovedForAll(otherOwner, otherOperator) != otherIsApprovedForAllBefore => ( - otherOwner == owner && - otherOperator == operator - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs/EnumerableMap.spec b/lib_openzeppelin_contracts/certora/specs/EnumerableMap.spec deleted file mode 100644 index 1801d99..0000000 --- a/lib_openzeppelin_contracts/certora/specs/EnumerableMap.spec +++ /dev/null @@ -1,333 +0,0 @@ -import "helpers/helpers.spec"; - -methods { - // library - function set(bytes32,bytes32) external returns (bool) envfree; - function remove(bytes32) external returns (bool) envfree; - function contains(bytes32) external returns (bool) envfree; - function length() external returns (uint256) envfree; - function key_at(uint256) external returns (bytes32) envfree; - function value_at(uint256) external returns (bytes32) envfree; - function tryGet_contains(bytes32) external returns (bool) envfree; - function tryGet_value(bytes32) external returns (bytes32) envfree; - function get(bytes32) external returns (bytes32) envfree; - - // FV - function _positionOf(bytes32) external returns (uint256) envfree; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helpers │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -definition sanity() returns bool = - length() < max_uint256; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: the value mapping is empty for keys that are not in the EnumerableMap. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant noValueIfNotContained(bytes32 key) - !contains(key) => tryGet_value(key) == to_bytes32(0) - { - preserved set(bytes32 otherKey, bytes32 someValue) { - require sanity(); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: All indexed keys are contained │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant indexedContained(uint256 index) - index < length() => contains(key_at(index)) - { - preserved { - requireInvariant consistencyIndex(index); - requireInvariant consistencyIndex(require_uint256(length() - 1)); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: A value can only be stored at a single location │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant atUniqueness(uint256 index1, uint256 index2) - index1 == index2 <=> key_at(index1) == key_at(index2) - { - preserved remove(bytes32 key) { - requireInvariant atUniqueness(index1, require_uint256(length() - 1)); - requireInvariant atUniqueness(index2, require_uint256(length() - 1)); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: index <> value relationship is consistent │ -│ │ -│ Note that the two consistencyXxx invariants, put together, prove that at_ and _positionOf are inverse of one │ -│ another. This proves that we have a bijection between indices (the enumerability part) and keys (the entries that │ -│ are set and removed from the EnumerableMap). │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant consistencyIndex(uint256 index) - index < length() => to_mathint(_positionOf(key_at(index))) == index + 1 - { - preserved remove(bytes32 key) { - requireInvariant consistencyIndex(require_uint256(length() - 1)); - } - } - -invariant consistencyKey(bytes32 key) - contains(key) => ( - _positionOf(key) > 0 && - _positionOf(key) <= length() && - key_at(require_uint256(_positionOf(key) - 1)) == key - ) - { - preserved remove(bytes32 otherKey) { - requireInvariant consistencyKey(otherKey); - requireInvariant atUniqueness( - require_uint256(_positionOf(key) - 1), - require_uint256(_positionOf(otherKey) - 1) - ); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: state only changes by setting or removing elements │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule stateChange(env e, bytes32 key) { - require sanity(); - requireInvariant consistencyKey(key); - - uint256 lengthBefore = length(); - bool containsBefore = contains(key); - bytes32 valueBefore = tryGet_value(key); - - method f; - calldataarg args; - f(e, args); - - uint256 lengthAfter = length(); - bool containsAfter = contains(key); - bytes32 valueAfter = tryGet_value(key); - - assert lengthBefore != lengthAfter => ( - (f.selector == sig:set(bytes32,bytes32).selector && to_mathint(lengthAfter) == lengthBefore + 1) || - (f.selector == sig:remove(bytes32).selector && to_mathint(lengthAfter) == lengthBefore - 1) - ); - - assert containsBefore != containsAfter => ( - (f.selector == sig:set(bytes32,bytes32).selector && containsAfter) || - (f.selector == sig:remove(bytes32).selector && !containsAfter) - ); - - assert valueBefore != valueAfter => ( - (f.selector == sig:set(bytes32,bytes32).selector && containsAfter) || - (f.selector == sig:remove(bytes32).selector && !containsAfter && valueAfter == to_bytes32(0)) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: check liveness of view functions. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule liveness_1(bytes32 key) { - requireInvariant consistencyKey(key); - - // contains never revert - bool contains = contains@withrevert(key); - assert !lastReverted; - - // tryGet never reverts (key) - tryGet_contains@withrevert(key); - assert !lastReverted; - - // tryGet never reverts (value) - tryGet_value@withrevert(key); - assert !lastReverted; - - // get reverts iff the key is not in the map - get@withrevert(key); - assert !lastReverted <=> contains; -} - -rule liveness_2(uint256 index) { - requireInvariant consistencyIndex(index); - - // length never revert - uint256 length = length@withrevert(); - assert !lastReverted; - - // key_at reverts iff the index is out of bound - key_at@withrevert(index); - assert !lastReverted <=> index < length; - - // value_at reverts iff the index is out of bound - value_at@withrevert(index); - assert !lastReverted <=> index < length; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: get and tryGet return the expected values. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule getAndTryGet(bytes32 key) { - requireInvariant noValueIfNotContained(key); - - bool contained = contains(key); - bool tryContained = tryGet_contains(key); - bytes32 tryValue = tryGet_value(key); - bytes32 value = get@withrevert(key); // revert is not contained - - assert contained == tryContained; - assert contained => tryValue == value; - assert !contained => tryValue == to_bytes32(0); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: set key-value in EnumerableMap │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule set(bytes32 key, bytes32 value, bytes32 otherKey) { - require sanity(); - - uint256 lengthBefore = length(); - bool containsBefore = contains(key); - bool containsOtherBefore = contains(otherKey); - bytes32 otherValueBefore = tryGet_value(otherKey); - - bool added = set@withrevert(key, value); - bool success = !lastReverted; - - assert success && contains(key) && get(key) == value, - "liveness & immediate effect"; - - assert added <=> !containsBefore, - "return value: added iff not contained"; - - assert to_mathint(length()) == lengthBefore + to_mathint(added ? 1 : 0), - "effect: length increases iff added"; - - assert added => (key_at(lengthBefore) == key && value_at(lengthBefore) == value), - "effect: add at the end"; - - assert containsOtherBefore != contains(otherKey) => (added && key == otherKey), - "side effect: other keys are not affected"; - - assert otherValueBefore != tryGet_value(otherKey) => key == otherKey, - "side effect: values attached to other keys are not affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: remove key from EnumerableMap │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule remove(bytes32 key, bytes32 otherKey) { - requireInvariant consistencyKey(key); - requireInvariant consistencyKey(otherKey); - - uint256 lengthBefore = length(); - bool containsBefore = contains(key); - bool containsOtherBefore = contains(otherKey); - bytes32 otherValueBefore = tryGet_value(otherKey); - - bool removed = remove@withrevert(key); - bool success = !lastReverted; - - assert success && !contains(key), - "liveness & immediate effect"; - - assert removed <=> containsBefore, - "return value: removed iff contained"; - - assert to_mathint(length()) == lengthBefore - to_mathint(removed ? 1 : 0), - "effect: length decreases iff removed"; - - assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey), - "side effect: other keys are not affected"; - - assert otherValueBefore != tryGet_value(otherKey) => key == otherKey, - "side effect: values attached to other keys are not affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: when adding a new key, the other keys remain in set, at the same index. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule setEnumerability(bytes32 key, bytes32 value, uint256 index) { - require sanity(); - - bytes32 atKeyBefore = key_at(index); - bytes32 atValueBefore = value_at(index); - - set(key, value); - - bytes32 atKeyAfter = key_at@withrevert(index); - assert !lastReverted; - - bytes32 atValueAfter = value_at@withrevert(index); - assert !lastReverted; - - assert atKeyAfter == atKeyBefore; - assert atValueAfter != atValueBefore => ( - key == atKeyBefore && - value == atValueAfter - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule removeEnumerability(bytes32 key, uint256 index) { - uint256 last = require_uint256(length() - 1); - - requireInvariant consistencyKey(key); - requireInvariant consistencyIndex(index); - requireInvariant consistencyIndex(last); - - bytes32 atKeyBefore = key_at(index); - bytes32 atValueBefore = value_at(index); - bytes32 lastKeyBefore = key_at(last); - bytes32 lastValueBefore = value_at(last); - - remove(key); - - // can't read last value & keys (length decreased) - bytes32 atKeyAfter = key_at@withrevert(index); - assert lastReverted <=> index == last; - - bytes32 atValueAfter = value_at@withrevert(index); - assert lastReverted <=> index == last; - - // One value that is allowed to change is if previous value was removed, - // in that case the last value before took its place. - assert ( - index != last && - atKeyBefore != atKeyAfter - ) => ( - atKeyBefore == key && - atKeyAfter == lastKeyBefore - ); - - assert ( - index != last && - atValueBefore != atValueAfter - ) => ( - atValueAfter == lastValueBefore - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs/EnumerableSet.spec b/lib_openzeppelin_contracts/certora/specs/EnumerableSet.spec deleted file mode 100644 index 94d0a91..0000000 --- a/lib_openzeppelin_contracts/certora/specs/EnumerableSet.spec +++ /dev/null @@ -1,246 +0,0 @@ -import "helpers/helpers.spec"; - -methods { - // library - function add(bytes32) external returns (bool) envfree; - function remove(bytes32) external returns (bool) envfree; - function contains(bytes32) external returns (bool) envfree; - function length() external returns (uint256) envfree; - function at_(uint256) external returns (bytes32) envfree; - - // FV - function _positionOf(bytes32) external returns (uint256) envfree; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helpers │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -definition sanity() returns bool = - length() < max_uint256; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: All indexed keys are contained │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant indexedContained(uint256 index) - index < length() => contains(at_(index)) - { - preserved { - requireInvariant consistencyIndex(index); - requireInvariant consistencyIndex(require_uint256(length() - 1)); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: A value can only be stored at a single location │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant atUniqueness(uint256 index1, uint256 index2) - index1 == index2 <=> at_(index1) == at_(index2) - { - preserved remove(bytes32 key) { - requireInvariant atUniqueness(index1, require_uint256(length() - 1)); - requireInvariant atUniqueness(index2, require_uint256(length() - 1)); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: index <> key relationship is consistent │ -│ │ -│ Note that the two consistencyXxx invariants, put together, prove that at_ and _positionOf are inverse of one │ -│ another. This proves that we have a bijection between indices (the enumerability part) and keys (the entries that │ -│ are added and removed from the EnumerableSet). │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant consistencyIndex(uint256 index) - index < length() => _positionOf(at_(index)) == require_uint256(index + 1) - { - preserved remove(bytes32 key) { - requireInvariant consistencyIndex(require_uint256(length() - 1)); - } - } - -invariant consistencyKey(bytes32 key) - contains(key) => ( - _positionOf(key) > 0 && - _positionOf(key) <= length() && - at_(require_uint256(_positionOf(key) - 1)) == key - ) - { - preserved remove(bytes32 otherKey) { - requireInvariant consistencyKey(otherKey); - requireInvariant atUniqueness( - require_uint256(_positionOf(key) - 1), - require_uint256(_positionOf(otherKey) - 1) - ); - } - } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: state only changes by adding or removing elements │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule stateChange(env e, bytes32 key) { - require sanity(); - requireInvariant consistencyKey(key); - - uint256 lengthBefore = length(); - bool containsBefore = contains(key); - - method f; - calldataarg args; - f(e, args); - - uint256 lengthAfter = length(); - bool containsAfter = contains(key); - - assert lengthBefore != lengthAfter => ( - (f.selector == sig:add(bytes32).selector && lengthAfter == require_uint256(lengthBefore + 1)) || - (f.selector == sig:remove(bytes32).selector && lengthAfter == require_uint256(lengthBefore - 1)) - ); - - assert containsBefore != containsAfter => ( - (f.selector == sig:add(bytes32).selector && containsAfter) || - (f.selector == sig:remove(bytes32).selector && containsBefore) - ); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: check liveness of view functions. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule liveness_1(bytes32 key) { - requireInvariant consistencyKey(key); - - // contains never revert - contains@withrevert(key); - assert !lastReverted; -} - -rule liveness_2(uint256 index) { - requireInvariant consistencyIndex(index); - - // length never revert - uint256 length = length@withrevert(); - assert !lastReverted; - - // at reverts iff the index is out of bound - at_@withrevert(index); - assert !lastReverted <=> index < length; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: add key to EnumerableSet if not already contained │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule add(bytes32 key, bytes32 otherKey) { - require sanity(); - - uint256 lengthBefore = length(); - bool containsBefore = contains(key); - bool containsOtherBefore = contains(otherKey); - - bool added = add@withrevert(key); - bool success = !lastReverted; - - assert success && contains(key), - "liveness & immediate effect"; - - assert added <=> !containsBefore, - "return value: added iff not contained"; - - assert length() == require_uint256(lengthBefore + to_mathint(added ? 1 : 0)), - "effect: length increases iff added"; - - assert added => at_(lengthBefore) == key, - "effect: add at the end"; - - assert containsOtherBefore != contains(otherKey) => (added && key == otherKey), - "side effect: other keys are not affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: remove key from EnumerableSet if already contained │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule remove(bytes32 key, bytes32 otherKey) { - requireInvariant consistencyKey(key); - requireInvariant consistencyKey(otherKey); - - uint256 lengthBefore = length(); - bool containsBefore = contains(key); - bool containsOtherBefore = contains(otherKey); - - bool removed = remove@withrevert(key); - bool success = !lastReverted; - - assert success && !contains(key), - "liveness & immediate effect"; - - assert removed <=> containsBefore, - "return value: removed iff contained"; - - assert length() == require_uint256(lengthBefore - to_mathint(removed ? 1 : 0)), - "effect: length decreases iff removed"; - - assert containsOtherBefore != contains(otherKey) => (removed && key == otherKey), - "side effect: other keys are not affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: when adding a new key, the other keys remain in set, at the same index. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule addEnumerability(bytes32 key, uint256 index) { - require sanity(); - - bytes32 atBefore = at_(index); - add(key); - bytes32 atAfter = at_@withrevert(index); - bool atAfterSuccess = !lastReverted; - - assert atAfterSuccess; - assert atBefore == atAfter; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: when removing a existing key, the other keys remain in set, at the same index (except for the last one). │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule removeEnumerability(bytes32 key, uint256 index) { - uint256 last = require_uint256(length() - 1); - - requireInvariant consistencyKey(key); - requireInvariant consistencyIndex(index); - requireInvariant consistencyIndex(last); - - bytes32 atBefore = at_(index); - bytes32 lastBefore = at_(last); - - remove(key); - - // can't read last value (length decreased) - bytes32 atAfter = at_@withrevert(index); - assert lastReverted <=> index == last; - - // One value that is allowed to change is if previous value was removed, - // in that case the last value before took its place. - assert ( - index != last && - atBefore != atAfter - ) => ( - atBefore == key && - atAfter == lastBefore - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs/Initializable.spec b/lib_openzeppelin_contracts/certora/specs/Initializable.spec deleted file mode 100644 index 07c2930..0000000 --- a/lib_openzeppelin_contracts/certora/specs/Initializable.spec +++ /dev/null @@ -1,165 +0,0 @@ -import "helpers/helpers.spec"; - -methods { - // initialize, reinitialize, disable - function initialize() external envfree; - function reinitialize(uint64) external envfree; - function disable() external envfree; - - function nested_init_init() external envfree; - function nested_init_reinit(uint64) external envfree; - function nested_reinit_init(uint64) external envfree; - function nested_reinit_reinit(uint64,uint64) external envfree; - - // view - function version() external returns uint64 envfree; - function initializing() external returns bool envfree; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Definitions │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -definition isUninitialized() returns bool = version() == 0; -definition isInitialized() returns bool = version() > 0; -definition isDisabled() returns bool = version() == max_uint64; - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: A contract must only ever be in an initializing state while in the middle of a transaction execution. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant notInitializing() - !initializing(); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: The version cannot decrease & disable state is irrevocable. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule increasingVersion(env e) { - uint64 versionBefore = version(); - bool disabledBefore = isDisabled(); - - method f; calldataarg args; - f(e, args); - - assert versionBefore <= version(), "_initialized must only increase"; - assert disabledBefore => isDisabled(), "a disabled initializer must stay disabled"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: Cannot initialize a contract that is already initialized. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule cannotInitializeTwice() { - require isInitialized(); - - initialize@withrevert(); - - assert lastReverted, "contract must only be initialized once"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: Cannot initialize once disabled. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule cannotInitializeOnceDisabled() { - require isDisabled(); - - initialize@withrevert(); - - assert lastReverted, "contract is disabled"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: Cannot reinitialize once disabled. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule cannotReinitializeOnceDisabled() { - require isDisabled(); - - uint64 n; - reinitialize@withrevert(n); - - assert lastReverted, "contract is disabled"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: Cannot nest initializers (after construction). │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule cannotNestInitializers_init_init() { - nested_init_init@withrevert(); - assert lastReverted, "nested initializers"; -} - -rule cannotNestInitializers_init_reinit(uint64 m) { - nested_init_reinit@withrevert(m); - assert lastReverted, "nested initializers"; -} - -rule cannotNestInitializers_reinit_init(uint64 n) { - nested_reinit_init@withrevert(n); - assert lastReverted, "nested initializers"; -} - -rule cannotNestInitializers_reinit_reinit(uint64 n, uint64 m) { - nested_reinit_reinit@withrevert(n, m); - assert lastReverted, "nested initializers"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: Initialize correctly sets the version. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule initializeEffects() { - requireInvariant notInitializing(); - - bool isUninitializedBefore = isUninitialized(); - - initialize@withrevert(); - bool success = !lastReverted; - - assert success <=> isUninitializedBefore, "can only initialize uninitialized contracts"; - assert success => version() == 1, "initialize must set version() to 1"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: Reinitialize correctly sets the version. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule reinitializeEffects() { - requireInvariant notInitializing(); - - uint64 versionBefore = version(); - - uint64 n; - reinitialize@withrevert(n); - bool success = !lastReverted; - - assert success <=> versionBefore < n, "can only reinitialize to a latter versions"; - assert success => version() == n, "reinitialize must set version() to n"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: Can disable. │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule disableEffect() { - requireInvariant notInitializing(); - - disable@withrevert(); - bool success = !lastReverted; - - assert success, "call to _disableInitializers failed"; - assert isDisabled(), "disable state not set"; -} diff --git a/lib_openzeppelin_contracts/certora/specs/Nonces.spec b/lib_openzeppelin_contracts/certora/specs/Nonces.spec deleted file mode 100644 index 4647c5c..0000000 --- a/lib_openzeppelin_contracts/certora/specs/Nonces.spec +++ /dev/null @@ -1,92 +0,0 @@ -import "helpers/helpers.spec"; - -methods { - function nonces(address) external returns (uint256) envfree; - function useNonce(address) external returns (uint256) envfree; - function useCheckedNonce(address,uint256) external envfree; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helpers │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -function nonceSanity(address account) returns bool { - return nonces(account) < max_uint256; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: useNonce uses nonce │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule useNonce(address account) { - require nonceSanity(account); - - address other; - - mathint nonceBefore = nonces(account); - mathint otherNonceBefore = nonces(other); - - mathint nonceUsed = useNonce@withrevert(account); - bool success = !lastReverted; - - mathint nonceAfter = nonces(account); - mathint otherNonceAfter = nonces(other); - - // liveness - assert success, "doesn't revert"; - - // effect - assert nonceAfter == nonceBefore + 1 && nonceBefore == nonceUsed, "nonce is used"; - - // no side effect - assert otherNonceBefore != otherNonceAfter => other == account, "no other nonce is used"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: useCheckedNonce uses only the current nonce │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule useCheckedNonce(address account, uint256 currentNonce) { - require nonceSanity(account); - - address other; - - mathint nonceBefore = nonces(account); - mathint otherNonceBefore = nonces(other); - - useCheckedNonce@withrevert(account, currentNonce); - bool success = !lastReverted; - - mathint nonceAfter = nonces(account); - mathint otherNonceAfter = nonces(other); - - // liveness - assert success <=> to_mathint(currentNonce) == nonceBefore, "works iff current nonce is correct"; - - // effect - assert success => nonceAfter == nonceBefore + 1, "nonce is used"; - - // no side effect - assert otherNonceBefore != otherNonceAfter => other == account, "no other nonce is used"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: nonce only increments │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule nonceOnlyIncrements(address account) { - require nonceSanity(account); - - mathint nonceBefore = nonces(account); - - env e; method f; calldataarg args; - f(e, args); - - mathint nonceAfter = nonces(account); - - assert nonceAfter == nonceBefore || nonceAfter == nonceBefore + 1, "nonce only increments"; -} diff --git a/lib_openzeppelin_contracts/certora/specs/Ownable.spec b/lib_openzeppelin_contracts/certora/specs/Ownable.spec deleted file mode 100644 index 0d50813..0000000 --- a/lib_openzeppelin_contracts/certora/specs/Ownable.spec +++ /dev/null @@ -1,77 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IOwnable.spec"; - -methods { - function restricted() external; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: transferOwnership changes ownership │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule transferOwnership(env e) { - require nonpayable(e); - - address newOwner; - address current = owner(); - - transferOwnership@withrevert(e, newOwner); - bool success = !lastReverted; - - assert success <=> (e.msg.sender == current && newOwner != 0), "unauthorized caller or invalid arg"; - assert success => owner() == newOwner, "current owner changed"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: renounceOwnership removes the owner │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule renounceOwnership(env e) { - require nonpayable(e); - - address current = owner(); - - renounceOwnership@withrevert(e); - bool success = !lastReverted; - - assert success <=> e.msg.sender == current, "unauthorized caller"; - assert success => owner() == 0, "owner not cleared"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Access control: only current owner can call restricted functions │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyCurrentOwnerCanCallOnlyOwner(env e) { - require nonpayable(e); - - address current = owner(); - - calldataarg args; - restricted@withrevert(e, args); - - assert !lastReverted <=> e.msg.sender == current, "access control failed"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: ownership can only change in specific ways │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyOwnerOrPendingOwnerCanChangeOwnership(env e) { - address oldCurrent = owner(); - - method f; calldataarg args; - f(e, args); - - address newCurrent = owner(); - - // If owner changes, must be either transferOwnership or renounceOwnership - assert oldCurrent != newCurrent => ( - (e.msg.sender == oldCurrent && newCurrent != 0 && f.selector == sig:transferOwnership(address).selector) || - (e.msg.sender == oldCurrent && newCurrent == 0 && f.selector == sig:renounceOwnership().selector) - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs/Ownable2Step.spec b/lib_openzeppelin_contracts/certora/specs/Ownable2Step.spec deleted file mode 100644 index d13c6d3..0000000 --- a/lib_openzeppelin_contracts/certora/specs/Ownable2Step.spec +++ /dev/null @@ -1,108 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IOwnable2Step.spec"; - -methods { - function restricted() external; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: transferOwnership sets the pending owner │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule transferOwnership(env e) { - require nonpayable(e); - - address newOwner; - address current = owner(); - - transferOwnership@withrevert(e, newOwner); - bool success = !lastReverted; - - assert success <=> e.msg.sender == current, "unauthorized caller"; - assert success => pendingOwner() == newOwner, "pending owner not set"; - assert success => owner() == current, "current owner changed"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: renounceOwnership removes the owner and the pendingOwner │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule renounceOwnership(env e) { - require nonpayable(e); - - address current = owner(); - - renounceOwnership@withrevert(e); - bool success = !lastReverted; - - assert success <=> e.msg.sender == current, "unauthorized caller"; - assert success => pendingOwner() == 0, "pending owner not cleared"; - assert success => owner() == 0, "owner not cleared"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: acceptOwnership changes owner and reset pending owner │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule acceptOwnership(env e) { - - require nonpayable(e); - - address current = owner(); - address pending = pendingOwner(); - - acceptOwnership@withrevert(e); - bool success = !lastReverted; - - assert success <=> e.msg.sender == pending, "unauthorized caller"; - assert success => pendingOwner() == 0, "pending owner not cleared"; - assert success => owner() == pending, "owner not transferred"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Access control: only current owner can call restricted functions │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule onlyCurrentOwnerCanCallOnlyOwner(env e) { - require nonpayable(e); - - address current = owner(); - - calldataarg args; - restricted@withrevert(e, args); - - assert !lastReverted <=> e.msg.sender == current, "access control failed"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: ownership and pending ownership can only change in specific ways │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule ownerOrPendingOwnerChange(env e, method f) { - address oldCurrent = owner(); - address oldPending = pendingOwner(); - - calldataarg args; - f(e, args); - - address newCurrent = owner(); - address newPending = pendingOwner(); - - // If owner changes, must be either acceptOwnership or renounceOwnership - assert oldCurrent != newCurrent => ( - (e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == sig:acceptOwnership().selector) || - (e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == sig:renounceOwnership().selector) - ); - - // If pending changes, must be either acceptance or reset - assert oldPending != newPending => ( - (e.msg.sender == oldCurrent && newCurrent == oldCurrent && f.selector == sig:transferOwnership(address).selector) || - (e.msg.sender == oldPending && newCurrent == oldPending && newPending == 0 && f.selector == sig:acceptOwnership().selector) || - (e.msg.sender == oldCurrent && newCurrent == 0 && newPending == 0 && f.selector == sig:renounceOwnership().selector) - ); -} diff --git a/lib_openzeppelin_contracts/certora/specs/Pausable.spec b/lib_openzeppelin_contracts/certora/specs/Pausable.spec deleted file mode 100644 index a7aff9c..0000000 --- a/lib_openzeppelin_contracts/certora/specs/Pausable.spec +++ /dev/null @@ -1,96 +0,0 @@ -import "helpers/helpers.spec"; - -methods { - function paused() external returns (bool) envfree; - function pause() external; - function unpause() external; - function onlyWhenPaused() external; - function onlyWhenNotPaused() external; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: _pause pauses the contract │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule pause(env e) { - require nonpayable(e); - - bool pausedBefore = paused(); - - pause@withrevert(e); - bool success = !lastReverted; - - bool pausedAfter = paused(); - - // liveness - assert success <=> !pausedBefore, "works if and only if the contract was not paused before"; - - // effect - assert success => pausedAfter, "contract must be paused after a successful call"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: _unpause unpauses the contract │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule unpause(env e) { - require nonpayable(e); - - bool pausedBefore = paused(); - - unpause@withrevert(e); - bool success = !lastReverted; - - bool pausedAfter = paused(); - - // liveness - assert success <=> pausedBefore, "works if and only if the contract was paused before"; - - // effect - assert success => !pausedAfter, "contract must be unpaused after a successful call"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: whenPaused modifier can only be called if the contract is paused │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule whenPaused(env e) { - require nonpayable(e); - - onlyWhenPaused@withrevert(e); - assert !lastReverted <=> paused(), "works if and only if the contract is paused"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Function correctness: whenNotPaused modifier can only be called if the contract is not paused │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule whenNotPaused(env e) { - require nonpayable(e); - - onlyWhenNotPaused@withrevert(e); - assert !lastReverted <=> !paused(), "works if and only if the contract is not paused"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rules: only _pause and _unpause can change paused status │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule noPauseChange(env e) { - method f; - calldataarg args; - - bool pausedBefore = paused(); - f(e, args); - bool pausedAfter = paused(); - - assert pausedBefore != pausedAfter => ( - (!pausedAfter && f.selector == sig:unpause().selector) || - (pausedAfter && f.selector == sig:pause().selector) - ), "contract's paused status can only be changed by _pause() or _unpause()"; -} diff --git a/lib_openzeppelin_contracts/certora/specs/TimelockController.spec b/lib_openzeppelin_contracts/certora/specs/TimelockController.spec deleted file mode 100644 index 5123768..0000000 --- a/lib_openzeppelin_contracts/certora/specs/TimelockController.spec +++ /dev/null @@ -1,274 +0,0 @@ -import "helpers/helpers.spec"; -import "methods/IAccessControl.spec"; - -methods { - function PROPOSER_ROLE() external returns (bytes32) envfree; - function EXECUTOR_ROLE() external returns (bytes32) envfree; - function CANCELLER_ROLE() external returns (bytes32) envfree; - function isOperation(bytes32) external returns (bool); - function isOperationPending(bytes32) external returns (bool); - function isOperationReady(bytes32) external returns (bool); - function isOperationDone(bytes32) external returns (bool); - function getTimestamp(bytes32) external returns (uint256) envfree; - function getMinDelay() external returns (uint256) envfree; - - function hashOperation(address, uint256, bytes, bytes32, bytes32) external returns(bytes32) envfree; - function hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) external returns(bytes32) envfree; - - function schedule(address, uint256, bytes, bytes32, bytes32, uint256) external; - function scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256) external; - function execute(address, uint256, bytes, bytes32, bytes32) external; - function executeBatch(address[], uint256[], bytes[], bytes32, bytes32) external; - function cancel(bytes32) external; - - function updateDelay(uint256) external; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Helpers │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -// Uniformly handle scheduling of batched and non-batched operations. -function helperScheduleWithRevert(env e, method f, bytes32 id, uint256 delay) { - if (f.selector == sig:schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector) { - address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt; - require hashOperation(target, value, data, predecessor, salt) == id; // Correlation - schedule@withrevert(e, target, value, data, predecessor, salt, delay); - } else if (f.selector == sig:scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector) { - address[] targets; uint256[] values; bytes[] payloads; bytes32 predecessor; bytes32 salt; - require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation - scheduleBatch@withrevert(e, targets, values, payloads, predecessor, salt, delay); - } else { - calldataarg args; - f@withrevert(e, args); - } -} - -// Uniformly handle execution of batched and non-batched operations. -function helperExecuteWithRevert(env e, method f, bytes32 id, bytes32 predecessor) { - if (f.selector == sig:execute(address, uint256, bytes, bytes32, bytes32).selector) { - address target; uint256 value; bytes data; bytes32 salt; - require hashOperation(target, value, data, predecessor, salt) == id; // Correlation - execute@withrevert(e, target, value, data, predecessor, salt); - } else if (f.selector == sig:executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector) { - address[] targets; uint256[] values; bytes[] payloads; bytes32 salt; - require hashOperationBatch(targets, values, payloads, predecessor, salt) == id; // Correlation - executeBatch@withrevert(e, targets, values, payloads, predecessor, salt); - } else { - calldataarg args; - f@withrevert(e, args); - } -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Definitions │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -definition DONE_TIMESTAMP() returns uint256 = 1; -definition UNSET() returns uint8 = 0x1; -definition PENDING() returns uint8 = 0x2; -definition DONE() returns uint8 = 0x4; - -definition isUnset(env e, bytes32 id) returns bool = !isOperation(e, id); -definition isPending(env e, bytes32 id) returns bool = isOperationPending(e, id); -definition isDone(env e, bytes32 id) returns bool = isOperationDone(e, id); -definition state(env e, bytes32 id) returns uint8 = (isUnset(e, id) ? UNSET() : 0) | (isPending(e, id) ? PENDING() : 0) | (isDone(e, id) ? DONE() : 0); - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariants: consistency of accessors │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant isOperationCheck(env e, bytes32 id) - isOperation(e, id) <=> getTimestamp(id) > 0 - filtered { f -> !f.isView } - -invariant isOperationPendingCheck(env e, bytes32 id) - isOperationPending(e, id) <=> getTimestamp(id) > DONE_TIMESTAMP() - filtered { f -> !f.isView } - -invariant isOperationDoneCheck(env e, bytes32 id) - isOperationDone(e, id) <=> getTimestamp(id) == DONE_TIMESTAMP() - filtered { f -> !f.isView } - -invariant isOperationReadyCheck(env e, bytes32 id) - isOperationReady(e, id) <=> (isOperationPending(e, id) && getTimestamp(id) <= e.block.timestamp) - filtered { f -> !f.isView } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Invariant: a proposal id is either unset, pending or done │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -invariant stateConsistency(bytes32 id, env e) - // Check states are mutually exclusive - (isUnset(e, id) <=> (!isPending(e, id) && !isDone(e, id) )) && - (isPending(e, id) <=> (!isUnset(e, id) && !isDone(e, id) )) && - (isDone(e, id) <=> (!isUnset(e, id) && !isPending(e, id))) && - // Check that the state helper behaves as expected: - (isUnset(e, id) <=> state(e, id) == UNSET() ) && - (isPending(e, id) <=> state(e, id) == PENDING() ) && - (isDone(e, id) <=> state(e, id) == DONE() ) && - // Check substate - isOperationReady(e, id) => isPending(e, id) - filtered { f -> !f.isView } - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: state transition rules │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule stateTransition(bytes32 id, env e, method f, calldataarg args) { - require e.block.timestamp > 1; // Sanity - - uint8 stateBefore = state(e, id); - f(e, args); - uint8 stateAfter = state(e, id); - - // Cannot jump from UNSET to DONE - assert stateBefore == UNSET() => stateAfter != DONE(); - - // UNSET → PENDING: schedule or scheduleBatch - assert stateBefore == UNSET() && stateAfter == PENDING() => ( - f.selector == sig:schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector || - f.selector == sig:scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector - ); - - // PENDING → UNSET: cancel - assert stateBefore == PENDING() && stateAfter == UNSET() => ( - f.selector == sig:cancel(bytes32).selector - ); - - // PENDING → DONE: execute or executeBatch - assert stateBefore == PENDING() && stateAfter == DONE() => ( - f.selector == sig:execute(address, uint256, bytes, bytes32, bytes32).selector || - f.selector == sig:executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector - ); - - // DONE is final - assert stateBefore == DONE() => stateAfter == DONE(); -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: minimum delay can only be updated through a timelock execution │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule minDelayOnlyChange(env e) { - uint256 delayBefore = getMinDelay(); - - method f; calldataarg args; - f(e, args); - - assert delayBefore != getMinDelay() => (e.msg.sender == currentContract && f.selector == sig:updateDelay(uint256).selector), "Unauthorized delay update"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: schedule liveness and effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule schedule(env e, method f, bytes32 id, uint256 delay) filtered { f -> - f.selector == sig:schedule(address, uint256, bytes, bytes32, bytes32, uint256).selector || - f.selector == sig:scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256).selector -} { - require nonpayable(e); - - // Basic timestamp assumptions - require e.block.timestamp > 1; - require e.block.timestamp + delay < max_uint256; - require e.block.timestamp + getMinDelay() < max_uint256; - - bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId); - - uint8 stateBefore = state(e, id); - bool isDelaySufficient = delay >= getMinDelay(); - bool isProposerBefore = hasRole(PROPOSER_ROLE(), e.msg.sender); - - helperScheduleWithRevert(e, f, id, delay); - bool success = !lastReverted; - - // liveness - assert success <=> ( - stateBefore == UNSET() && - isDelaySufficient && - isProposerBefore - ); - - // effect - assert success => state(e, id) == PENDING(), "State transition violation"; - assert success => getTimestamp(id) == require_uint256(e.block.timestamp + delay), "Proposal timestamp not correctly set"; - - // no side effect - assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: execute liveness and effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule execute(env e, method f, bytes32 id, bytes32 predecessor) filtered { f -> - f.selector == sig:execute(address, uint256, bytes, bytes32, bytes32).selector || - f.selector == sig:executeBatch(address[], uint256[], bytes[], bytes32, bytes32).selector -} { - bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId); - - uint8 stateBefore = state(e, id); - bool isOperationReadyBefore = isOperationReady(e, id); - bool isExecutorOrOpen = hasRole(EXECUTOR_ROLE(), e.msg.sender) || hasRole(EXECUTOR_ROLE(), 0); - bool predecessorDependency = predecessor == to_bytes32(0) || isDone(e, predecessor); - - helperExecuteWithRevert(e, f, id, predecessor); - bool success = !lastReverted; - - // The underlying transaction can revert, and that would cause the execution to revert. We can check that all non - // reverting calls meet the requirements in terms of proposal readiness, access control and predecessor dependency. - // We can't however guarantee that these requirements being meet ensure liveness of the proposal, because the - // proposal can revert for reasons beyond our control. - - // liveness, should be `<=>` but can only check `=>` (see comment above) - assert success => ( - stateBefore == PENDING() && - isOperationReadyBefore && - predecessorDependency && - isExecutorOrOpen - ); - - // effect - assert success => state(e, id) == DONE(), "State transition violation"; - - // no side effect - assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected"; -} - -/* -┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ -│ Rule: cancel liveness and effects │ -└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ -*/ -rule cancel(env e, bytes32 id) { - require nonpayable(e); - - bytes32 otherId; uint256 otherTimestamp = getTimestamp(otherId); - - uint8 stateBefore = state(e, id); - bool isCanceller = hasRole(CANCELLER_ROLE(), e.msg.sender); - - cancel@withrevert(e, id); - bool success = !lastReverted; - - // liveness - assert success <=> ( - stateBefore == PENDING() && - isCanceller - ); - - // effect - assert success => state(e, id) == UNSET(), "State transition violation"; - - // no side effect - assert otherTimestamp != getTimestamp(otherId) => id == otherId, "Other proposal affected"; -} diff --git a/lib_openzeppelin_contracts/certora/specs/helpers/helpers.spec b/lib_openzeppelin_contracts/certora/specs/helpers/helpers.spec deleted file mode 100644 index 7125ce2..0000000 --- a/lib_openzeppelin_contracts/certora/specs/helpers/helpers.spec +++ /dev/null @@ -1,12 +0,0 @@ -// environment -definition nonpayable(env e) returns bool = e.msg.value == 0; -definition nonzerosender(env e) returns bool = e.msg.sender != 0; -definition sanity(env e) returns bool = clock(e) > 0 && clock(e) <= max_uint48; - -// math -definition min(mathint a, mathint b) returns mathint = a < b ? a : b; -definition max(mathint a, mathint b) returns mathint = a > b ? a : b; - -// time -definition clock(env e) returns mathint = to_mathint(e.block.timestamp); -definition isSetAndPast(env e, uint48 timepoint) returns bool = timepoint != 0 && to_mathint(timepoint) <= clock(e); diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IAccessControl.spec b/lib_openzeppelin_contracts/certora/specs/methods/IAccessControl.spec deleted file mode 100644 index 5c395b0..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IAccessControl.spec +++ /dev/null @@ -1,8 +0,0 @@ -methods { - function DEFAULT_ADMIN_ROLE() external returns (bytes32) envfree; - function hasRole(bytes32, address) external returns(bool) envfree; - function getRoleAdmin(bytes32) external returns(bytes32) envfree; - function grantRole(bytes32, address) external; - function revokeRole(bytes32, address) external; - function renounceRole(bytes32, address) external; -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IAccessControlDefaultAdminRules.spec b/lib_openzeppelin_contracts/certora/specs/methods/IAccessControlDefaultAdminRules.spec deleted file mode 100644 index d02db18..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IAccessControlDefaultAdminRules.spec +++ /dev/null @@ -1,36 +0,0 @@ -import "./IERC5313.spec"; - -methods { - // === View == - - // Default Admin - function defaultAdmin() external returns(address) envfree; - function pendingDefaultAdmin() external returns(address, uint48) envfree; - - // Default Admin Delay - function defaultAdminDelay() external returns(uint48); - function pendingDefaultAdminDelay() external returns(uint48, uint48); - function defaultAdminDelayIncreaseWait() external returns(uint48) envfree; - - // === Mutations == - - // Default Admin - function beginDefaultAdminTransfer(address) external; - function cancelDefaultAdminTransfer() external; - function acceptDefaultAdminTransfer() external; - - // Default Admin Delay - function changeDefaultAdminDelay(uint48) external; - function rollbackDefaultAdminDelay() external; - - // == FV == - - // Default Admin - function pendingDefaultAdmin_() external returns (address) envfree; - function pendingDefaultAdminSchedule_() external returns (uint48) envfree; - - // Default Admin Delay - function pendingDelay_() external returns (uint48); - function pendingDelaySchedule_() external returns (uint48); - function delayChangeWait_(uint48) external returns (uint48); -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IAccessManaged.spec b/lib_openzeppelin_contracts/certora/specs/methods/IAccessManaged.spec deleted file mode 100644 index 886d917..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IAccessManaged.spec +++ /dev/null @@ -1,5 +0,0 @@ -methods { - function authority() external returns (address) envfree; - function isConsumingScheduledOp() external returns (bytes4) envfree; - function setAuthority(address) external; -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IAccessManager.spec b/lib_openzeppelin_contracts/certora/specs/methods/IAccessManager.spec deleted file mode 100644 index 5d305f7..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IAccessManager.spec +++ /dev/null @@ -1,33 +0,0 @@ -methods { - function ADMIN_ROLE() external returns (uint64) envfree; - function PUBLIC_ROLE() external returns (uint64) envfree; - function canCall(address,address,bytes4) external returns (bool,uint32); - function expiration() external returns (uint32) envfree; - function minSetback() external returns (uint32) envfree; - function isTargetClosed(address) external returns (bool) envfree; - function getTargetFunctionRole(address,bytes4) external returns (uint64) envfree; - function getTargetAdminDelay(address) external returns (uint32); - function getRoleAdmin(uint64) external returns (uint64) envfree; - function getRoleGuardian(uint64) external returns (uint64) envfree; - function getRoleGrantDelay(uint64) external returns (uint32); - function getAccess(uint64,address) external returns (uint48,uint32,uint32,uint48); - function hasRole(uint64,address) external returns (bool,uint32); - function labelRole(uint64,string) external; - function grantRole(uint64,address,uint32) external; - function revokeRole(uint64,address) external; - function renounceRole(uint64,address) external; - function setRoleAdmin(uint64,uint64) external; - function setRoleGuardian(uint64,uint64) external; - function setGrantDelay(uint64,uint32) external; - function setTargetFunctionRole(address,bytes4[],uint64) external; - function setTargetAdminDelay(address,uint32) external; - function setTargetClosed(address,bool) external; - function hashOperation(address,address,bytes) external returns (bytes32) envfree; - function getNonce(bytes32) external returns (uint32) envfree; - function getSchedule(bytes32) external returns (uint48); - function schedule(address,bytes,uint48) external returns (bytes32,uint32); - function execute(address,bytes) external returns (uint32); - function cancel(address,address,bytes) external returns (uint32); - function consumeScheduledOp(address,bytes) external; - function updateAuthority(address,address) external; -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IERC20.spec b/lib_openzeppelin_contracts/certora/specs/methods/IERC20.spec deleted file mode 100644 index 100901a..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IERC20.spec +++ /dev/null @@ -1,11 +0,0 @@ -methods { - function name() external returns (string) envfree; - function symbol() external returns (string) envfree; - function decimals() external returns (uint8) envfree; - function totalSupply() external returns (uint256) envfree; - function balanceOf(address) external returns (uint256) envfree; - function allowance(address,address) external returns (uint256) envfree; - function approve(address,uint256) external returns (bool); - function transfer(address,uint256) external returns (bool); - function transferFrom(address,address,uint256) external returns (bool); -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IERC2612.spec b/lib_openzeppelin_contracts/certora/specs/methods/IERC2612.spec deleted file mode 100644 index 4ecc17b..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IERC2612.spec +++ /dev/null @@ -1,5 +0,0 @@ -methods { - function permit(address,address,uint256,uint256,uint8,bytes32,bytes32) external; - function nonces(address) external returns (uint256) envfree; - function DOMAIN_SEPARATOR() external returns (bytes32) envfree; -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IERC3156FlashBorrower.spec b/lib_openzeppelin_contracts/certora/specs/methods/IERC3156FlashBorrower.spec deleted file mode 100644 index 733c168..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IERC3156FlashBorrower.spec +++ /dev/null @@ -1,3 +0,0 @@ -methods { - function _.onFlashLoan(address,address,uint256,uint256,bytes) external => DISPATCHER(true); -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IERC3156FlashLender.spec b/lib_openzeppelin_contracts/certora/specs/methods/IERC3156FlashLender.spec deleted file mode 100644 index 66ed14c..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IERC3156FlashLender.spec +++ /dev/null @@ -1,5 +0,0 @@ -methods { - function maxFlashLoan(address) external returns (uint256) envfree; - function flashFee(address,uint256) external returns (uint256) envfree; - function flashLoan(address,address,uint256,bytes) external returns (bool); -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IERC5313.spec b/lib_openzeppelin_contracts/certora/specs/methods/IERC5313.spec deleted file mode 100644 index f1d469f..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IERC5313.spec +++ /dev/null @@ -1,3 +0,0 @@ -methods { - function owner() external returns (address) envfree; -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IERC721.spec b/lib_openzeppelin_contracts/certora/specs/methods/IERC721.spec deleted file mode 100644 index 34ff50b..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IERC721.spec +++ /dev/null @@ -1,17 +0,0 @@ -methods { - // IERC721 - function balanceOf(address) external returns (uint256) envfree; - function ownerOf(uint256) external returns (address) envfree; - function getApproved(uint256) external returns (address) envfree; - function isApprovedForAll(address,address) external returns (bool) envfree; - function safeTransferFrom(address,address,uint256,bytes) external; - function safeTransferFrom(address,address,uint256) external; - function transferFrom(address,address,uint256) external; - function approve(address,uint256) external; - function setApprovalForAll(address,bool) external; - - // IERC721Metadata - function name() external returns (string); - function symbol() external returns (string); - function tokenURI(uint256) external returns (string); -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IERC721Receiver.spec b/lib_openzeppelin_contracts/certora/specs/methods/IERC721Receiver.spec deleted file mode 100644 index e6bdf42..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IERC721Receiver.spec +++ /dev/null @@ -1,3 +0,0 @@ -methods { - function _.onERC721Received(address,address,uint256,bytes) external => DISPATCHER(true); -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IOwnable.spec b/lib_openzeppelin_contracts/certora/specs/methods/IOwnable.spec deleted file mode 100644 index 4d7c925..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IOwnable.spec +++ /dev/null @@ -1,5 +0,0 @@ -methods { - function owner() external returns (address) envfree; - function transferOwnership(address) external; - function renounceOwnership() external; -} diff --git a/lib_openzeppelin_contracts/certora/specs/methods/IOwnable2Step.spec b/lib_openzeppelin_contracts/certora/specs/methods/IOwnable2Step.spec deleted file mode 100644 index e6a9957..0000000 --- a/lib_openzeppelin_contracts/certora/specs/methods/IOwnable2Step.spec +++ /dev/null @@ -1,7 +0,0 @@ -methods { - function owner() external returns (address) envfree; - function pendingOwner() external returns (address) envfree; - function transferOwnership(address) external; - function acceptOwnership() external; - function renounceOwnership() external; -} diff --git a/lib_openzeppelin_contracts/docs/README.md b/lib_openzeppelin_contracts/docs/README.md deleted file mode 100644 index ca39e51..0000000 --- a/lib_openzeppelin_contracts/docs/README.md +++ /dev/null @@ -1,16 +0,0 @@ -Documentation is hosted at https://docs.openzeppelin.com/contracts. - -All of the content for the site is in this repository. The guides are in the -[docs](/docs) directory, and the API Reference is extracted from comments in -the source code. If you want to help improve the content, this is the -repository you should be contributing to. - -[`solidity-docgen`](https://github.com/OpenZeppelin/solidity-docgen) is the -program that extracts the API Reference from source code. - -The [`docs.openzeppelin.com`](https://github.com/OpenZeppelin/docs.openzeppelin.com) -repository hosts the configuration for the entire site, which includes -documentation for all of the OpenZeppelin projects. - -To run the docs locally you should run `npm run docs:watch` on this -repository. diff --git a/lib_openzeppelin_contracts/docs/antora.yml b/lib_openzeppelin_contracts/docs/antora.yml deleted file mode 100644 index 4bc06b3..0000000 --- a/lib_openzeppelin_contracts/docs/antora.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: contracts -title: Contracts -version: 5.x -prerelease: false -nav: - - modules/ROOT/nav.adoc - - modules/api/nav.adoc diff --git a/lib_openzeppelin_contracts/docs/config.js b/lib_openzeppelin_contracts/docs/config.js deleted file mode 100644 index f0af663..0000000 --- a/lib_openzeppelin_contracts/docs/config.js +++ /dev/null @@ -1,21 +0,0 @@ -const path = require('path'); -const fs = require('fs'); - -/** @type import('solidity-docgen/dist/config').UserConfig */ -module.exports = { - outputDir: 'docs/modules/api/pages', - templates: 'docs/templates', - exclude: ['mocks'], - pageExtension: '.adoc', - pages: (_, file, config) => { - // For each contract file, find the closest README.adoc and return its location as the output page path. - const sourcesDir = path.resolve(config.root, config.sourcesDir); - let dir = path.resolve(config.root, file.absolutePath); - while (dir.startsWith(sourcesDir)) { - dir = path.dirname(dir); - if (fs.existsSync(path.join(dir, 'README.adoc'))) { - return path.relative(sourcesDir, dir) + config.pageExtension; - } - } - }, -}; diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-control-multiple.svg b/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-control-multiple.svg deleted file mode 100644 index 0314e09..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-control-multiple.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-manager-functions.svg b/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-manager-functions.svg deleted file mode 100644 index dbbf041..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-manager-functions.svg +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-manager.svg b/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-manager.svg deleted file mode 100644 index 12f91ba..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/images/access-manager.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-3a.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-3a.png deleted file mode 100644 index 4cb5223..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-3a.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-3b.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-3b.png deleted file mode 100644 index 3dc5256..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-3b.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-6.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-6.png deleted file mode 100644 index 1587fb5..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack-6.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack.png deleted file mode 100644 index dc059b2..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-attack.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-deposit.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-deposit.png deleted file mode 100644 index b6c75e6..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-deposit.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-mint.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-mint.png deleted file mode 100644 index f89ab90..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-mint.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-linear.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-linear.png deleted file mode 100644 index 09e8045..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-linear.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-loglog.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-loglog.png deleted file mode 100644 index 4eb19ef..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-loglog.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-loglogext.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-loglogext.png deleted file mode 100644 index 127bc7f..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/erc4626-rate-loglogext.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/tally-exec.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/tally-exec.png deleted file mode 100644 index e24a145..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/tally-exec.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/images/tally-vote.png b/lib_openzeppelin_contracts/docs/modules/ROOT/images/tally-vote.png deleted file mode 100644 index 7d270fc..0000000 Binary files a/lib_openzeppelin_contracts/docs/modules/ROOT/images/tally-vote.png and /dev/null differ diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/nav.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/nav.adoc deleted file mode 100644 index 15af8b4..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/nav.adoc +++ /dev/null @@ -1,23 +0,0 @@ -* xref:index.adoc[Overview] -* xref:wizard.adoc[Wizard] -* xref:extending-contracts.adoc[Extending Contracts] -* xref:upgradeable.adoc[Using with Upgrades] - -* xref:backwards-compatibility.adoc[Backwards Compatibility] - -* xref:access-control.adoc[Access Control] - -* xref:tokens.adoc[Tokens] -** xref:erc20.adoc[ERC-20] -*** xref:erc20-supply.adoc[Creating Supply] -** xref:erc721.adoc[ERC-721] -** xref:erc1155.adoc[ERC-1155] -** xref:erc4626.adoc[ERC-4626] - -* xref:governance.adoc[Governance] - -* xref:utilities.adoc[Utilities] - -* xref:subgraphs::index.adoc[Subgraphs] - -* xref:faq.adoc[FAQ] diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/access-control.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/access-control.adoc deleted file mode 100644 index bef18ca..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/access-control.adoc +++ /dev/null @@ -1,288 +0,0 @@ -= Access Control - -Access control—that is, "who is allowed to do this thing"—is incredibly important in the world of smart contracts. The access control of your contract may govern who can mint tokens, vote on proposals, freeze transfers, and many other things. It is therefore *critical* to understand how you implement it, lest someone else https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7[steals your whole system]. - -[[ownership-and-ownable]] -== Ownership and `Ownable` - -The most common and basic form of access control is the concept of _ownership_: there's an account that is the `owner` of a contract and can do administrative tasks on it. This approach is perfectly reasonable for contracts that have a single administrative user. - -OpenZeppelin Contracts provides xref:api:access.adoc#Ownable[`Ownable`] for implementing ownership in your contracts. - -```solidity -include::api:example$access-control/MyContractOwnable.sol[] -``` - -At deployment, the xref:api:access.adoc#Ownable-owner--[`owner`] of an `Ownable` contract is set to the provided `initialOwner` parameter. - -Ownable also lets you: - -* xref:api:access.adoc#Ownable-transferOwnership-address-[`transferOwnership`] from the owner account to a new one, and -* xref:api:access.adoc#Ownable-renounceOwnership--[`renounceOwnership`] for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. - -WARNING: Removing the owner altogether will mean that administrative tasks that are protected by `onlyOwner` will no longer be callable! - -Ownable is a simple and effective way to implement access control, but you should be mindful of the dangers associated with transferring the ownership to an incorrect account that can't interact with this contract anymore. An alternative to this problem is using xref:api:access.adoc#Ownable2Step[`Ownable2Step`]; a variant of Ownable that requires the new owner to explicitly accept the ownership transfer by calling xref:api:access.adoc#Ownable2Step-acceptOwnership--[`acceptOwnership`]. - -Note that *a contract can also be the owner of another one*! This opens the door to using, for example, a https://gnosis-safe.io[Gnosis Safe], an https://aragon.org[Aragon DAO], or a totally custom contract that _you_ create. - -In this way, you can use _composability_ to add additional layers of access control complexity to your contracts. Instead of having a single regular Ethereum account (Externally Owned Account, or EOA) as the owner, you could use a 2-of-3 multisig run by your project leads, for example. Prominent projects in the space, such as https://makerdao.com[MakerDAO], use systems similar to this one. - -[[role-based-access-control]] -== Role-Based Access Control - -While the simplicity of _ownership_ can be useful for simple systems or quick prototyping, different levels of authorization are often needed. You may want for an account to have permission to ban users from a system, but not create new tokens. https://en.wikipedia.org/wiki/Role-based_access_control[_Role-Based Access Control (RBAC)_] offers flexibility in this regard. - -In essence, we will be defining multiple _roles_, each allowed to perform different sets of actions. An account may have, for example, 'moderator', 'minter' or 'admin' roles, which you will then check for instead of simply using `onlyOwner`. This check can be enforced through the `onlyRole` modifier. Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. - -Most software uses access control systems that are role-based: some users are regular users, some may be supervisors or managers, and a few will often have administrative privileges. - -[[using-access-control]] -=== Using `AccessControl` - -OpenZeppelin Contracts provides xref:api:access.adoc#AccessControl[`AccessControl`] for implementing role-based access control. Its usage is straightforward: for each role that you want to define, -you will create a new _role identifier_ that is used to grant, revoke, and check if an account has that role. - -Here's a simple example of using `AccessControl` in an xref:erc20.adoc[ERC-20 token] to define a 'minter' role, which allows accounts that have it create new tokens: - -[source,solidity] ----- -include::api:example$access-control/AccessControlERC20MintBase.sol[] ----- - -NOTE: Make sure you fully understand how xref:api:access.adoc#AccessControl[`AccessControl`] works before using it on your system, or copy-pasting the examples from this guide. - -While clear and explicit, this isn't anything we wouldn't have been able to achieve with `Ownable`. Indeed, where `AccessControl` shines is in scenarios where granular permissions are required, which can be implemented by defining _multiple_ roles. - -Let's augment our ERC-20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using the `onlyRole` modifier: - -[source,solidity] ----- -include::api:example$access-control/AccessControlERC20MintOnlyRole.sol[] ----- - -So clean! By splitting concerns this way, more granular levels of permission may be implemented than were possible with the simpler _ownership_ approach to access control. Limiting what each component of a system is able to do is known as the https://en.wikipedia.org/wiki/Principle_of_least_privilege[principle of least privilege], and is a good security practice. Note that each account may still have more than one role, if so desired. - -[[granting-and-revoking]] -=== Granting and Revoking Roles - -The ERC-20 token example above uses `_grantRole`, an `internal` function that is useful when programmatically assigning roles (such as during construction). But what if we later want to grant the 'minter' role to additional accounts? - -By default, **accounts with a role cannot grant it or revoke it from other accounts**: all having a role does is making the `hasRole` check pass. To grant and revoke roles dynamically, you will need help from the _role's admin_. - -Every role has an associated admin role, which grants permission to call the `grantRole` and `revokeRole` functions. A role can be granted or revoked by using these if the calling account has the corresponding admin role. Multiple roles may have the same admin role to make management easier. A role's admin can even be the same role itself, which would cause accounts with that role to be able to also grant and revoke it. - -This mechanism can be used to create complex permissioning structures resembling organizational charts, but it also provides an easy way to manage simpler applications. `AccessControl` includes a special role, called `DEFAULT_ADMIN_ROLE`, which acts as the **default admin role for all roles**. An account with this role will be able to manage any other role, unless `_setRoleAdmin` is used to select a new admin role. - -Since it is the admin for all roles by default, and in fact it is also its own admin, this role carries significant risk. To mitigate this risk we provide xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], a recommended extension of `AccessControl` that adds a number of enforced security measures for this role: the admin is restricted to a single account, with a 2-step transfer procedure with a delay in between steps. - -Let's take a look at the ERC-20 token example, this time taking advantage of the default admin role: - -[source,solidity] ----- -include::api:example$access-control/AccessControlERC20MintMissing.sol[] ----- - -Note that, unlike the previous examples, no accounts are granted the 'minter' or 'burner' roles. However, because those roles' admin role is the default admin role, and _that_ role was granted to `msg.sender`, that same account can call `grantRole` to give minting or burning permission, and `revokeRole` to remove it. - -Dynamic role allocation is often a desirable property, for example in systems where trust in a participant may vary over time. It can also be used to support use cases such as https://en.wikipedia.org/wiki/Know_your_customer[KYC], where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. - -[[querying-privileged-accounts]] -=== Querying Privileged Accounts - -Because accounts might <> dynamically, it is not always possible to determine which accounts hold a particular role. This is important as it allows proving certain properties about a system, such as that an administrative account is a multisig or a DAO, or that a certain role has been removed from all users, effectively disabling any associated functionality. - -Under the hood, `AccessControl` uses `EnumerableSet`, a more powerful variant of Solidity's `mapping` type, which allows for key enumeration. `getRoleMemberCount` can be used to retrieve the number of accounts that have a particular role, and `getRoleMember` can then be called to get the address of each of these accounts. - -```javascript -const minterCount = await myToken.getRoleMemberCount(MINTER_ROLE); - -const members = []; -for (let i = 0; i < minterCount; ++i) { - members.push(await myToken.getRoleMember(MINTER_ROLE, i)); -} -``` - -== Delayed operation - -Access control is essential to prevent unauthorized access to critical functions. These functions may be used to mint tokens, freeze transfers or perform an upgrade that completely changes the smart contract logic. While xref:api:access.adoc#Ownable[`Ownable`] and xref:api:access.adoc#AccessControl[`AccessControl`] can prevent unauthorized access, they do not address the issue of a misbehaving administrator attacking their own system to the prejudice of their users. - -This is the issue the xref:api:governance.adoc#TimelockController[`TimelockController`] is addressing. - -The xref:api:governance.adoc#TimelockController[`TimelockController`] is a proxy that is governed by proposers and executors. When set as the owner/admin/controller of a smart contract, it ensures that whichever maintenance operation is ordered by the proposers is subject to a delay. This delay protects the users of the smart contract by giving them time to review the maintenance operation and exit the system if they consider it is in their best interest to do so. - -=== Using `TimelockController` - -By default, the address that deployed the xref:api:governance.adoc#TimelockController[`TimelockController`] gets administration privileges over the timelock. This role grants the right to assign proposers, executors, and other administrators. - -The first step in configuring the xref:api:governance.adoc#TimelockController[`TimelockController`] is to assign at least one proposer and one executor. These can be assigned during construction or later by anyone with the administrator role. These roles are not exclusive, meaning an account can have both roles. - -Roles are managed using the xref:api:access.adoc#AccessControl[`AccessControl`] interface and the `bytes32` values for each role are accessible through the `ADMIN_ROLE`, `PROPOSER_ROLE` and `EXECUTOR_ROLE` constants. - -There is an additional feature built on top of `AccessControl`: giving the executor role to `address(0)` opens access to anyone to execute a proposal once the timelock has expired. This feature, while useful, should be used with caution. - -At this point, with both a proposer and an executor assigned, the timelock can perform operations. - -An optional next step is for the deployer to renounce its administrative privileges and leave the timelock self-administered. If the deployer decides to do so, all further maintenance, including assigning new proposers/schedulers or changing the timelock duration will have to follow the timelock workflow. This links the governance of the timelock to the governance of contracts attached to the timelock, and enforce a delay on timelock maintenance operations. - -WARNING: If the deployer renounces administrative rights in favour of timelock itself, assigning new proposers or executors will require a timelocked operation. This means that if the accounts in charge of any of these two roles become unavailable, then the entire contract (and any contract it controls) becomes locked indefinitely. - -With both the proposer and executor roles assigned and the timelock in charge of its own administration, you can now transfer the ownership/control of any contract to the timelock. - -TIP: A recommended configuration is to grant both roles to a secure governance contract such as a DAO or a multisig, and to additionally grant the executor role to a few EOAs held by people in charge of helping with the maintenance operations. These wallets cannot take over control of the timelock but they can help smoothen the workflow. - -=== Minimum delay - -Operations executed by the xref:api:governance.adoc#TimelockController[`TimelockController`] are not subject to a fixed delay but rather a minimum delay. Some major updates might call for a longer delay. For example, if a delay of just a few days might be sufficient for users to audit a minting operation, it makes sense to use a delay of a few weeks, or even a few months, when scheduling a smart contract upgrade. - -The minimum delay (accessible through the xref:api:governance.adoc#TimelockController-getMinDelay--[`getMinDelay`] method) can be updated by calling the xref:api:governance.adoc#TimelockController-updateDelay-uint256-[`updateDelay`] function. Bear in mind that access to this function is only accessible by the timelock itself, meaning this maintenance operation has to go through the timelock itself. - -[[access-management]] -== Access Management - -For a system of contracts, better integrated role management can be achieved with an xref:api:access.adoc#AccessManager[`AccessManager`] instance. Instead of managing each contract's permission separately, AccessManager stores all the permissions in a single contract, making your protocol easier to audit and maintain. - -Although xref:api:access.adoc#AccessControl[`AccessControl`] offers a more dynamic solution for adding permissions to your contracts than Ownable, decentralized protocols tend to become more complex after integrating new contract instances and requires you to keep track of permissions separately in each contract. This increases the complexity of permissions management and monitoring across the system. - -image::access-control-multiple.svg[Access Control multiple] - -Protocols managing permissions in production systems often require more integrated alternatives to fragmented permissions through multiple `AccessControl` instances. - -image::access-manager.svg[AccessManager] - -The AccessManager is designed around the concept of role and target functions: - -* Roles are granted to accounts (addresses) following a many-to-many approach for flexibility. This means that each user can have one or multiple roles and multiple users can have the same role. -* Access to a restricted target function is limited to one role. A target function is defined by one https://docs.soliditylang.org/en/v0.8.20/abi-spec.html#function-selector[function selector] on one contract (called target). - -For a call to be authorized, the caller must bear the role that is assigned to the current target function (contract address + function selector). - -image::access-manager-functions.svg[AccessManager functions] - -=== Using `AccessManager` - -OpenZeppelin Contracts provides xref:api:access.adoc#AccessManager[`AccessManager`] for managing roles across any number of contracts. The `AccessManager` itself is a contract that can be deployed and used out of the box. It sets an initial admin in the constructor who will be allowed to perform management operations. - -In order to restrict access to some functions of your contract, you should inherit from the xref:api:access.adoc#AccessManaged[`AccessManaged`] contract provided along with the manager. This provides the `restricted` modifier that can be used to protect any externally facing function. Note that you will have to specify the address of the AccessManager instance (xref:api:access.adoc#AccessManaged-constructor-address-[`initialAuthority`]) in the constructor so the `restricted` modifier knows which manager to use for checking permissions. - -Here's a simple example of an xref:tokens.adoc#ERC20[ERC-20 token] that defines a `mint` function that is restricted by an xref:api:access.adoc#AccessManager[`AccessManager`]: - -```solidity -include::api:example$access-control/AccessManagedERC20MintBase.sol[] -``` - -NOTE: Make sure you fully understand how xref:api:access.adoc#AccessManager[`AccessManager`] works before using it or copy-pasting the examples from this guide. - -Once the managed contract has been deployed, it is now under the manager's control. The initial admin can then assign the minter role to an address and also allow the role to call the `mint` function. For example, this is demonstrated in the following Javascript code using Ethers.js: - -```javascript -// const target = ...; -// const user = ...; -const MINTER = 42n; // Roles are uint64 (0 is reserved for the ADMIN_ROLE) - -// Grant the minter role with no execution delay -await manager.grantRole(MINTER, user, 0); - -// Allow the minter role to call the function selector -// corresponding to the mint function -await manager.setTargetFunctionRole( - target, - ['0x40c10f19'], // bytes4(keccak256('mint(address,uint256)')) - MINTER -); -``` - -Even though each role has its own list of function permissions, each role member (`address`) has an execution delay that will dictate how long the account should wait to execute a function that requires its role. Delayed operations must have the xref:api:access.adoc#AccessManager-schedule-address-bytes-uint48-[`schedule`] function called on them first in the AccessManager before they can be executed, either by calling to the target function or using the AccessManager's xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] function. - -Additionally, roles can have a granting delay that prevents adding members immediately. The AccessManager admins can set this grant delay as follows: - -```javascript -const HOUR = 60 * 60; - -const GRANT_DELAY = 24 * HOUR; -const EXECUTION_DELAY = 5 * HOUR; -const ACCOUNT = "0x..."; - -await manager.connect(initialAdmin).setGrantDelay(MINTER, GRANT_DELAY); - -// The role will go into effect after the GRANT_DELAY passes -await manager.connect(initialAdmin).grantRole(MINTER, ACCOUNT, EXECUTION_DELAY); -``` - -Note that roles do not define a name. As opposed to the xref:api:access.adoc#AccessControl[`AccessControl`] case, roles are identified as numeric values instead of being hardcoded in the contract as `bytes32` values. It is still possible to allow for tooling discovery (e.g. for role exploration) using role labeling with the xref:api:access.adoc#AccessManager-labelRole-uint64-string-[`labelRole`] function. - -```javascript -await manager.labelRole(MINTER, "MINTER"); -``` - -Given the admins of the `AccessManaged` can modify all of its permissions, it's recommended to keep only a single admin address secured under a multisig or governance layer. To achieve this, it is possible for the initial admin to set up all the required permissions, targets, and functions, assign a new admin, and finally renounce its admin role. - -For improved incident response coordination, the manager includes a mode where administrators can completely close a target contract. When closed, all calls to restricted target functions in a target contract will revert. - -Closing and opening contracts don't alter any of their settings, neither permissions nor delays. Particularly, the roles required for calling specific target functions are not modified. - -This mode is useful for incident response operations that require temporarily shutting down a contract in order to evaluate emergencies and reconfigure permissions. - -```javascript -const target = await myToken.getAddress(); - -// Token's `restricted` functions closed -await manager.setTargetClosed(target, true); - -// Token's `restricted` functions open -await manager.setTargetClosed(target, false); -``` - -WARNING: Even if an `AccessManager` defines permissions for a target function, these won't be applied if the managed contract instance is not using the xref:api:access.adoc#AccessManaged-restricted--[`restricted`] modifier for that function, or if its manager is a different one. - -=== Role Admins and Guardians - -An important aspect of the AccessControl contract is that roles aren't granted nor revoked by role members. Instead, it relies on the concept of a role admin for granting and revoking. - -In the case of the `AccessManager`, the same rule applies and only the role's admins are able to call xref:api:access.adoc#AccessManager-grantRole-uint64-address-uint32-[grant] and xref:api:access.adoc#AccessManager-revokeRole-uint64-address-[revoke] functions. Note that calling these functions will be subject to the execution delay that the executing role admin has. - -Additionally, the `AccessManager` stores a _guardian_ as an extra protection for each role. This guardian has the ability to cancel operations that have been scheduled by any role member with an execution delay. Consider that a role will have its initial admin and guardian default to the `ADMIN_ROLE` (`0`). - -IMPORTANT: Be careful with the members of `ADMIN_ROLE`, since it acts as the default admin and guardian for every role. A misbehaved guardian can cancel operations at will, affecting the AccessManager's operation. - -=== Manager configuration - -The `AccessManager` provides a built-in interface for configuring permission settings that can be accessed by its `ADMIN_ROLE` members. - -This configuration interface includes the following functions: - -* Add a label to a role using the xref:api:access.adoc#AccessManager-labelRole-uint64-string-[`labelRole`] function. -* Assign the admin and guardian of a role with xref:api:access.adoc#AccessManager-setRoleAdmin-uint64-uint64-[`setRoleAdmin`] and xref:api:access.adoc#AccessManager-setRoleGuardian-uint64-uint64-[`setRoleGuardian`]. -* Set each role's grant delay via xref:api:access.adoc#AccessManager-setGrantDelay-uint64-uint32-[`setGrantDelay`]. - -As an admin, some actions will require a delay. Similar to each member's execution delay, some admin operations require waiting for execution and should follow the xref:api:access.adoc#AccessManager-schedule-address-bytes-uint48-[`schedule`] and xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] workflow. - -More specifically, these delayed functions are those for configuring the settings of a specific target contract. The delay applied to these functions can be adjusted by the manager admins with xref:api:access.adoc#AccessManager-setTargetAdminDelay-address-uint32-[`setTargetAdminDelay`]. - -The delayed admin actions are: - -* Updating an `AccessManaged` contract xref:api:access.adoc#AccessManaged-authority--[authority] using xref:api:access.adoc#AccessManager-updateAuthority-address-address-[`updateAuthority`]. -* Closing or opening a target via xref:api:access.adoc#AccessManager-setTargetClosed-address-bool-[`setTargetClosed`]. -* Changing permissions of whether a role can call a target function with xref:api:access.adoc#AccessManager-setTargetFunctionRole-address-bytes4---uint64-[`setTargetFunctionRole`]. - -=== Using with Ownable - -Contracts already inheriting from xref:api:access.adoc#Ownable[`Ownable`] can migrate to AccessManager by transferring ownership to the manager. After that, all calls to functions with the `onlyOwner` modifier should be called through the manager's xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] function, even if the caller doesn't require a delay. - -```javascript -await ownable.connect(owner).transferOwnership(accessManager); -``` - -=== Using with AccessControl - -For systems already using xref:api:access.adoc#AccessControl[`AccessControl`], the `DEFAULT_ADMIN_ROLE` can be granted to the `AccessManager` after revoking every other role. Subsequent calls should be made through the manager's xref:api:access.adoc#AccessManager-execute-address-bytes-[`execute`] method, similar to the Ownable case. - -```javascript -// Revoke old roles -await accessControl.connect(admin).revokeRole(MINTER_ROLE, account); - -// Grant the admin role to the access manager -await accessControl.connect(admin).grantRole(DEFAULT_ADMIN_ROLE, accessManager); - -await accessControl.connect(admin).renounceRole(DEFAULT_ADMIN_ROLE, admin); -``` diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/backwards-compatibility.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/backwards-compatibility.adoc deleted file mode 100644 index 3737a99..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/backwards-compatibility.adoc +++ /dev/null @@ -1,48 +0,0 @@ -= Backwards Compatibility -:page-aliases: releases-stability.adoc - -OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. Patch and minor updates will generally be backwards compatible, with rare exceptions as detailed below. Major updates should be assumed incompatible with previous releases. On this page, we provide details about these guarantees. - -== API - -In backwards compatible releases, all changes should be either additions or modifications to internal implementation details. Most code should continue to compile and behave as expected. The exceptions to this rule are listed below. - -=== Security - -Infrequently a patch or minor update will remove or change an API in a breaking way, but only if the previous API is considered insecure. These breaking changes will be noted in the changelog and release notes, and published along with a security advisory. - -=== Draft or Pre-Final ERCs - -ERCs that are not Final can change in incompatible ways. For this reason, we avoid shipping implementations of ERCs before they are Final. Some exceptions are made for ERCs that have been published for a long time and that seem unlikely to change. Breaking changes to the ERC specification are still technically possible in those cases, so these implementations are published in files named `draft-*.sol` to make that condition explicit. - -=== Virtual & Overrides - -Almost all functions in this library are virtual with some exceptions, but this does not mean that overrides are encouraged. There is a subset of functions that are designed to be overridden. By defining overrides outside of this subset you are potentially relying on internal implementation details. We make efforts to preserve backwards compatibility even in these cases but it is extremely difficult and easy to accidentally break. Caution is advised. - -Additionally, some minor updates may result in new compilation errors of the kind "two or more base classes define function with same name and parameter types" or "need to specify overridden contract", due to what Solidity considers ambiguity in inherited functions. This should be resolved by adding an override that invokes the function via `super`. - -See xref:extending-contracts.adoc[Extending Contracts] for more about virtual and overrides. - -=== Structs - -Struct members with an underscore prefix should be considered "private" and may break in minor versions. Struct data should only be accessed and modified through library functions. - -=== Errors - -The specific error format and data that is included with reverts should not be assumed stable unless otherwise specified. - -=== Major Releases - -Major releases should be assumed incompatible. Nevertheless, the external interfaces of contracts will remain compatible if they are standardized, or if the maintainers judge that changing them would cause significant strain on the ecosystem. - -An important aspect that major releases may break is "upgrade compatibility", in particular storage layout compatibility. It will never be safe for a live contract to upgrade from one major release to another. - -== Storage Layout - -Minor and patch updates always preserve storage layout compatibility. This means that a live contract can be upgraded from one minor to another without corrupting the storage layout. In some cases it may be necessary to initialize new state variables when upgrading, although we expect this to be infrequent. - -We recommend using xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins or CLI] to ensure storage layout safety of upgrades. - -== Solidity Version - -The minimum Solidity version required to compile the contracts will remain unchanged in minor and patch updates. New contracts introduced in minor releases may make use of newer Solidity features and require a more recent version of the compiler. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/crowdsales.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/crowdsales.adoc deleted file mode 100644 index 3757921..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/crowdsales.adoc +++ /dev/null @@ -1,11 +0,0 @@ -= Crowdsales - -All crowdsale-related contracts were removed from the OpenZeppelin Contracts library on the https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256[v3.0.0 release] due to both a decline in their usage and the complexity associated with migrating them to Solidity v0.6. - -They are however still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: - -```console -$ npm install @openzeppelin/contracts@v2.5 -``` - -Refer to the https://docs.openzeppelin.com/contracts/2.x/crowdsales[v2.x documentation] when working with them. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/drafts.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/drafts.adoc deleted file mode 100644 index b2c1ae6..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/drafts.adoc +++ /dev/null @@ -1,19 +0,0 @@ -= Drafts - -All draft contracts were either moved into a different directory or removed from the OpenZeppelin Contracts library on the https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256[v3.0.0 release]. - -* `ERC20Migrator`: removed. -* xref:api:token/ERC20.adoc#ERC20Snapshot[`ERC20Snapshot`]: moved to `token/ERC20`. -* `ERC20Detailed` and `ERC1046`: removed. -* `TokenVesting`: removed. Pending a replacement that is being discussed in https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1214[`#1214`]. -* xref:api:utils.adoc#Counters[`Counters`]: moved to xref:api:utils.adoc[`utils`]. -* xref:api:utils.adoc#Strings[`Strings`]: moved to xref:api:utils.adoc[`utils`]. -* xref:api:utils.adoc#SignedSafeMath[`SignedSafeMath`]: moved to xref:api:utils.adoc[`utils`]. - -Removed contracts are still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: - -```console -$ npm install @openzeppelin/contracts@v2.5 -``` - -Refer to the xref:2.x@contracts:api:drafts.adoc[v2.x documentation] when working with them. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc1155.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc1155.adoc deleted file mode 100644 index 5bfb49a..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc1155.adoc +++ /dev/null @@ -1,118 +0,0 @@ -= ERC-1155 - -ERC-1155 is a novel token standard that aims to take the best from previous standards to create a xref:tokens.adoc#different-kinds-of-tokens[*fungibility-agnostic*] and *gas-efficient* xref:tokens.adoc#but_first_coffee_a_primer_on_token_contracts[token contract]. - -TIP: ERC-1155 draws ideas from all of xref:erc20.adoc[ERC-20], xref:erc721.adoc[ERC-721], and https://eips.ethereum.org/EIPS/eip-777[ERC-777]. If you're unfamiliar with those standards, head to their guides before moving on. - -[[multi-token-standard]] -== Multi Token Standard - -The distinctive feature of ERC-1155 is that it uses a single smart contract to represent multiple tokens at once. This is why its xref:api:token/ERC1155.adoc#IERC1155-balanceOf-address-uint256-[`balanceOf`] function differs from ERC-20's and ERC-777's: it has an additional `id` argument for the identifier of the token that you want to query the balance of. - -This is similar to how ERC-721 does things, but in that standard a token `id` has no concept of balance: each token is non-fungible and exists or doesn't. The ERC-721 xref:api:token/ERC721.adoc#IERC721-balanceOf-address-[`balanceOf`] function refers to _how many different tokens_ an account has, not how many of each. On the other hand, in ERC-1155 accounts have a distinct balance for each token `id`, and non-fungible tokens are implemented by simply minting a single one of them. - -This approach leads to massive gas savings for projects that require multiple tokens. Instead of deploying a new contract for each token type, a single ERC-1155 token contract can hold the entire system state, reducing deployment costs and complexity. - -[[batch-operations]] -== Batch Operations - -Because all state is held in a single contract, it is possible to operate over multiple tokens in a single transaction very efficiently. The standard provides two functions, xref:api:token/ERC1155.adoc#IERC1155-balanceOfBatch-address---uint256---[`balanceOfBatch`] and xref:api:token/ERC1155.adoc#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-[`safeBatchTransferFrom`], that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. - -In the spirit of the standard, we've also included batch operations in the non-standard functions, such as xref:api:token/ERC1155.adoc#ERC1155-_mintBatch-address-uint256---uint256---bytes-[`_mintBatch`]. - -== Constructing an ERC-1155 Token Contract - -We'll use ERC-1155 to track multiple items in our game, which will each have their own unique attributes. We mint all items to the deployer of the contract, which we can later transfer to players. Players are free to keep their tokens or trade them with other people as they see fit, as they would any other asset on the blockchain! - -For simplicity, we will mint all items in the constructor, but you could add minting functionality to the contract to mint on demand to players. - -TIP: For an overview of minting mechanisms, check out xref:erc20-supply.adoc[Creating ERC-20 Supply]. - -Here's what a contract for tokenized items might look like: - -[source,solidity] ----- -include::api:example$token/ERC1155/GameItems.sol[] ----- - -Note that for our Game Items, Gold is a fungible token whilst Thor's Hammer is a non-fungible token as we minted only one. - -The xref:api:token/ERC1155.adoc#ERC1155[`ERC1155`] contract includes the optional extension xref:api:token/ERC1155.adoc#IERC1155MetadataURI[`IERC1155MetadataURI`]. That's where the xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`uri`] function comes from: we use it to retrieve the metadata uri. - -Also note that, unlike ERC-20, ERC-1155 lacks a `decimals` field, since each token is distinct and cannot be partitioned. - -Once deployed, we will be able to query the deployer’s balance: -[source,javascript] ----- -> gameItems.balanceOf(deployerAddress,3) -1000000000 ----- - -We can transfer items to player accounts: -[source,javascript] ----- -> gameItems.safeTransferFrom(deployerAddress, playerAddress, 2, 1, "0x0") -> gameItems.balanceOf(playerAddress, 2) -1 -> gameItems.balanceOf(deployerAddress, 2) -0 ----- - -We can also batch transfer items to player accounts and get the balance of batches: -[source,javascript] ----- -> gameItems.safeBatchTransferFrom(deployerAddress, playerAddress, [0,1,3,4], [50,100,1,1], "0x0") -> gameItems.balanceOfBatch([playerAddress,playerAddress,playerAddress,playerAddress,playerAddress], [0,1,2,3,4]) -[50,100,1,1,1] ----- - -The metadata uri can be obtained: - -[source,javascript] ----- -> gameItems.uri(2) -"https://game.example/api/item/{id}.json" ----- - -The `uri` can include the string `++{id}++` which clients must replace with the actual token ID, in lowercase hexadecimal (with no 0x prefix) and leading zero padded to 64 hex characters. - -For token ID `2` and uri `++https://game.example/api/item/{id}.json++` clients would replace `++{id}++` with `0000000000000000000000000000000000000000000000000000000000000002` to retrieve JSON at `https://game.example/api/item/0000000000000000000000000000000000000000000000000000000000000002.json`. - -The JSON document for token ID 2 might look something like: - -[source,json] ----- -{ - "name": "Thor's hammer", - "description": "Mjölnir, the legendary hammer of the Norse god of thunder.", - "image": "https://game.example/item-id-8u5h2m.png", - "strength": 20 -} ----- - -For more information about the metadata JSON Schema, check out the https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema[ERC-1155 Metadata URI JSON Schema]. - -NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game! - -TIP: If you'd like to put all item information on-chain, you can extend ERC-721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the URI information, but these techniques are out of the scope of this overview guide - -[[sending-to-contracts]] -== Sending Tokens to Contracts - -A key difference when using xref:api:token/ERC1155.adoc#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-[`safeTransferFrom`] is that token transfers to other contracts may revert with the following custom error: - -[source,text] ----- -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: - -[source,solidity] ----- -include::api:example$token/ERC1155/MyERC115HolderContract.sol[] ----- - -We can also implement more complex scenarios using the xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-[`onERC1155Received`] and xref:api:token/ERC1155.adoc#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-[`onERC1155BatchReceived`] functions. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc20-supply.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc20-supply.adoc deleted file mode 100644 index ae21e4a..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc20-supply.adoc +++ /dev/null @@ -1,71 +0,0 @@ -= Creating ERC-20 Supply - -In this guide, you will learn how to create an ERC-20 token with a custom supply mechanism. We will showcase two idiomatic ways to use OpenZeppelin Contracts for this purpose that you will be able to apply to your smart contract development practice. - -The standard interface implemented by tokens built on Ethereum is called ERC-20, and Contracts includes a widely used implementation of it: the aptly named xref:api:token/ERC20.adoc[`ERC20`] contract. This contract, like the standard itself, is quite simple and bare-bones. In fact, if you try to deploy an instance of `ERC20` as-is it will be quite literally useless... it will have no supply! What use is a token with no supply? - -The way that supply is created is not defined in the ERC-20 document. Every token is free to experiment with its own mechanisms, ranging from the most decentralized to the most centralized, from the most naive to the most researched, and more. - -[[fixed-supply]] -== Fixed Supply - -Let's say we want a token with a fixed supply of 1000, initially allocated to the account that deploys the contract. If you've used Contracts v1, you may have written code like the following: - -[source,solidity] ----- -contract ERC20FixedSupply is ERC20 { - constructor() { - totalSupply += 1000; - balances[msg.sender] += 1000; - } -} ----- - -Starting with Contracts v2, this pattern is not only discouraged, but disallowed. The variables `totalSupply` and `balances` are now private implementation details of `ERC20`, and you can't directly write to them. Instead, there is an internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`] function that will do exactly this: - -[source,solidity] ----- -contract ERC20FixedSupply is ERC20 { - constructor() ERC20("Fixed", "FIX") { - _mint(msg.sender, 1000); - } -} ----- - -Encapsulating state like this makes it safer to extend contracts. For instance, in the first example we had to manually keep the `totalSupply` in sync with the modified balances, which is easy to forget. In fact, we omitted something else that is also easily forgotten: the `Transfer` event that is required by the standard, and which is relied on by some clients. The second example does not have this bug, because the internal `_mint` function takes care of it. - -[[rewarding-miners]] -== Rewarding Miners - -The internal xref:api:token/ERC20.adoc#ERC20-_mint-address-uint256-[`_mint`] function is the key building block that allows us to write ERC-20 extensions that implement a supply mechanism. - -The mechanism we will implement is a token reward for the miners that produce Ethereum blocks. In Solidity, we can access the address of the current block's miner in the global variable `block.coinbase`. We will mint a token reward to this address whenever someone calls the function `mintMinerReward()` on our token. The mechanism may sound silly, but you never know what kind of dynamic this might result in, and it's worth analyzing and experimenting with! - -[source,solidity] ----- -contract ERC20WithMinerReward is ERC20 { - constructor() ERC20("Reward", "RWD") {} - - function mintMinerReward() public { - _mint(block.coinbase, 1000); - } -} ----- - -As we can see, `_mint` makes it super easy to do this correctly. - -[[automating-the-reward]] -== Automating the Reward - -So far our supply mechanism was triggered manually, but `ERC20` also allows us to extend the core functionality of the token through the xref:api:token/ERC20.adoc#ERC20-_update-address-address-uint256-[`_update`] function. - -Adding to the supply mechanism from the previous section, we can use this function to mint a miner reward for every token transfer that is included in the blockchain. - -```solidity -include::api:example$ERC20WithAutoMinerReward.sol[] -``` - -[[wrapping-up]] -== Wrapping Up - -We've seen how to implement a ERC-20 supply mechanism: internally through `_mint`. Hopefully this has helped you understand how to use OpenZeppelin Contracts and some of the design principles behind it, and you can apply them to your own smart contracts. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc20.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc20.adoc deleted file mode 100644 index 104b4ef..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc20.adoc +++ /dev/null @@ -1,67 +0,0 @@ -= ERC-20 - -An ERC-20 token contract keeps track of xref:tokens.adoc#different-kinds-of-tokens[_fungible_ tokens]: any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC-20 tokens useful for things like a *medium of exchange currency*, *voting rights*, *staking*, and more. - -OpenZeppelin Contracts provides many ERC20-related contracts. On the xref:api:token/ERC20.adoc[`API reference`] you'll find detailed information on their properties and usage. - -[[constructing-an-erc20-token-contract]] -== Constructing an ERC-20 Token Contract - -Using Contracts, we can easily create our own ERC-20 token contract, which will be used to track _Gold_ (GLD), an internal currency in a hypothetical game. - -Here's what our GLD token might look like. - -[source,solidity] ----- -include::api:example$token/ERC20/GLDToken.sol[] ----- - -Our contracts are often used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance], and here we're reusing xref:api:token/ERC20.adoc#erc20[`ERC20`] for both the basic standard implementation and the xref:api:token/ERC20.adoc#ERC20-name--[`name`], xref:api:token/ERC20.adoc#ERC20-symbol--[`symbol`], and xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] optional extensions. Additionally, we're creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract. - -TIP: For a more complete discussion of ERC-20 supply mechanisms, see xref:erc20-supply.adoc[Creating ERC-20 Supply]. - -That's it! Once deployed, we will be able to query the deployer's balance: - -[source,javascript] ----- -> GLDToken.balanceOf(deployerAddress) -1000000000000000000000 ----- - -We can also xref:api:token/ERC20.adoc#IERC20-transfer-address-uint256-[transfer] these tokens to other accounts: - -[source,javascript] ----- -> GLDToken.transfer(otherAddress, 300000000000000000000) -> GLDToken.balanceOf(otherAddress) -300000000000000000000 -> GLDToken.balanceOf(deployerAddress) -700000000000000000000 ----- - -[[a-note-on-decimals]] -== A Note on `decimals` - -Often, you'll want to be able to divide your tokens into arbitrary amounts: say, if you own `5 GLD`, you may want to send `1.5 GLD` to a friend, and keep `3.5 GLD` to yourself. Unfortunately, Solidity and the EVM do not support this behavior: only integer (whole) numbers can be used, which poses an issue. You may send `1` or `2` tokens, but not `1.5`. - -To work around this, xref:api:token/ERC20.adoc#ERC20[`ERC20`] provides a xref:api:token/ERC20.adoc#ERC20-decimals--[`decimals`] field, which is used to specify how many decimal places a token has. To be able to transfer `1.5 GLD`, `decimals` must be at least `1`, since that number has a single decimal place. - -How can this be achieved? It's actually very simple: a token contract can use larger integer values, so that a balance of `50` will represent `5 GLD`, a transfer of `15` will correspond to `1.5 GLD` being sent, and so on. - -It is important to understand that `decimals` is _only used for display purposes_. All arithmetic inside the contract is still performed on integers, and it is the different user interfaces (wallets, exchanges, etc.) that must adjust the displayed values according to `decimals`. The total token supply and balance of each account are not specified in `GLD`: you need to divide by `10 ** decimals` to get the actual `GLD` amount. - -You'll probably want to use a `decimals` value of `18`, just like Ether and most ERC-20 token contracts in use, unless you have a very special reason not to. When minting tokens or transferring them around, you will be actually sending the number `num GLD * (10 ** decimals)`. - -NOTE: By default, `ERC20` uses a value of `18` for `decimals`. To use a different value, you will need to override the `decimals()` function in your contract. - -```solidity -function decimals() public view virtual override returns (uint8) { - return 16; -} -``` - -So if you want to send `5` tokens using a token contract with 18 decimals, the method to call will actually be: - -```solidity -transfer(recipient, 5 * (10 ** 18)); -``` diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc4626.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc4626.adoc deleted file mode 100644 index 03f3c21..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc4626.adoc +++ /dev/null @@ -1,214 +0,0 @@ -= ERC-4626 -:stem: latexmath - -https://eips.ethereum.org/EIPS/eip-4626[ERC-4626] is an extension of xref:erc20.adoc[ERC-20] that proposes a standard interface for token vaults. This standard interface can be used by widely different contracts (including lending markets, aggregators, and intrinsically interest bearing tokens), which brings a number of subtleties. Navigating these potential issues is essential to implementing a compliant and composable token vault. - -We provide a base implementation of ERC-4626 that includes a simple vault. This contract is designed in a way that allows developers to easily re-configure the vault's behavior, with minimal overrides, while staying compliant. In this guide, we will discuss some security considerations that affect ERC-4626. We will also discuss common customizations of the vault. - -[[inflation-attack]] -== Security concern: Inflation attack - -=== Visualizing the vault - -In exchange for the assets deposited into an ERC-4626 vault, a user receives shares. These shares can later be burned to redeem the corresponding underlying assets. The number of shares a user gets depends on the amount of assets they put in and on the exchange rate of the vault. This exchange rate is defined by the current liquidity held by the vault. - -- If a vault has 100 tokens to back 200 shares, then each share is worth 0.5 assets. -- If a vault has 200 tokens to back 100 shares, then each share is worth 2.0 assets. - -In other words, the exchange rate can be defined as the slope of the line that passes through the origin and the current number of assets and shares in the vault. Deposits and withdrawals move the vault in this line. - -image::erc4626-rate-linear.png[Exchange rates in linear scale] - -When plotted in log-log scale, the rate is defined similarly, but appears differently (because the point (0,0) is infinitely far away). Rates are represented by "diagonal" lines with different offsets. - -image::erc4626-rate-loglog.png[Exchange rates in logarithmic scale] - -In such a representation, widely different rates can be clearly visible in the same graph. This wouldn't be the case in linear scale. - -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. - -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. - -image::erc4626-deposit.png[Depositing assets] - -In the figure we can see that for a given deposit of 500 assets, the number of shares we get and the corresponding rounding losses depend on the exchange rate. If the exchange rate is that of the orange curve, we are getting less than a share, so we lose 100% of our deposit. However, if the exchange rate is that of the green curve, we get 5000 shares, which limits our rounding losses to at most 0.02%. - -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. - -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. - -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. - -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. - -In math that gives: - -- stem:[a_0] the attacker deposit -- stem:[a_1] the attacker donation -- stem:[u] the user deposit - -[%header,cols=4*] -|=== -| -| Assets -| Shares -| Rate - -| initial -| stem:[0] -| stem:[0] -| - - -| after attacker's deposit -| stem:[a_0] -| stem:[a_0] -| stem:[1] - -| after attacker's donation -| stem:[a_0+a_1] -| stem:[a_0] -| stem:[\frac{a_0}{a_0+a_1}] -|=== - -This means a deposit of stem:[u] will give stem:[\frac{u \times a_0}{a_0 + a_1}] shares. - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -[stem] -++++ -\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0} -++++ - -Using stem:[a_0 = 1] and stem:[a_1 = u] is enough. So the attacker only needs stem:[u+1] assets to perform a successful attack. - -It is easy to generalize the above results to scenarios where the attacker is going after a smaller fraction of the user's deposit. In order to target stem:[\frac{u}{n}], the user needs to suffer rounding of a similar fraction, which means the user must receive at most stem:[n] shares. This results in: - -[stem] -++++ -\frac{u \times a_0}{a_0+a_1} < n \iff \frac{u}{n} < 1 + \frac{a_1}{a_0} -++++ - -In this scenario, the attack is stem:[n] times less powerful (in how much it is stealing) and costs stem:[n] times less to execute. In both cases, the amount of funds the attacker needs to commit is equivalent to its potential earnings. - -=== Defending with a virtual offset - -The defense we propose is based on the approach used in link:https://github.com/boringcrypto/YieldBox[YieldBox]. It consists of two parts: - -- Use an offset between the "precision" of the representation of shares and assets. Said otherwise, we use more decimal places to represent the shares than the underlying token does to represent the assets. -- Include virtual shares and virtual assets in the exchange rate computation. These virtual assets enforce the conversion rate when the vault is empty. - -These two parts work together in enforcing the security of the vault. First, the increased precision corresponds to a high rate, which we saw is safer as it reduces the rounding error when computing the amount of shares. Second, the virtual assets and shares (in addition to simplifying a lot of the computations) capture part of the donation, making it unprofitable for a developer to perform an attack. - -Following the previous math definitions, we have: - -- stem:[\delta] the vault offset -- stem:[a_0] the attacker deposit -- stem:[a_1] the attacker donation -- stem:[u] the user deposit - -[%header,cols=4*] -|=== -| -| Assets -| Shares -| Rate - -| initial -| stem:[1] -| stem:[10^\delta] -| stem:[10^\delta] - -| after attacker's deposit -| stem:[1+a_0] -| stem:[10^\delta \times (1+a_0)] -| stem:[10^\delta] - -| after attacker's donation -| stem:[1+a_0+a_1] -| stem:[10^\delta \times (1+a_0)] -| stem:[10^\delta \times \frac{1+a_0}{1+a_0+a_1}] -|=== - -One important thing to note is that the attacker only owns a fraction stem:[\frac{a_0}{1 + a_0}] of the shares, so when doing the donation, he will only be able to recover that fraction stem:[\frac{a_1 \times a_0}{1 + a_0}] of the donation. The remaining stem:[\frac{a_1}{1+a_0}] are captured by the vault. - -[stem] -++++ -\mathit{loss} = \frac{a_1}{1+a_0} -++++ - -When the user deposits stem:[u], he receives - -[stem] -++++ -10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} -++++ - -For the attacker to dilute that deposit to 0 shares, causing the user to lose all its deposit, it must ensure that - -[stem] -++++ -10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1 -++++ - -[stem] -++++ -\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0} -++++ - -[stem] -++++ -\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0} -++++ - -[stem] -++++ -\iff 10^\delta \times u \le \mathit{loss} -++++ - -- If the offset is 0, the attacker loss is at least equal to the user's deposit. -- If the offset is greater than 0, the attacker will have to suffer losses that are orders of magnitude bigger than the amount of value that can hypothetically be stolen from the user. - -This shows that even with an offset of 0, the virtual shares and assets make this attack non profitable for the attacker. Bigger offsets increase the security even further by making any attack on the user extremely wasteful. - -The following figure shows how the offset impacts the initial rate and limits the ability of an attacker with limited funds to inflate it effectively. - -image::erc4626-attack-3a.png[Inflation attack without offset=3] -stem:[\delta = 3], stem:[a_0 = 1], stem:[a_1 = 10^5] - -image::erc4626-attack-3b.png[Inflation attack without offset=3 and an attacker deposit that limits its losses] -stem:[\delta = 3], stem:[a_0 = 100], stem:[a_1 = 10^5] - -image::erc4626-attack-6.png[Inflation attack without offset=6] -stem:[\delta = 6], stem:[a_0 = 1], stem:[a_1 = 10^5] - - -[[fees]] -== Custom behavior: Adding fees to the vault - -In an ERC-4626 vaults, fees can be captured during the deposit/mint and/or during the withdraw/redeem steps. In both cases it is essential to remain compliant with the ERC-4626 requirements with regard to the preview functions. - -For example, if calling `deposit(100, receiver)`, the caller should deposit exactly 100 underlying tokens, including fees, and the receiver should receive a number of shares that matches the value returned by `previewDeposit(100)`. Similarly, `previewMint` should account for the fees that the user will have to pay on top of share's cost. - -As for the `Deposit` event, while this is less clear in the EIP spec itself, there seems to be consensus that it should include the number of assets paid for by the user, including the fees. - -On the other hand, when withdrawing assets, the number given by the user should correspond to what he receives. Any fees should be added to the quote (in shares) performed by `previewWithdraw`. - -The `Withdraw` event should include the number of shares the user burns (including fees) and the number of assets the user actually receives (after fees are deducted). - -The consequence of this design is that both the `Deposit` and `Withdraw` events will describe two exchange rates. The spread between the "Buy-in" and the "Exit" prices correspond to the fees taken by the vault. - -The following example describes how fees proportional to the deposited/withdrawn amount can be implemented: - -```solidity -include::api:example$ERC4626Fees.sol[] -``` diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc721.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc721.adoc deleted file mode 100644 index 4b784db..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/erc721.adoc +++ /dev/null @@ -1,58 +0,0 @@ -= ERC-721 - -We've discussed how you can make a _fungible_ token using xref:erc20.adoc[ERC-20], but what if not all tokens are alike? This comes up in situations like *real estate*, *voting rights*, or *collectibles*, where some items are valued more than others, due to their usefulness, rarity, etc. ERC-721 is a standard for representing ownership of xref:tokens.adoc#different-kinds-of-tokens[_non-fungible_ tokens], that is, where each token is unique. - -ERC-721 is a more complex standard than ERC-20, with multiple optional extensions, and is split across a number of contracts. The OpenZeppelin Contracts provide flexibility regarding how these are combined, along with custom useful extensions. Check out the xref:api:token/ERC721.adoc[API Reference] to learn more about these. - -== Constructing an ERC-721 Token Contract - -We'll use ERC-721 to track items in our game, which will each have their own unique attributes. Whenever one is to be awarded to a player, it will be minted and sent to them. Players are free to keep their token or trade it with other people as they see fit, as they would any other asset on the blockchain! Please note any account can call `awardItem` to mint items. To restrict what accounts can mint items we can add xref:access-control.adoc[Access Control]. - -Here's what a contract for tokenized items might look like: - -[source,solidity] ----- -include::api:example$token/ERC721/GameItem.sol[] ----- - -The xref:api:token/ERC721.adoc#ERC721URIStorage[`ERC721URIStorage`] contract is an implementation of ERC-721 that includes the metadata standard extensions (xref:api:token/ERC721.adoc#IERC721Metadata[`IERC721Metadata`]) as well as a mechanism for per-token metadata. That's where the xref:api:token/ERC721.adoc#ERC721-_setTokenURI-uint256-string-[`_setTokenURI`] method comes from: we use it to store an item's metadata. - -Also note that, unlike ERC-20, ERC-721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. - -New items can be created: - -[source,javascript] ----- -> gameItem.awardItem(playerAddress, "https://game.example/item-id-8u5h2m.json") -Transaction successful. Transaction hash: 0x... -Events emitted: - - Transfer(0x0000000000000000000000000000000000000000, playerAddress, 7) ----- - -And the owner and metadata of each item queried: - -[source,javascript] ----- -> gameItem.ownerOf(7) -playerAddress -> gameItem.tokenURI(7) -"https://game.example/item-id-8u5h2m.json" ----- - -This `tokenURI` should resolve to a JSON document that might look something like: - -[source,json] ----- -{ - "name": "Thor's hammer", - "description": "Mjölnir, the legendary hammer of the Norse god of thunder.", - "image": "https://game.example/item-id-8u5h2m.png", - "strength": 20 -} ----- - -For more information about the `tokenURI` metadata JSON Schema, check out the https://eips.ethereum.org/EIPS/eip-721[ERC-721 specification]. - -NOTE: You'll notice that the item's information is included in the metadata, but that information isn't on-chain! So a game developer could change the underlying metadata, changing the rules of the game! - -TIP: If you'd like to put all item information on-chain, you can extend ERC-721 to do so (though it will be rather costly) by providing a xref:utilities.adoc#base64[`Base64`] Data URI with the JSON schema encoded. You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/extending-contracts.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/extending-contracts.adoc deleted file mode 100644 index 1cdc0d7..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/extending-contracts.adoc +++ /dev/null @@ -1,51 +0,0 @@ -= Extending Contracts - -Most of the OpenZeppelin Contracts are expected to be used via https://solidity.readthedocs.io/en/latest/contracts.html#inheritance[inheritance]: you will _inherit_ from them when writing your own contracts. - -This is the commonly found `is` syntax, like in `contract MyToken is ERC20`. - -[NOTE] -==== -Unlike ``contract``s, Solidity ``library``s are not inherited from and instead rely on the https://solidity.readthedocs.io/en/latest/contracts.html#using-for[`using for`] syntax. - -OpenZeppelin Contracts has some ``library``s: most are in the xref:api:utils.adoc[Utils] directory. -==== - -== Overriding - -Inheritance is often used to add the parent contract's functionality to your own contract, but that's not all it can do. You can also _change_ how some parts of the parent behave using _overrides_. - -For example, imagine you want to change xref:api:access.adoc#AccessControl[`AccessControl`] so that xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[`revokeRole`] can no longer be called. This can be achieved using overrides: - -```solidity -include::api:example$access-control/AccessControlModified.sol[] -``` - -The old `revokeRole` is then replaced by our override, and any calls to it will immediately revert. We cannot _remove_ the function from the contract, but reverting on all calls is good enough. - -=== Calling `super` - -Sometimes you want to _extend_ a parent's behavior, instead of outright changing it to something else. This is where `super` comes in. - -The `super` keyword will let you call functions defined in a parent contract, even if they are overridden. This mechanism can be used to add additional checks to a function, emit events, or otherwise add functionality as you see fit. - -TIP: For more information on how overrides work, head over to the https://solidity.readthedocs.io/en/latest/contracts.html#index-17[official Solidity documentation]. - -Here is a modified version of xref:api:access.adoc#AccessControl[`AccessControl`] where xref:api:access.adoc#AccessControl-revokeRole-bytes32-address-[`revokeRole`] cannot be used to revoke the `DEFAULT_ADMIN_ROLE`: - - -```solidity -include::api:example$access-control/AccessControlNonRevokableAdmin.sol[] -``` - -The `super.revokeRole` statement at the end will invoke ``AccessControl``'s original version of `revokeRole`, the same code that would've run if there were no overrides in place. - -NOTE: The same rule is implemented and extended in xref:api:access.adoc#AccessControlDefaultAdminRules[`AccessControlDefaultAdminRules`], an extension that also adds enforced security measures for the `DEFAULT_ADMIN_ROLE`. - -== Security - -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. - -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. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/faq.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/faq.adoc deleted file mode 100644 index 81c34bb..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/faq.adoc +++ /dev/null @@ -1,13 +0,0 @@ -= Frequently Asked Questions - -== Can I restrict a function to EOAs only? - -When calling external addresses from your contract it is unsafe to assume that an address is an externally-owned account (EOA) and not a contract. Attempting to prevent calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract constructor. - -Although checking that the address has code, `address.code.length > 0`, may seem to differentiate contracts from EOAs, it can only say that an address is currently a contract, and its negation (that an address is not currently a contract) does not imply that the address is an EOA. Some counterexamples are: - - - address of a contract in construction - - address where a contract will be created - - address where a contract lived, but was destroyed - -Furthermore, an address will be considered a contract within the same transaction where it is scheduled for destruction by `SELFDESTRUCT`, which only has an effect at the end of the entire transaction. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/governance.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/governance.adoc deleted file mode 100644 index d2ff3cd..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/governance.adoc +++ /dev/null @@ -1,239 +0,0 @@ -= How to set up on-chain governance - -In this guide we will learn how OpenZeppelin’s Governor contract works, how to set it up, and how to use it to create proposals, vote for them, and execute them, using tools provided by Ethers.js and Tally. - -NOTE: Find detailed contract documentation at xref:api:governance.adoc[Governance API]. - -== Introduction - -Decentralized protocols are in constant evolution from the moment they are publicly released. Often, the initial team retains control of this evolution in the first stages, but eventually delegates it to a community of stakeholders. The process by which this community makes decisions is called on-chain governance, and it has become a central component of decentralized protocols, fueling varied decisions such as parameter tweaking, smart contract upgrades, integrations with other protocols, treasury management, grants, etc. - -This governance protocol is generally implemented in a special-purpose contract called “Governor”. The GovernorAlpha and GovernorBravo contracts designed by Compound have been very successful and popular so far, with the downside that projects with different requirements have had to fork the code to customize it for their needs, which can pose a high risk of introducing security issues. For OpenZeppelin Contracts, we set out to build a modular system of Governor contracts so that forking is not needed, and different requirements can be accommodated by writing small modules using Solidity inheritance. You will find the most common requirements out of the box in OpenZeppelin Contracts, but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. Additionally, the design of OpenZeppelin Governor requires minimal use of storage and results in more gas efficient operation. - -== Compatibility - -OpenZeppelin’s Governor system was designed with a concern for compatibility with existing systems that were based on Compound’s GovernorAlpha and GovernorBravo. Because of this, you will find that many modules are presented in two variants, one of which is built for compatibility with those systems. - -=== ERC20Votes & ERC20VotesComp - -The ERC-20 extension to keep track of votes and vote delegation is one such case. The shorter one is the more generic version because it can support token supplies greater than 2^96, while the “Comp” variant is limited in that regard, but exactly fits the interface of the COMP token that is used by GovernorAlpha and Bravo. Both contract variants share the same events, so they are fully compatible when looking at events only. - -=== Governor & GovernorStorage - -An OpenZeppelin Governor contract is not interface-compatible with Compound's GovernorAlpha or Bravo. Even though events are fully compatible, proposal lifecycle functions (creation, execution, etc.) have different signatures that are meant to optimize storage use. Other functions from GovernorAlpha are Bravo are likewise not available. It’s possible to opt in some Bravo-like behavior by inheriting from the GovernorStorage module. This module provides proposal enumerability and alternate versions of the `queue`, `execute` and `cancel` function that only take the proposal id. This module reduces the calldata needed by some operations in exchange for an increased the storage footprint. This might be a good trade-off for some L2 chains. It also provides primitives for indexer-free frontends. - -Note that even with the use of this module, one important difference with Compound's GovernorBravo is the way that `proposalId`s are calculated. Governor uses the hash of the proposal parameters with the purpose of keeping its data off-chain by event indexing, while the original Bravo implementation uses sequential `proposalId`s. - -=== GovernorTimelockControl & GovernorTimelockCompound - -When using a timelock with your Governor contract, you can use either OpenZeppelin’s TimelockController or Compound’s Timelock. Based on the choice of timelock, you should choose the corresponding Governor module: GovernorTimelockControl or GovernorTimelockCompound respectively. This allows you to migrate an existing GovernorAlpha instance to an OpenZeppelin-based Governor without changing the timelock in use. - -=== Tally - -https://www.tally.xyz[Tally] is a full-fledged application for user owned on-chain governance. It comprises a voting dashboard, proposal creation wizard, real time research and analysis, and educational content. - -For all of these options, the Governor will be compatible with Tally: users will be able to create proposals, see voting periods and delays following xref:api:interfaces.adoc#IERC6372[IERC6372], visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use https://docs.openzeppelin.com/defender/module/actions#transaction-proposals-reference[Defender Transaction Proposals] as an alternative interface. - -In the rest of this guide, we will focus on a fresh deploy of the vanilla OpenZeppelin Governor features without concern for compatibility with GovernorAlpha or Bravo. - -== Setup - -=== Token - -The voting power of each account in our governance setup will be determined by an ERC-20 token. The token has to implement the ERC20Votes extension. This extension will keep track of historical balances so that voting power is retrieved from past snapshots rather than current balance, which is an important protection that prevents double voting. - -```solidity -include::api:example$governance/MyToken.sol[] -``` - -If your project already has a live token that does not include ERC20Votes and is not upgradeable, you can wrap it in a governance token by using ERC20Wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. - -```solidity -include::api:example$governance/MyTokenWrapped.sol[] -``` - -NOTE: The only other source of voting power available in OpenZeppelin Contracts currently is xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]. ERC-721 tokens that don't provide this functionality can be wrapped into a voting tokens using a combination of xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`] and xref:api:token/ERC721Wrapper.adoc#ERC721Wrapper[`ERC721Wrapper`]. - -NOTE: The internal clock used by the token to store voting balances will dictate the operating mode of the Governor contract attached to it. By default, block numbers are used. Since v4.9, developers can override the xref:api:interfaces.adoc#IERC6372[IERC6372] clock to use timestamps instead of block numbers. - -=== Governor - -Initially, we will build a Governor without a timelock. The core logic is given by the Governor contract, but we still need to choose: 1) how voting power is determined, 2) how many votes are needed for quorum, 3) what options people have when casting a vote and how those votes are counted, and 4) what type of token should be used to vote. Each of these aspects is customizable by writing your own module, or more easily choosing one from OpenZeppelin Contracts. - -For 1) we will use the GovernorVotes module, which hooks to an IVotes instance to determine the voting power of an account based on the token balance they hold when a proposal becomes active. This module requires as a constructor parameter the address of the token. This module also discovers the clock mode (ERC-6372) used by the token and applies it to the Governor. - -For 2) we will use GovernorVotesQuorumFraction which works together with ERC20Votes to define quorum as a percentage of the total supply at the block a proposal’s voting power is retrieved. This requires a constructor parameter to set the percentage. Most Governors nowadays use 4%, so we will initialize the module with parameter 4 (this indicates the percentage, resulting in 4%). - -For 3) we will use GovernorCountingSimple, a module that offers 3 options to voters: For, Against, and Abstain, and where only For and Abstain votes are counted towards quorum. - -Besides these modules, Governor itself has some parameters we must set. - -votingDelay: How long after a proposal is created should voting power be fixed. A large voting delay gives users time to unstake tokens if necessary. - -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. - -```solidity -include::api:example$governance/MyGovernor.sol[] -``` - -=== Timelock - -It is good practice to add a timelock to governance decisions. This allows users to exit the system if they disagree with a decision before it is executed. We will use OpenZeppelin’s TimelockController in combination with the GovernorTimelockControl module. - -IMPORTANT: When using a timelock, it is the timelock that will execute proposals and thus the timelock that should hold any funds, ownership, and access control roles. Before version 4.5 there was no way to recover funds in the Governor contract when using a timelock! Before version 4.3, when using the Compound Timelock, ETH in the timelock was not easily accessible. - -TimelockController uses an AccessControl setup that we need to understand in order to set up roles. - -- The Proposer role is in charge of queueing operations: this is the role the Governor instance should be granted, and it should likely be the only proposer in the system. -- The Executor role is in charge of executing already available operations: we can assign this role to the special zero address to allow anyone to execute (if operations can be particularly time sensitive, the Governor should be made Executor instead). -- Lastly, there is the Admin role, which can grant and revoke the two previous roles: this is a very sensitive role that will be granted automatically to the timelock itself, and optionally to a second account, which can be used for ease of setup but should promptly renounce the role. - -== Proposal Lifecycle - -Let’s walk through how to create and execute a proposal on our newly deployed Governor. - -A proposal is a sequence of actions that the Governor contract will perform if it passes. Each action consists of a target address, calldata encoding a function call, and an amount of ETH to include. Additionally, a proposal includes a human-readable description. - -=== Create a Proposal - -Let’s say we want to create a proposal to give a team a grant, in the form of ERC-20 tokens from the governance treasury. This proposal will consist of a single action where the target is the ERC-20 token, calldata is the encoded function call `transfer(, )`, and with 0 ETH attached. - -Generally a proposal will be created with the help of an interface such as Tally or https://docs.openzeppelin.com/defender/module/actions#transaction-proposals-reference[Defender Proposals]. Here we will show how to create the proposal using Ethers.js. - -First we get all the parameters necessary for the proposal action. - -```javascript -const tokenAddress = ...; -const token = await ethers.getContractAt(‘ERC20’, tokenAddress); - -const teamAddress = ...; -const grantAmount = ...; -const transferCalldata = token.interface.encodeFunctionData(‘transfer’, [teamAddress, grantAmount]); -``` - -Now we are ready to call the propose function of the Governor. Note that we don’t pass in one array of actions, but instead three arrays corresponding to the list of targets, the list of values, and the list of calldatas. In this case it’s a single action, so it’s simple: - -```javascript -await governor.propose( - [tokenAddress], - [0], - [transferCalldata], - “Proposal #1: Give grant to team”, -); -``` - -This will create a new proposal, with a proposal id that is obtained by hashing together the proposal data, and which will also be found in an event in the logs of the transaction. - -=== Cast a Vote - -Once a proposal is active, delegates can cast their vote. Note that it is delegates who carry voting power: if a token holder wants to participate, they can set a trusted representative as their delegate, or they can become a delegate themselves by self-delegating their voting power. - -Votes are cast by interacting with the Governor contract through the `castVote` family of functions. Voters would generally invoke this from a governance UI such as Tally. - -image::tally-vote.png[Voting in Tally] - -=== Execute the Proposal - -Once the voting period is over, if quorum was reached (enough voting power participated) and the majority voted in favor, the proposal is considered successful and can proceed to be executed. Once a proposal passes, it can be queued and executed from the same place you voted. - -image::tally-exec.png[Administration Panel in Tally] - -We will see now how to do this manually using Ethers.js. - -If a timelock was set up, the first step to execution is queueing. You will notice that both the queue and execute functions require passing in the entire proposal parameters, as opposed to just the proposal id. This is necessary because this data is not stored on chain, as a measure to save gas. Note that these parameters can always be found in the events emitted by the contract. The only parameter that is not sent in its entirety is the description, since this is only needed in its hashed form to compute the proposal id. - -To queue, we call the queue function: - -```javascript -const descriptionHash = ethers.utils.id(“Proposal #1: Give grant to team”); - -await governor.queue( - [tokenAddress], - [0], - [transferCalldata], - descriptionHash, -); -``` - -This will cause the Governor to interact with the timelock contract and queue the actions for execution after the required delay. - -After enough time has passed (according to the timelock parameters), the proposal can be executed. If there was no timelock to begin with, this step can be ran immediately after the proposal succeeds. - -```javascript -await governor.execute( - [tokenAddress], - [0], - [transferCalldata], - descriptionHash, -); -``` - -Executing the proposal will transfer the ERC-20 tokens to the chosen recipient. To wrap up: we set up a system where a treasury is controlled by the collective decision of the token holders of a project, and all actions are executed via proposals enforced by on-chain votes. - -== Timestamp based governance - -=== Motivation - -It is sometimes difficult to deal with durations expressed in number of blocks because of inconsistent or unpredictable time between blocks. This is particularly true of some L2 networks where blocks are produced based on blockchain usage. Using number of blocks can also lead to the governance rules being affected by network upgrades that modify the expected time between blocks. - -The difficulty of replacing block numbers with timestamps is that the Governor and the token must both use the same format when querying past votes. If a token is designed around block numbers, it is not possible for a Governor to reliably do timestamp based lookups. - -Therefore, designing a timestamp based voting system starts with the token. - -=== Token - -Since v4.9, all voting contracts (including xref:api:token/ERC20.adoc#ERC20Votes[`ERC20Votes`] and xref:api:token/ERC721.adoc#ERC721Votes[`ERC721Votes`]) rely on xref:api:interfaces.adoc#IERC6372[IERC6372] for clock management. In order to change from operating with block numbers to operating with timestamps, all that is required is to override the `clock()` and `CLOCK_MODE()` functions. - -```solidity -include::api:example$governance/MyTokenTimestampBased.sol[] -``` - -=== Governor - -The Governor will automatically detect the clock mode used by the token and adapt to it. There is no need to override anything in the Governor contract. However, the clock mode does affect how some values are interpreted. It is therefore necessary to set the `votingDelay()` and `votingPeriod()` accordingly. - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; -import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/compatibility/GovernorCountingSimple.sol"; -import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; -import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; -import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; -import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; -import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol"; - -contract MyGovernor is Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl { - constructor(IVotes _token, TimelockController _timelock) - Governor("MyGovernor") - GovernorVotes(_token) - GovernorVotesQuorumFraction(4) - GovernorTimelockControl(_timelock) - {} - - function votingDelay() public pure virtual override returns (uint256) { - return 1 days; - } - - function votingPeriod() public pure virtual override returns (uint256) { - return 1 weeks; - } - - function proposalThreshold() public pure virtual override returns (uint256) { - return 0; - } - - // ... -} -``` - -=== Disclaimer - -Timestamp based voting is a recent feature that was formalized in ERC-6372 and ERC-5805, and introduced in v4.9. At the time this feature is released, some governance tooling may not support it yet. Users can expect invalid reporting of deadlines & durations if the tool is not able to interpret the ERC6372 clock. This invalid reporting by offchain tools does not affect the onchain security and functionality of the governance contract. - -Governors with timestamp support (v4.9 and above) are compatible with old tokens (before v4.9) and will operate in "block number" mode (which is the mode all old tokens operate on). On the other hand, old Governor instances (before v4.9) are not compatible with new tokens operating using timestamps. If you update your token code to use timestamps, make sure to also update your Governor code. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/index.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/index.adoc deleted file mode 100644 index 1ba4495..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/index.adoc +++ /dev/null @@ -1,70 +0,0 @@ -= Contracts - -*A library for secure smart contract development.* Build on a solid foundation of community-vetted code. - - * Implementations of standards like xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721]. - * Flexible xref:access-control.adoc[role-based permissioning] scheme. - * Reusable xref:utilities.adoc[Solidity components] to build custom contracts and complex decentralized systems. - -IMPORTANT: OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. For upgradeable contracts, the storage layout of different major versions should be assumed incompatible, for example, it is unsafe to upgrade from 4.9.3 to 5.0.0. Learn more at xref:backwards-compatibility.adoc[Backwards Compatibility]. - -== Overview - -[[install]] -=== Installation - -==== Hardhat (npm) - -```console -$ npm install @openzeppelin/contracts -``` - -==== Foundry (git) - -WARNING: When installing via git, it is a common error to use the `master` branch. This is a development branch that should be avoided in favor of tagged releases. The release process involves security measures that the `master` branch does not guarantee. - -WARNING: Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch. - -```console -$ forge install OpenZeppelin/openzeppelin-contracts -``` - -Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.` - -[[usage]] -=== Usage - -Once installed, you can use the contracts in the library by importing them: - -[source,solidity] ----- -include::api:example$MyNFT.sol[] ----- - -TIP: If you're new to smart contract development, head to xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] to learn about creating a new project and compiling your contracts. - -To keep your system secure, you should **always** use the installed code as-is, and neither copy-paste it from online sources, nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs. - -[[security]] -== Security - -Please report any security issues you find via our https://www.immunefi.com/bounty/openzeppelin[bug bounty program on Immunefi] or directly to security@openzeppelin.org. - -The https://contracts.openzeppelin.com/security[Security Center] contains more details about the secure development process. - -[[next-steps]] -== Learn More - -The guides in the sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides: - -* xref:access-control.adoc[Access Control]: decide who can perform each of the actions on your system. -* xref:tokens.adoc[Tokens]: create tradable assets or collectibles, like the well known xref:erc20.adoc[ERC20] and xref:erc721.adoc[ERC721] standards. -* xref:utilities.adoc[Utilities]: generic useful tools, including non-overflowing math, signature verification, and trustless paying systems. - -The xref:api:token/ERC20.adoc[full API] is also thoroughly documented, and serves as a great reference when developing your smart contract application. You can also ask for help or follow Contracts' development in the https://forum.openzeppelin.com[community forum]. - -Finally, you may want to take a look at the https://blog.openzeppelin.com/guides/[guides on our blog], which cover several common use cases and good practices. The following articles provide great background reading, though please note, some of the referenced tools have changed as the tooling in the ecosystem continues to rapidly evolve. - -* https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05[The Hitchhiker’s Guide to Smart Contracts in Ethereum] will help you get an overview of the various tools available for smart contract development, and help you set up your environment. -* https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094[A Gentle Introduction to Ethereum Programming, Part 1] provides very useful information on an introductory level, including many basic concepts from the Ethereum platform. -* For a more in-depth dive, you may read the guide https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317[Designing the architecture for your Ethereum application], which discusses how to better structure your application and its relationship to the real world. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/tokens.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/tokens.adoc deleted file mode 100644 index 217c5e0..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/tokens.adoc +++ /dev/null @@ -1,31 +0,0 @@ -= Tokens - -Ah, the "token": blockchain's most powerful and most misunderstood tool. - -A token is a _representation of something in the blockchain_. This something can be money, time, services, shares in a company, a virtual pet, anything. By representing things as tokens, we can allow smart contracts to interact with them, exchange them, create or destroy them. - -[[but_first_coffee_a_primer_on_token_contracts]] -== But First, [strikethrough]#Coffee# a Primer on Token Contracts - -Much of the confusion surrounding tokens comes from two concepts getting mixed up: _token contracts_ and the actual _tokens_. - -A _token contract_ is simply an Ethereum smart contract. "Sending tokens" actually means "calling a method on a smart contract that someone wrote and deployed". At the end of the day, a token contract is not much more than a mapping of addresses to balances, plus some methods to add and subtract from those balances. - -It is these balances that represent the _tokens_ themselves. Someone "has tokens" when their balance in the token contract is non-zero. That's it! These balances could be considered money, experience points in a game, deeds of ownership, or voting rights, and each of these tokens would be stored in different token contracts. - -[[different-kinds-of-tokens]] -== Different Kinds of Tokens - -Note that there's a big difference between having two voting rights and two deeds of ownership: each vote is equal to all others, but houses usually are not! This is called https://en.wikipedia.org/wiki/Fungibility[fungibility]. _Fungible goods_ are equivalent and interchangeable, like Ether, fiat currencies, and voting rights. _Non-fungible_ goods are unique and distinct, like deeds of ownership, or collectibles. - -In a nutshell, when dealing with non-fungibles (like your house) you care about _which ones_ you have, while in fungible assets (like your bank account statement) what matters is _how much_ you have. - -== Standards - -Even though the concept of a token is simple, they have a variety of complexities in the implementation. Because everything in Ethereum is just a smart contract, and there are no rules about what smart contracts have to do, the community has developed a variety of *standards* (called EIPs or ERCs) for documenting how a contract can interoperate with other contracts. - -You've probably heard of the ERC-20 or ERC-721 token standards, and that's why you're here. Head to our specialized guides to learn more about these: - - * xref:erc20.adoc[ERC-20]: the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. - * xref:erc721.adoc[ERC-721]: the de-facto solution for non-fungible tokens, often used for collectibles and games. - * xref:erc1155.adoc[ERC-1155]: a novel standard for multi-tokens, allowing for a single contract to represent multiple fungible and non-fungible tokens, along with batched operations for increased gas efficiency. diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/upgradeable.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/upgradeable.adoc deleted file mode 100644 index 6d252d8..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/upgradeable.adoc +++ /dev/null @@ -1,77 +0,0 @@ -= Using with Upgrades - -If your contract is going to be deployed with upgradeability, such as using the xref:upgrades-plugins::index.adoc[OpenZeppelin Upgrades Plugins], you will need to use the Upgradeable variant of OpenZeppelin Contracts. - -This variant is available as a separate package called `@openzeppelin/contracts-upgradeable`, which is hosted in the repository https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable[OpenZeppelin/openzeppelin-contracts-upgradeable]. It uses `@openzeppelin/contracts` as a peer dependency. - -It follows all of the rules for xref:upgrades-plugins::writing-upgradeable.adoc[Writing Upgradeable Contracts]: constructors are replaced by initializer functions, state variables are initialized in initializer functions, and we additionally check for storage incompatibilities across minor versions. - -TIP: OpenZeppelin provides a full suite of tools for deploying and securing upgradeable smart contracts. xref:openzeppelin::upgrades.adoc[Check out the full list of resources]. - -== Overview - -=== Installation - -```console -$ npm install @openzeppelin/contracts-upgradeable @openzeppelin/contracts -``` - -=== Usage - -The Upgradeable package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix `Upgradeable`. - -```diff --import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -+import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; - --contract MyCollectible is ERC721 { -+contract MyCollectible is ERC721Upgradeable { -``` - -NOTE: Interfaces and libraries are not included in the Upgradeable package, but are instead imported from the main OpenZeppelin Contracts package. - -Constructors are replaced by internal initializer functions following the naming convention `+__{ContractName}_init+`. Since these are internal, you must always define your own public initializer function and call the parent initializer of the contract you extend. - -```diff -- constructor() ERC721("MyCollectible", "MCO") public { -+ function initialize() initializer public { -+ __ERC721_init("MyCollectible", "MCO"); - } -``` - -CAUTION: Use with multiple inheritance requires special attention. See the section below titled <>. - -Once this contract is set up and compiled, you can deploy it using the xref:upgrades-plugins::index.adoc[Upgrades Plugins]. The following snippet shows an example deployment script using Hardhat. - -```js -// scripts/deploy-my-collectible.js -const { ethers, upgrades } = require("hardhat"); - -async function main() { - const MyCollectible = await ethers.getContractFactory("MyCollectible"); - - const mc = await upgrades.deployProxy(MyCollectible); - - await mc.waitForDeployment(); - console.log("MyCollectible deployed to:", await mc.getAddress()); -} - -main(); -``` - -== Further Notes - -[[multiple-inheritance]] -=== Multiple Inheritance - -Initializer functions are not linearized by the compiler like constructors. Because of this, each `+__{ContractName}_init+` function embeds the linearized calls to all parent initializers. As a consequence, calling two of these `init` functions can potentially initialize the same contract twice. - -The function `+__{ContractName}_init_unchained+` found in every contract is the initializer function minus the calls to parent initializers, and can be used to avoid the double initialization problem, but doing this manually is not recommended. We hope to be able to implement safety checks for this in future versions of the Upgrades Plugins. - -=== Namespaced Storage - -You may notice that contracts use a struct with the `@custom:storage-location erc7201:` annotation to store the contract's state variables. This follows the https://eips.ethereum.org/EIPS/eip-7201[ERC-7201: Namespaced Storage Layout] pattern, where each contract has its own storage layout in a namespace that is separate from other contracts in the inheritance chain. - -Without namespaced storage, it isn't safe to simply add a state variable because it "shifts down" all of the state variables below in the inheritance chain. This makes the storage layouts incompatible, as explained in xref:upgrades-plugins::writing-upgradeable.adoc#modifying-your-contracts[Writing Upgradeable Contracts]. - -The namespaced storage pattern used in the Upgradeable package allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments. It also allows changing the inheritance order with no impact on the resulting storage layout, as long as all inherited contracts use namespaced storage. \ No newline at end of file diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/utilities.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/utilities.adoc deleted file mode 100644 index c82a15c..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/utilities.adoc +++ /dev/null @@ -1,235 +0,0 @@ -= Utilities - -The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. For a complete list, check out the xref:api:utils.adoc[API Reference]. -Here are some of the more popular ones. - -[[cryptography]] -== Cryptography - -=== Checking Signatures On-Chain - -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)]]`. - -The data signer can be recovered with xref:api:utils.adoc#ECDSA-recover-bytes32-bytes-[`ECDSA.recover`], and its address compared to verify the signature. Most wallets will hash the data to sign and add the prefix `\x19Ethereum Signed Message:\n`, so when attempting to recover the signer of an Ethereum signed message hash, you'll want to use xref:api:utils.adoc#MessageHashUtils-toEthSignedMessageHash-bytes32-[`toEthSignedMessageHash`]. - -[source,solidity] ----- -using ECDSA for bytes32; -using MessageHashUtils for bytes32; - -function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) { - return data - .toEthSignedMessageHash() - .recover(signature) == account; -} ----- - -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. - -=== 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. - -TIP: OpenZeppelin Contracts provides a https://github.com/OpenZeppelin/merkle-tree[JavaScript library] for building trees off-chain and generating proofs. - -xref:api:utils.adoc#MerkleProof[`MerkleProof`] provides: - -* xref:api:utils.adoc#MerkleProof-verify-bytes32---bytes32-bytes32-[`verify`] - can prove that some value is part of a https://en.wikipedia.org/wiki/Merkle_tree[Merkle tree]. - -* xref:api:utils.adoc#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---[`multiProofVerify`] - can prove multiple values are part of a Merkle tree. - -For an on-chain Merkle Tree, see the xref:api:utils.adoc#MerkleTree[`MerkleTree`] library. - -[[introspection]] -== Introspection - -In Solidity, it's frequently helpful to know whether or not a contract supports an interface you'd like to use. ERC-165 is a standard that helps do runtime interface detection. Contracts provide helpers both for implementing ERC-165 in your contracts and querying other contracts: - -* xref:api:utils.adoc#IERC165[`IERC165`] — this is the ERC-165 interface that defines xref:api:utils.adoc#IERC165-supportsInterface-bytes4-[`supportsInterface`]. When implementing ERC-165, you'll conform to this interface. -* xref:api:utils.adoc#ERC165[`ERC165`] — inherit this contract if you'd like to support interface detection using a lookup table in contract storage. You can register interfaces using xref:api:utils.adoc#ERC165-_registerInterface-bytes4-[`_registerInterface(bytes4)`]: check out example usage as part of the ERC-721 implementation. -* xref:api:utils.adoc#ERC165Checker[`ERC165Checker`] — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about. -* include with `using ERC165Checker for address;` -* xref:api:utils.adoc#ERC165Checker-_supportsInterface-address-bytes4-[`myAddress._supportsInterface(bytes4)`] -* xref:api:utils.adoc#ERC165Checker-_supportsAllInterfaces-address-bytes4---[`myAddress._supportsAllInterfaces(bytes4[\])`] - -[source,solidity] ----- -contract MyContract { - using ERC165Checker for address; - - bytes4 private InterfaceId_ERC721 = 0x80ac58cd; - - /** - * @dev transfer an ERC-721 token from this contract to someone else - */ - function transferERC721( - address token, - address to, - uint256 tokenId - ) - public - { - require(token.supportsInterface(InterfaceId_ERC721), "IS_NOT_721_TOKEN"); - IERC721(token).transferFrom(address(this), to, tokenId); - } -} ----- - -[[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. - -Include these contracts with `using Math for uint256` or `using SignedMath for int256` and then use their functions in your code: - -[source,solidity] ----- -contract MyContract { - using Math for uint256; - using SignedMath for int256; - - function tryOperations(uint256 a, uint256 b) internal pure { - (bool succeededAdd, uint256 resultAdd) = x.tryAdd(y); - (bool succeededSub, uint256 resultSub) = x.trySub(y); - (bool succeededMul, uint256 resultMul) = x.tryMul(y); - (bool succeededDiv, uint256 resultDiv) = x.tryDiv(y); - // ... - } - - function unsignedAverage(int256 a, int256 b) { - int256 avg = a.average(b); - // ... - } -} ----- - -Easy! - -TIP: While working with different data types that might require casting, you can use xref:api:utils.adoc#SafeCast[`SafeCast`] for type casting with added overflow checks. - -[[structures]] -== Structures - -Some use cases require more powerful data structures than arrays and mappings offered natively in Solidity. Contracts provides these libraries for enhanced data structure management: - -- xref:api:utils.adoc#BitMaps[`BitMaps`]: Store packed booleans in storage. -- xref:api:utils.adoc#Checkpoints[`Checkpoints`]: Checkpoint values with built-in lookups. -- xref:api:utils.adoc#DoubleEndedQueue[`DoubleEndedQueue`]: Store items in a queue with `pop()` and `queue()` constant time operations. -- 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. - -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. - -=== 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). - -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: - -[source,solidity] ----- -// NOTE: Functions are exposed without access control for demonstration purposes - -using MerkleTree for MerkleTree.Bytes32PushTree; -MerkleTree.Bytes32PushTree private _tree; - -function setup(uint8 _depth, bytes32 _zero) public /* onlyOwner */ { - root = _tree.setup(_depth, _zero); -} - -function push(bytes32 leaf) public /* onlyOwner */ { - (uint256 leafIndex, bytes32 currentRoot) = _tree.push(leaf); - // Store the new root. -} ----- - -[[misc]] -== Misc - -=== Storage Slots - -Solidity allocates a storage pointer for each variable declared in a contract. However, there are cases when it's required to access storage pointers that can't be derived by using regular Solidity. -For those cases, the xref:api:utils.adoc#StorageSlot[`StorageSlot`] library allows for manipulating storage slots directly. - -[source,solidity] ----- -bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - -function _getImplementation() internal view returns (address) { - return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; -} - -function _setImplementation(address newImplementation) internal { - require(newImplementation.code.length > 0); - StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; -} ----- - -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. - -[source,solidity] ----- -bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; - -function _getTransientLock() internal view returns (bool) { - return _LOCK_SLOT.asBoolean().tload(); -} - -function _setTransientLock(bool lock) internal { - _LOCK_SLOT.asBoolean().tstore(lock); -} ----- - -WARNING: Manipulating storage slots directly is an advanced practice. Developers MUST make sure that the storage pointer is not colliding with other variables. - -One of the most common use cases for writing directly to storage slots is ERC-7201 for namespaced storage, which is guaranteed to not collide with other storage slots derived by Solidity. - -Users can leverage this standard using the xref:api:utils.adoc#SlotDerivation[`SlotDerivation`] library. - -[source,solidity] ----- -using SlotDerivation for bytes32; -string private constant _NAMESPACE = "" // eg. example.main - -function erc7201Pointer() internal view returns (bytes32) { - return _NAMESPACE.erc7201Slot(); -} ----- - -=== Base64 - -xref:api:utils.adoc#Base64[`Base64`] util allows you to transform `bytes32` data into its Base64 `string` representation. - -This is especially useful for building URL-safe tokenURIs for both xref:api:token/ERC721.adoc#IERC721Metadata-tokenURI-uint256-[`ERC-721`] or xref:api:token/ERC1155.adoc#IERC1155MetadataURI-uri-uint256-[`ERC-1155`]. This library provides a clever way to serve URL-safe https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/[Data URI] compliant strings to serve on-chain data structures. - -Here is an example to send JSON Metadata through a Base64 Data URI using an ERC-721: - -[source, solidity] ----- -include::api:example$utilities/Base64NFT.sol[] ----- - -=== Multicall - -The `Multicall` abstract contract comes with a `multicall` function that bundles together multiple calls in a single external call. With it, external accounts may perform atomic operations comprising several function calls. This is not only useful for EOAs to make multiple calls in a single transaction, it's also a way to revert a previous call if a later one fails. - -Consider this dummy contract: - -[source,solidity] ----- -include::api:example$utilities/Multicall.sol[] ----- - -This is how to call the `multicall` function using Ethers.js, allowing `foo` and `bar` to be called in a single transaction: -[source,javascript] ----- -// scripts/foobar.js - -const instance = await ethers.deployContract("Box"); - -await instance.multicall([ - instance.interface.encodeFunctionData("foo"), - instance.interface.encodeFunctionData("bar") -]); ----- diff --git a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/wizard.adoc b/lib_openzeppelin_contracts/docs/modules/ROOT/pages/wizard.adoc deleted file mode 100644 index ed416e2..0000000 --- a/lib_openzeppelin_contracts/docs/modules/ROOT/pages/wizard.adoc +++ /dev/null @@ -1,15 +0,0 @@ -= Contracts Wizard -:page-notoc: - -Not sure where to start? Use the interactive generator below to bootstrap your -contract and learn about the components offered in OpenZeppelin Contracts. - -TIP: Place the resulting contract in your `contracts` or `src` directory in order to compile it with a tool like Hardhat or Foundry. Consider reading our guide on xref:learn::developing-smart-contracts.adoc[Developing Smart Contracts] for more guidance! - -++++ - - - -++++ - - diff --git a/lib_openzeppelin_contracts/docs/templates/contract.hbs b/lib_openzeppelin_contracts/docs/templates/contract.hbs deleted file mode 100644 index aaca0a3..0000000 --- a/lib_openzeppelin_contracts/docs/templates/contract.hbs +++ /dev/null @@ -1,137 +0,0 @@ -{{#each items}} -:{{name}}: pass:normal[xref:#{{anchor}}[`++{{name}}++`]] -{{/each}} - -[.contract] -[[{{anchor}}]] -=== `++{{name}}++` link:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v{{oz-version}}/{{__item_context.file.absolutePath}}[{github-icon},role=heading-link] - -[.hljs-theme-light.nopadding] -```solidity -import "@openzeppelin/{{__item_context.file.absolutePath}}"; -``` - -{{{natspec.dev}}} - -{{#if modifiers}} -[.contract-index] -.Modifiers --- -{{#each modifiers}} -* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] -{{/each}} --- -{{/if}} - -{{#if has-functions}} -[.contract-index] -.Functions --- -{{#each inherited-functions}} -{{#unless @first}} -[.contract-subindex-inherited] -.{{contract.name}} -{{/unless}} -{{#each functions}} -* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] -{{/each}} - -{{/each}} --- -{{/if}} - -{{#if has-events}} -[.contract-index] -.Events --- -{{#each inheritance}} -{{#unless @first}} -[.contract-subindex-inherited] -.{{name}} -{{/unless}} -{{#each events}} -* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] -{{/each}} - -{{/each}} --- -{{/if}} - -{{#if has-errors}} -[.contract-index] -.Errors --- -{{#each inheritance}} -{{#unless @first}} -[.contract-subindex-inherited] -.{{name}} -{{/unless}} -{{#each errors}} -* {xref-{{anchor~}} }[`++{{name}}({{names params}})++`] -{{/each}} - -{{/each}} --- -{{/if}} - -{{#if has-internal-variables}} -[.contract-index] -.Internal Variables --- -{{#each inheritance}} -{{#unless @first}} -[.contract-subindex-inherited] -.{{name}} -{{/unless}} -{{#each internal-variables}} -* {xref-{{anchor~}} }[`++{{typeDescriptions.typeString}} {{#if constant}}constant{{/if}} {{name}}++`] -{{/each}} - -{{/each}} --- -{{/if}} - -{{#each modifiers}} -[.contract-item] -[[{{anchor}}]] -==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}})++` [.item-kind]#modifier# - -{{{natspec.dev}}} - -{{/each}} - -{{#each functions}} -[.contract-item] -[[{{anchor}}]] -==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}}){{#if returns2}} → {{typed-params returns2}}{{/if}}++` [.item-kind]#{{visibility}}# - -{{{natspec.dev}}} - -{{/each}} - -{{#each events}} -[.contract-item] -[[{{anchor}}]] -==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}})++` [.item-kind]#event# - -{{{natspec.dev}}} - -{{/each}} - -{{#each errors}} -[.contract-item] -[[{{anchor}}]] -==== `[.contract-item-name]#++{{name}}++#++({{typed-params params}})++` [.item-kind]#error# - -{{{natspec.dev}}} - -{{/each}} - -{{#each internal-variables}} -[.contract-item] -[[{{anchor}}]] -==== `{{typeDescriptions.typeString}} [.contract-item-name]#++{{name}}++#` [.item-kind]#internal{{#if constant}} constant{{/if}}# - -{{{natspec.dev}}} - -{{/each}} diff --git a/lib_openzeppelin_contracts/docs/templates/helpers.js b/lib_openzeppelin_contracts/docs/templates/helpers.js deleted file mode 100644 index 1b63835..0000000 --- a/lib_openzeppelin_contracts/docs/templates/helpers.js +++ /dev/null @@ -1,46 +0,0 @@ -const { version } = require('../../package.json'); - -module.exports['oz-version'] = () => version; - -module.exports['readme-path'] = opts => { - return 'contracts/' + opts.data.root.id.replace(/\.adoc$/, '') + '/README.adoc'; -}; - -module.exports.names = params => params?.map(p => p.name).join(', '); - -module.exports['typed-params'] = params => { - return params?.map(p => `${p.type}${p.indexed ? ' indexed' : ''}${p.name ? ' ' + p.name : ''}`).join(', '); -}; - -const slug = (module.exports.slug = str => { - if (str === undefined) { - throw new Error('Missing argument'); - } - return str.replace(/\W/g, '-'); -}); - -const linksCache = new WeakMap(); - -function getAllLinks(items) { - if (linksCache.has(items)) { - return linksCache.get(items); - } - const res = {}; - linksCache.set(items, res); - for (const item of items) { - res[`xref-${item.anchor}`] = `xref:${item.__item_context.page}#${item.anchor}`; - res[slug(item.fullName)] = `pass:normal[xref:${item.__item_context.page}#${item.anchor}[\`${item.fullName}\`]]`; - } - return res; -} - -module.exports['with-prelude'] = opts => { - const links = getAllLinks(opts.data.site.items); - const contents = opts.fn(); - const neededLinks = contents - .match(/\{[-._a-z0-9]+\}/gi) - .map(m => m.replace(/^\{(.+)\}$/, '$1')) - .filter(k => k in links); - const prelude = neededLinks.map(k => `:${k}: ${links[k]}`).join('\n'); - return prelude + '\n' + contents; -}; diff --git a/lib_openzeppelin_contracts/docs/templates/page.hbs b/lib_openzeppelin_contracts/docs/templates/page.hbs deleted file mode 100644 index cab050a..0000000 --- a/lib_openzeppelin_contracts/docs/templates/page.hbs +++ /dev/null @@ -1,4 +0,0 @@ -:github-icon: pass:[] -{{#with-prelude}} -{{readme (readme-path)}} -{{/with-prelude}} diff --git a/lib_openzeppelin_contracts/docs/templates/properties.js b/lib_openzeppelin_contracts/docs/templates/properties.js deleted file mode 100644 index 52eebac..0000000 --- a/lib_openzeppelin_contracts/docs/templates/properties.js +++ /dev/null @@ -1,72 +0,0 @@ -const { isNodeType, findAll } = require('solidity-ast/utils'); -const { slug } = require('./helpers'); - -module.exports.anchor = function anchor({ item, contract }) { - let res = ''; - if (contract) { - res += contract.name + '-'; - } - res += item.name; - if ('parameters' in item) { - const signature = item.parameters.parameters.map(v => v.typeName.typeDescriptions.typeString).join(','); - res += slug('(' + signature + ')'); - } - if (isNodeType('VariableDeclaration', item)) { - res += '-' + slug(item.typeName.typeDescriptions.typeString); - } - return res; -}; - -module.exports.inheritance = function ({ item, build }) { - if (!isNodeType('ContractDefinition', item)) { - throw new Error('used inherited-items on non-contract'); - } - - return item.linearizedBaseContracts - .map(id => build.deref('ContractDefinition', id)) - .filter((c, i) => c.name !== 'Context' || i === 0); -}; - -module.exports['has-functions'] = function ({ item }) { - return item.inheritance.some(c => c.functions.length > 0); -}; - -module.exports['has-events'] = function ({ item }) { - return item.inheritance.some(c => c.events.length > 0); -}; - -module.exports['has-errors'] = function ({ item }) { - return item.inheritance.some(c => c.errors.length > 0); -}; - -module.exports['internal-variables'] = function ({ item }) { - return item.variables.filter(({ visibility }) => visibility === 'internal'); -}; - -module.exports['has-internal-variables'] = function ({ item }) { - return module.exports['internal-variables']({ item }).length > 0; -}; - -module.exports.functions = function ({ item }) { - return [ - ...[...findAll('FunctionDefinition', item)].filter(f => f.visibility !== 'private'), - ...[...findAll('VariableDeclaration', item)].filter(f => f.visibility === 'public'), - ]; -}; - -module.exports.returns2 = function ({ item }) { - if (isNodeType('VariableDeclaration', item)) { - return [{ type: item.typeDescriptions.typeString }]; - } else { - return item.returns; - } -}; - -module.exports['inherited-functions'] = function ({ item }) { - const { inheritance } = item; - const baseFunctions = new Set(inheritance.flatMap(c => c.functions.flatMap(f => f.baseFunctions ?? []))); - return inheritance.map((contract, i) => ({ - contract, - functions: contract.functions.filter(f => !baseFunctions.has(f.id) && (f.name !== 'constructor' || i === 0)), - })); -}; diff --git a/lib_openzeppelin_contracts/foundry.toml b/lib_openzeppelin_contracts/foundry.toml deleted file mode 100644 index 391c40e..0000000 --- a/lib_openzeppelin_contracts/foundry.toml +++ /dev/null @@ -1,12 +0,0 @@ -[profile.default] -solc_version = '0.8.24' -evm_version = 'cancun' -src = 'contracts' -out = 'out' -libs = ['node_modules', 'lib'] -test = 'test' -cache_path = 'cache_forge' - -[fuzz] -runs = 5000 -max_test_rejects = 150000 diff --git a/lib_openzeppelin_contracts/hardhat.config.js b/lib_openzeppelin_contracts/hardhat.config.js deleted file mode 100644 index ef90c2d..0000000 --- a/lib_openzeppelin_contracts/hardhat.config.js +++ /dev/null @@ -1,126 +0,0 @@ -/// ENVVAR -// - COMPILE_VERSION: compiler version (default: 0.8.20) -// - SRC: contracts folder to compile (default: contracts) -// - COMPILE_MODE: production modes enables optimizations (default: development) -// - IR: enable IR compilation (default: false) -// - COVERAGE: enable coverage report -// - ENABLE_GAS_REPORT: enable gas report -// - COINMARKETCAP: coinmarkercat api key for USD value in gas report -// - CI: output gas report to file instead of stdout - -const fs = require('fs'); -const path = require('path'); - -const { argv } = require('yargs/yargs')() - .env('') - .options({ - // Compilation settings - compiler: { - alias: 'compileVersion', - type: 'string', - default: '0.8.24', - }, - src: { - alias: 'source', - type: 'string', - default: 'contracts', - }, - mode: { - alias: 'compileMode', - type: 'string', - choices: ['production', 'development'], - default: 'development', - }, - ir: { - alias: 'enableIR', - type: 'boolean', - default: false, - }, - evm: { - alias: 'evmVersion', - type: 'string', - default: 'cancun', - }, - // Extra modules - coverage: { - type: 'boolean', - default: false, - }, - gas: { - alias: 'enableGasReport', - type: 'boolean', - default: false, - }, - coinmarketcap: { - alias: 'coinmarketcapApiKey', - type: 'string', - }, - }); - -require('@nomicfoundation/hardhat-chai-matchers'); -require('@nomicfoundation/hardhat-ethers'); -require('hardhat-exposed'); -require('hardhat-gas-reporter'); -require('hardhat-ignore-warnings'); -require('solidity-coverage'); -require('solidity-docgen'); - -for (const f of fs.readdirSync(path.join(__dirname, 'hardhat'))) { - require(path.join(__dirname, 'hardhat', f)); -} - -const withOptimizations = argv.gas || argv.coverage || argv.compileMode === 'production'; -const allowUnlimitedContractSize = argv.gas || argv.coverage || argv.compileMode === 'development'; - -/** - * @type import('hardhat/config').HardhatUserConfig - */ -module.exports = { - solidity: { - version: argv.compiler, - settings: { - optimizer: { - enabled: withOptimizations, - runs: 200, - }, - evmVersion: argv.evm, - viaIR: withOptimizations && argv.ir, - outputSelection: { '*': { '*': ['storageLayout'] } }, - }, - }, - warnings: { - 'contracts-exposed/**/*': { - 'code-size': 'off', - 'initcode-size': 'off', - }, - '*': { - 'code-size': withOptimizations, - 'unused-param': !argv.coverage, // coverage causes unused-param warnings - 'transient-storage': false, - default: 'error', - }, - }, - networks: { - hardhat: { - hardfork: argv.evm, - allowUnlimitedContractSize, - initialBaseFeePerGas: argv.coverage ? 0 : undefined, - }, - }, - exposed: { - imports: true, - initializers: true, - exclude: ['vendor/**/*', '**/*WithInit.sol'], - }, - gasReporter: { - enabled: argv.gas, - showMethodSig: true, - includeBytecodeInJSON: true, - currency: 'USD', - coinmarketcap: argv.coinmarketcap, - }, - paths: { - sources: argv.src, - }, - docgen: require('./docs/config'), -}; diff --git a/lib_openzeppelin_contracts/hardhat/async-test-sanity.js b/lib_openzeppelin_contracts/hardhat/async-test-sanity.js deleted file mode 100644 index c05e5bd..0000000 --- a/lib_openzeppelin_contracts/hardhat/async-test-sanity.js +++ /dev/null @@ -1,3 +0,0 @@ -process.on('unhandledRejection', reason => { - throw new Error(reason); -}); diff --git a/lib_openzeppelin_contracts/hardhat/env-artifacts.js b/lib_openzeppelin_contracts/hardhat/env-artifacts.js deleted file mode 100644 index e97ae64..0000000 --- a/lib_openzeppelin_contracts/hardhat/env-artifacts.js +++ /dev/null @@ -1,29 +0,0 @@ -const { HardhatError } = require('hardhat/internal/core/errors'); - -function isExpectedError(e, suffix) { - // HH700: Artifact not found - from https://hardhat.org/hardhat-runner/docs/errors#HH700 - return HardhatError.isHardhatError(e) && e.number === 700 && suffix !== ''; -} - -// Modifies the artifact require functions so that instead of X it loads the XUpgradeable contract. -// This allows us to run the same test suite on both the original and the transpiled and renamed Upgradeable contracts. -extendEnvironment(hre => { - const suffixes = ['UpgradeableWithInit', 'Upgradeable', '']; - - // Ethers - const originalReadArtifact = hre.artifacts.readArtifact; - hre.artifacts.readArtifact = async function (name) { - for (const suffix of suffixes) { - try { - return await originalReadArtifact.call(this, name + suffix); - } catch (e) { - if (isExpectedError(e, suffix)) { - continue; - } else { - throw e; - } - } - } - throw new Error('Unreachable'); - }; -}); diff --git a/lib_openzeppelin_contracts/hardhat/ignore-unreachable-warnings.js b/lib_openzeppelin_contracts/hardhat/ignore-unreachable-warnings.js deleted file mode 100644 index 8e3e343..0000000 --- a/lib_openzeppelin_contracts/hardhat/ignore-unreachable-warnings.js +++ /dev/null @@ -1,45 +0,0 @@ -// Warnings about unreachable code are emitted with a source location that corresponds to the unreachable code. -// We have some testing contracts that purposely cause unreachable code, but said code is in the library contracts, and -// with hardhat-ignore-warnings we are not able to selectively ignore them without potentially ignoring relevant -// warnings that we don't want to miss. -// Thus, we need to handle these warnings separately. We force Hardhat to compile them in a separate compilation job and -// then ignore the warnings about unreachable code that come from that compilation job. - -const { task } = require('hardhat/config'); -const { - TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, - TASK_COMPILE_SOLIDITY_COMPILE, -} = require('hardhat/builtin-tasks/task-names'); - -const marker = Symbol('unreachable'); -const markedCache = new WeakMap(); - -task(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, async (params, _, runSuper) => { - const job = await runSuper(params); - // If the file is in the unreachable directory, we make a copy of the config and mark it, which will cause it to get - // compiled separately (along with the other marked files). - if (params.file.sourceName.startsWith('contracts/mocks/') && /\bunreachable\b/.test(params.file.sourceName)) { - const originalConfig = job.solidityConfig; - let markedConfig = markedCache.get(originalConfig); - if (markedConfig === undefined) { - markedConfig = { ...originalConfig, [marker]: true }; - markedCache.set(originalConfig, markedConfig); - } - job.solidityConfig = markedConfig; - } - return job; -}); - -const W_UNREACHABLE_CODE = '5740'; - -task(TASK_COMPILE_SOLIDITY_COMPILE, async (params, _, runSuper) => { - const marked = params.compilationJob.solidityConfig[marker]; - const result = await runSuper(params); - if (marked) { - result.output = { - ...result.output, - errors: result.output.errors?.filter(e => e.severity !== 'warning' || e.errorCode !== W_UNREACHABLE_CODE), - }; - } - return result; -}); diff --git a/lib_openzeppelin_contracts/hardhat/remappings.js b/lib_openzeppelin_contracts/hardhat/remappings.js deleted file mode 100644 index cd9984d..0000000 --- a/lib_openzeppelin_contracts/hardhat/remappings.js +++ /dev/null @@ -1,18 +0,0 @@ -const fs = require('fs'); -const { task } = require('hardhat/config'); -const { TASK_COMPILE_GET_REMAPPINGS } = require('hardhat/builtin-tasks/task-names'); - -task(TASK_COMPILE_GET_REMAPPINGS).setAction((taskArgs, env, runSuper) => - runSuper().then(remappings => - Object.assign( - remappings, - Object.fromEntries( - fs - .readFileSync('remappings.txt', 'utf-8') - .split('\n') - .filter(Boolean) - .map(line => line.trim().split('=')), - ), - ), - ), -); diff --git a/lib_openzeppelin_contracts/hardhat/skip-foundry-tests.js b/lib_openzeppelin_contracts/hardhat/skip-foundry-tests.js deleted file mode 100644 index 965ba37..0000000 --- a/lib_openzeppelin_contracts/hardhat/skip-foundry-tests.js +++ /dev/null @@ -1,6 +0,0 @@ -const { subtask } = require('hardhat/config'); -const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require('hardhat/builtin-tasks/task-names'); - -subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(async (_, __, runSuper) => - (await runSuper()).filter(path => !path.endsWith('.t.sol')), -); diff --git a/lib_openzeppelin_contracts/hardhat/task-test-get-files.js b/lib_openzeppelin_contracts/hardhat/task-test-get-files.js deleted file mode 100644 index 108f40a..0000000 --- a/lib_openzeppelin_contracts/hardhat/task-test-get-files.js +++ /dev/null @@ -1,25 +0,0 @@ -const { internalTask } = require('hardhat/config'); -const { TASK_TEST_GET_TEST_FILES } = require('hardhat/builtin-tasks/task-names'); - -// Modifies `hardhat test` to skip the proxy tests after proxies are removed by the transpiler for upgradeability. - -internalTask(TASK_TEST_GET_TEST_FILES).setAction(async (args, hre, runSuper) => { - const path = require('path'); - const { promises: fs } = require('fs'); - - const hasProxies = await fs - .access(path.join(hre.config.paths.sources, 'proxy/Proxy.sol')) - .then(() => true) - .catch(() => false); - - const ignoredIfProxy = [ - 'proxy/beacon/BeaconProxy.test.js', - 'proxy/beacon/UpgradeableBeacon.test.js', - 'proxy/ERC1967/ERC1967Proxy.test.js', - 'proxy/transparent/ProxyAdmin.test.js', - 'proxy/transparent/TransparentUpgradeableProxy.test.js', - 'proxy/utils/UUPSUpgradeable.test.js', - ].map(p => path.join(hre.config.paths.tests, p)); - - return (await runSuper(args)).filter(file => hasProxies || !ignoredIfProxy.includes(file)); -}); diff --git a/lib_openzeppelin_contracts/logo.svg b/lib_openzeppelin_contracts/logo.svg deleted file mode 100644 index f1e14c2..0000000 --- a/lib_openzeppelin_contracts/logo.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/lib_openzeppelin_contracts/netlify.toml b/lib_openzeppelin_contracts/netlify.toml deleted file mode 100644 index 0447f41..0000000 --- a/lib_openzeppelin_contracts/netlify.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build] -command = "npm run docs" -publish = "build/site" diff --git a/lib_openzeppelin_contracts/package-lock.json b/lib_openzeppelin_contracts/package-lock.json deleted file mode 100644 index 91221ed..0000000 --- a/lib_openzeppelin_contracts/package-lock.json +++ /dev/null @@ -1,11604 +0,0 @@ -{ - "name": "openzeppelin-solidity", - "version": "5.0.2", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "openzeppelin-solidity", - "version": "5.0.2", - "license": "MIT", - "devDependencies": { - "@changesets/changelog-github": "^0.5.0", - "@changesets/cli": "^2.26.0", - "@changesets/pre": "^2.0.0", - "@changesets/read": "^0.6.0", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", - "@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/upgrade-safe-transpiler": "^0.3.32", - "@openzeppelin/upgrades-core": "^1.20.6", - "chai": "^4.2.0", - "eslint": "^8.30.0", - "eslint-config-prettier": "^9.0.0", - "ethers": "^6.7.1", - "glob": "^10.3.5", - "graphlib": "^2.1.8", - "hardhat": "^2.22.2", - "hardhat-exposed": "^0.3.15", - "hardhat-gas-reporter": "^2.0.0", - "hardhat-ignore-warnings": "^0.2.11", - "lodash.startcase": "^4.4.0", - "micromatch": "^4.0.2", - "p-limit": "^3.1.0", - "prettier": "^3.0.0", - "prettier-plugin-solidity": "^1.1.0", - "rimraf": "^5.0.1", - "semver": "^7.3.5", - "solhint": "^4.0.0", - "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", - "solidity-ast": "^0.4.50", - "solidity-coverage": "^0.8.5", - "solidity-docgen": "^0.6.0-beta.29", - "undici": "^6.11.1", - "yargs": "^17.0.0" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz", - "integrity": "sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg==", - "dev": true - }, - "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@changesets/apply-release-plan": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.4.tgz", - "integrity": "sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/config": "^2.3.1", - "@changesets/get-version-range-type": "^0.3.2", - "@changesets/git": "^2.0.0", - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3", - "detect-indent": "^6.0.0", - "fs-extra": "^7.0.1", - "lodash.startcase": "^4.4.0", - "outdent": "^0.5.0", - "prettier": "^2.7.1", - "resolve-from": "^5.0.0", - "semver": "^7.5.3" - } - }, - "node_modules/@changesets/apply-release-plan/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/@changesets/assemble-release-plan": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.4.tgz", - "integrity": "sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.6", - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3", - "semver": "^7.5.3" - } - }, - "node_modules/@changesets/changelog-git": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.1.14.tgz", - "integrity": "sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==", - "dev": true, - "dependencies": { - "@changesets/types": "^5.2.1" - } - }, - "node_modules/@changesets/changelog-github": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.5.0.tgz", - "integrity": "sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==", - "dev": true, - "dependencies": { - "@changesets/get-github-info": "^0.6.0", - "@changesets/types": "^6.0.0", - "dotenv": "^8.1.0" - } - }, - "node_modules/@changesets/changelog-github/node_modules/@changesets/types": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.0.0.tgz", - "integrity": "sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==", - "dev": true - }, - "node_modules/@changesets/cli": { - "version": "2.26.2", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.26.2.tgz", - "integrity": "sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/apply-release-plan": "^6.1.4", - "@changesets/assemble-release-plan": "^5.2.4", - "@changesets/changelog-git": "^0.1.14", - "@changesets/config": "^2.3.1", - "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.6", - "@changesets/get-release-plan": "^3.0.17", - "@changesets/git": "^2.0.0", - "@changesets/logger": "^0.0.5", - "@changesets/pre": "^1.0.14", - "@changesets/read": "^0.5.9", - "@changesets/types": "^5.2.1", - "@changesets/write": "^0.2.3", - "@manypkg/get-packages": "^1.1.3", - "@types/is-ci": "^3.0.0", - "@types/semver": "^7.5.0", - "ansi-colors": "^4.1.3", - "chalk": "^2.1.0", - "enquirer": "^2.3.0", - "external-editor": "^3.1.0", - "fs-extra": "^7.0.1", - "human-id": "^1.0.2", - "is-ci": "^3.0.1", - "meow": "^6.0.0", - "outdent": "^0.5.0", - "p-limit": "^2.2.0", - "preferred-pm": "^3.0.0", - "resolve-from": "^5.0.0", - "semver": "^7.5.3", - "spawndamnit": "^2.0.0", - "term-size": "^2.1.0", - "tty-table": "^4.1.5" - }, - "bin": { - "changeset": "bin.js" - } - }, - "node_modules/@changesets/cli/node_modules/@changesets/pre": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.14.tgz", - "integrity": "sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/errors": "^0.1.4", - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1" - } - }, - "node_modules/@changesets/cli/node_modules/@changesets/read": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.5.9.tgz", - "integrity": "sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/git": "^2.0.0", - "@changesets/logger": "^0.0.5", - "@changesets/parse": "^0.3.16", - "@changesets/types": "^5.2.1", - "chalk": "^2.1.0", - "fs-extra": "^7.0.1", - "p-filter": "^2.1.0" - } - }, - "node_modules/@changesets/cli/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@changesets/config": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@changesets/config/-/config-2.3.1.tgz", - "integrity": "sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==", - "dev": true, - "dependencies": { - "@changesets/errors": "^0.1.4", - "@changesets/get-dependents-graph": "^1.3.6", - "@changesets/logger": "^0.0.5", - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1", - "micromatch": "^4.0.2" - } - }, - "node_modules/@changesets/errors": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.1.4.tgz", - "integrity": "sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==", - "dev": true, - "dependencies": { - "extendable-error": "^0.1.5" - } - }, - "node_modules/@changesets/get-dependents-graph": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.6.tgz", - "integrity": "sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==", - "dev": true, - "dependencies": { - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3", - "chalk": "^2.1.0", - "fs-extra": "^7.0.1", - "semver": "^7.5.3" - } - }, - "node_modules/@changesets/get-github-info": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.6.0.tgz", - "integrity": "sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==", - "dev": true, - "dependencies": { - "dataloader": "^1.4.0", - "node-fetch": "^2.5.0" - } - }, - "node_modules/@changesets/get-release-plan": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-3.0.17.tgz", - "integrity": "sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/assemble-release-plan": "^5.2.4", - "@changesets/config": "^2.3.1", - "@changesets/pre": "^1.0.14", - "@changesets/read": "^0.5.9", - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3" - } - }, - "node_modules/@changesets/get-release-plan/node_modules/@changesets/pre": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-1.0.14.tgz", - "integrity": "sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/errors": "^0.1.4", - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1" - } - }, - "node_modules/@changesets/get-release-plan/node_modules/@changesets/read": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.5.9.tgz", - "integrity": "sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/git": "^2.0.0", - "@changesets/logger": "^0.0.5", - "@changesets/parse": "^0.3.16", - "@changesets/types": "^5.2.1", - "chalk": "^2.1.0", - "fs-extra": "^7.0.1", - "p-filter": "^2.1.0" - } - }, - "node_modules/@changesets/get-version-range-type": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz", - "integrity": "sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==", - "dev": true - }, - "node_modules/@changesets/git": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@changesets/git/-/git-2.0.0.tgz", - "integrity": "sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/errors": "^0.1.4", - "@changesets/types": "^5.2.1", - "@manypkg/get-packages": "^1.1.3", - "is-subdir": "^1.1.1", - "micromatch": "^4.0.2", - "spawndamnit": "^2.0.0" - } - }, - "node_modules/@changesets/logger": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.0.5.tgz", - "integrity": "sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==", - "dev": true, - "dependencies": { - "chalk": "^2.1.0" - } - }, - "node_modules/@changesets/parse": { - "version": "0.3.16", - "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.3.16.tgz", - "integrity": "sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==", - "dev": true, - "dependencies": { - "@changesets/types": "^5.2.1", - "js-yaml": "^3.13.1" - } - }, - "node_modules/@changesets/pre": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.0.tgz", - "integrity": "sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/errors": "^0.2.0", - "@changesets/types": "^6.0.0", - "@manypkg/get-packages": "^1.1.3", - "fs-extra": "^7.0.1" - } - }, - "node_modules/@changesets/pre/node_modules/@changesets/errors": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", - "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", - "dev": true, - "dependencies": { - "extendable-error": "^0.1.5" - } - }, - "node_modules/@changesets/pre/node_modules/@changesets/types": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.0.0.tgz", - "integrity": "sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==", - "dev": true - }, - "node_modules/@changesets/read": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.0.tgz", - "integrity": "sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/git": "^3.0.0", - "@changesets/logger": "^0.1.0", - "@changesets/parse": "^0.4.0", - "@changesets/types": "^6.0.0", - "chalk": "^2.1.0", - "fs-extra": "^7.0.1", - "p-filter": "^2.1.0" - } - }, - "node_modules/@changesets/read/node_modules/@changesets/errors": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", - "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", - "dev": true, - "dependencies": { - "extendable-error": "^0.1.5" - } - }, - "node_modules/@changesets/read/node_modules/@changesets/git": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.0.tgz", - "integrity": "sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/errors": "^0.2.0", - "@changesets/types": "^6.0.0", - "@manypkg/get-packages": "^1.1.3", - "is-subdir": "^1.1.1", - "micromatch": "^4.0.2", - "spawndamnit": "^2.0.0" - } - }, - "node_modules/@changesets/read/node_modules/@changesets/logger": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.0.tgz", - "integrity": "sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g==", - "dev": true, - "dependencies": { - "chalk": "^2.1.0" - } - }, - "node_modules/@changesets/read/node_modules/@changesets/parse": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.0.tgz", - "integrity": "sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==", - "dev": true, - "dependencies": { - "@changesets/types": "^6.0.0", - "js-yaml": "^3.13.1" - } - }, - "node_modules/@changesets/read/node_modules/@changesets/types": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.0.0.tgz", - "integrity": "sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==", - "dev": true - }, - "node_modules/@changesets/types": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-5.2.1.tgz", - "integrity": "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==", - "dev": true - }, - "node_modules/@changesets/write": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.2.3.tgz", - "integrity": "sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.1", - "@changesets/types": "^5.2.1", - "fs-extra": "^7.0.1", - "human-id": "^1.0.2", - "prettier": "^2.7.1" - } - }, - "node_modules/@changesets/write/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.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==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/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/eslintrc/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/js": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", - "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@ethereumjs/rlp": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", - "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", - "dev": true, - "bin": { - "rlp": "bin/rlp" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", - "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", - "dev": true, - "dependencies": { - "@ethereumjs/rlp": "^4.0.1", - "ethereum-cryptography": "^2.0.0", - "micro-ftch": "^0.3.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", - "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", - "dev": true, - "dependencies": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@scure/bip32": "1.3.1", - "@scure/bip39": "1.2.1" - } - }, - "node_modules/@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/abstract-provider": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", - "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0" - } - }, - "node_modules/@ethersproject/abstract-signer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", - "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "node_modules/@ethersproject/address": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", - "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/rlp": "^5.7.0" - } - }, - "node_modules/@ethersproject/base64": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", - "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0" - } - }, - "node_modules/@ethersproject/bignumber": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", - "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "bn.js": "^5.2.1" - } - }, - "node_modules/@ethersproject/bignumber/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/constants": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", - "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0" - } - }, - "node_modules/@ethersproject/hash": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", - "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/keccak256": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", - "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "js-sha3": "0.8.0" - } - }, - "node_modules/@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ] - }, - "node_modules/@ethersproject/networks": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", - "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/rlp": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", - "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "bn.js": "^5.2.1", - "elliptic": "6.5.4", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/signing-key/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/@ethersproject/strings": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", - "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/transactions": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", - "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0" - } - }, - "node_modules/@ethersproject/units": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", - "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/web": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", - "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", - "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@frangio/servbot": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@frangio/servbot/-/servbot-0.2.5.tgz", - "integrity": "sha512-ogja4iAPZ1VwM5MU3C1ZhB88358F0PGbmSTGOkIZwOyLaDoMHIqOVCnavHjR7DV5h+oAI4Z4KDqlam3myQUrmg==", - "dev": true, - "engines": { - "node": ">=12.x", - "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==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "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/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@manypkg/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.5.5", - "@types/node": "^12.7.1", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0" - } - }, - "node_modules/@manypkg/find-root/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true - }, - "node_modules/@manypkg/find-root/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@manypkg/get-packages": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", - "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.5.5", - "@changesets/types": "^4.0.1", - "@manypkg/find-root": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "^11.0.0", - "read-yaml-file": "^1.1.0" - } - }, - "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", - "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", - "dev": true - }, - "node_modules/@manypkg/get-packages/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@metamask/eth-sig-util": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", - "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", - "dev": true, - "dependencies": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "dependencies": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/@noble/curves": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", - "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.3.1" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", - "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", - "dev": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nomicfoundation/edr": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.3.3.tgz", - "integrity": "sha512-zP+e+3B1nEUx6bW5BPnIzCQbkhmYfdMBJdiVggTqqTfAA82sOkdOG7wsOMcz5qF3fYfx/irNRM1kgc9HVFIbpQ==", - "dev": true, - "engines": { - "node": ">= 18" - }, - "optionalDependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.3.3", - "@nomicfoundation/edr-darwin-x64": "0.3.3", - "@nomicfoundation/edr-linux-arm64-gnu": "0.3.3", - "@nomicfoundation/edr-linux-arm64-musl": "0.3.3", - "@nomicfoundation/edr-linux-x64-gnu": "0.3.3", - "@nomicfoundation/edr-linux-x64-musl": "0.3.3", - "@nomicfoundation/edr-win32-arm64-msvc": "0.3.3", - "@nomicfoundation/edr-win32-ia32-msvc": "0.3.3", - "@nomicfoundation/edr-win32-x64-msvc": "0.3.3" - } - }, - "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.3.tgz", - "integrity": "sha512-E9VGsUD+1Ga4mn/5ooHsMi8JEfhZbKP6CXN/BhJ8kXbIC10NqTD1RuhCKGRtYq4vqH/3Nfq25Xg8E8RWOF4KBQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.3.tgz", - "integrity": "sha512-vkZXZ1ydPg+Ijb2iyqENA+KCkxGTCUWG5itCSliiA0Li2YE7ujDMGhheEpFp1WVlZadviz0bfk1rZXbCqlirpg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.3.tgz", - "integrity": "sha512-gdIg0Yj1qqS9wVuywc5B/+DqKylfUGB6/CQn/shMqwAfsAVAVpchkhy66PR+REEx7fh/GkNctxLlENXPeLzDiA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.3.tgz", - "integrity": "sha512-AXZ08MFvhNeBZbOBNmz1SJ/DMrMOE2mHEJtaNnsctlxIunjxfrWww4q+WXB34jbr9iaVYYlPsaWe5sueuw6s3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.3.tgz", - "integrity": "sha512-xElOs1U+E6lBLtv1mnJ+E8nr2MxZgKiLo8bZAgBboy9odYtmkDVwhMjtsFKSuZbGxFtsSyGRT4cXw3JAbtUDeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.3.tgz", - "integrity": "sha512-2Fe6gwm1RAGQ/PfMYiaSba2OrFp8zzYWh+am9lYObOFjV9D+A1zhIzfy0UC74glPks5eV8eY4pBPrVR042m2Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-win32-arm64-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-arm64-msvc/-/edr-win32-arm64-msvc-0.3.3.tgz", - "integrity": "sha512-8NHyxIsFrl0ufSQ/ErqF2lKIa/gz1gaaa1a2vKkDEqvqCUcPhBTYhA5NHgTPhLETFTnCFr0z+YbctFCyjh4qrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/edr-win32-ia32-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-ia32-msvc/-/edr-win32-ia32-msvc-0.3.3.tgz", - "integrity": "sha512-0F6hM0kGia4dQVb/kauho9JcP1ozWisY2/She+ISR5ceuhzmAwQJluM0g+0TYDME0LtxBxiMPq/yPiZMQeq31w==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.3.tgz", - "integrity": "sha512-d75q1uaMb6z9i+GQZoblbOfFBvlBnWc+5rB13UWRkCOJSnoYwyFWhGJx5GeM59gC7aIblc5VD9qOAhHuvM9N+w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz", - "integrity": "sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-util": "9.0.4" - } - }, - "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz", - "integrity": "sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==", - "dev": true, - "bin": { - "rlp": "bin/rlp.cjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz", - "integrity": "sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "c-kzg": "^2.1.2" - }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } - } - }, - "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz", - "integrity": "sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "c-kzg": "^2.1.2" - }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } - } - }, - "node_modules/@nomicfoundation/hardhat-chai-matchers": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.6.tgz", - "integrity": "sha512-Te1Uyo9oJcTCF0Jy9dztaLpshmlpjLf2yPtWXlXuLjMt3RRSmJLm/+rKVTW6gfadAEs12U/it6D0ZRnnRGiICQ==", - "dev": true, - "dependencies": { - "@types/chai-as-promised": "^7.1.3", - "chai-as-promised": "^7.1.1", - "deep-eql": "^4.0.1", - "ordinal": "^1.0.3" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "chai": "^4.2.0", - "ethers": "^6.1.0", - "hardhat": "^2.9.4" - } - }, - "node_modules/@nomicfoundation/hardhat-ethers": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.4.tgz", - "integrity": "sha512-k9qbLoY7qn6C6Y1LI0gk2kyHXil2Tauj4kGzQ8pgxYXIGw8lWn8tuuL72E11CrlKaXRUvOgF0EXrv/msPI2SbA==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "lodash.isequal": "^4.5.0" - }, - "peerDependencies": { - "ethers": "^6.1.0", - "hardhat": "^2.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-network-helpers": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.9.tgz", - "integrity": "sha512-OXWCv0cHpwLUO2u7bFxBna6dQtCC2Gg/aN/KtJLO7gmuuA28vgmVKYFRCDUqrbjujzgfwQ2aKyZ9Y3vSmDqS7Q==", - "dev": true, - "dependencies": { - "ethereumjs-util": "^7.1.4" - }, - "peerDependencies": { - "hardhat": "^2.9.5" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", - "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==", - "dev": true, - "engines": { - "node": ">= 12" - }, - "optionalDependencies": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz", - "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", - "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", - "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", - "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", - "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", - "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", - "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", - "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", - "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", - "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@openzeppelin/docs-utils": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@openzeppelin/docs-utils/-/docs-utils-0.1.5.tgz", - "integrity": "sha512-GfqXArKmdq8rv+hsP+g8uS1VEkvMIzWs31dCONffzmqFwJ+MOsaNQNZNXQnLRgUkzk8i5mTNDjJuxDy+aBZImQ==", - "dev": true, - "dependencies": { - "@frangio/servbot": "^0.2.5", - "chalk": "^3.0.0", - "chokidar": "^3.5.3", - "env-paths": "^2.2.0", - "find-up": "^4.1.0", - "is-port-reachable": "^3.0.0", - "js-yaml": "^3.13.1", - "lodash.startcase": "^4.4.0", - "minimist": "^1.2.0" - }, - "bin": { - "oz-docs": "oz-docs.js" - } - }, - "node_modules/@openzeppelin/docs-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@openzeppelin/docs-utils/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@openzeppelin/docs-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@openzeppelin/docs-utils/node_modules/color-name": { - "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 - }, - "node_modules/@openzeppelin/docs-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@openzeppelin/docs-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "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" - } - }, - "node_modules/@openzeppelin/upgrade-safe-transpiler": { - "version": "0.3.32", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrade-safe-transpiler/-/upgrade-safe-transpiler-0.3.32.tgz", - "integrity": "sha512-ypgj6MXXcDG0dOuMwENXt0H4atCtCsPgpDgWZYewb2egfUCMpj6d2GO4pcNZgdn1zYsmUHfm5ZA/Nga/8qkdKA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.0", - "compare-versions": "^6.0.0", - "ethereum-cryptography": "^2.0.0", - "lodash": "^4.17.20", - "minimatch": "^9.0.0", - "minimist": "^1.2.5", - "solidity-ast": "^0.4.51" - }, - "bin": { - "upgrade-safe-transpiler": "dist/cli.js" - } - }, - "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/ethereum-cryptography": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", - "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", - "dev": true, - "dependencies": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@scure/bip32": "1.3.1", - "@scure/bip39": "1.2.1" - } - }, - "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@openzeppelin/upgrade-safe-transpiler/node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@openzeppelin/upgrades-core": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.29.0.tgz", - "integrity": "sha512-csZvAMNqUJjMDNBPbaXcV9Nlo4oagMD/HkOBHTpYbBTpnmUhwPVHOMv+Rl0RatBdLHuGc6hw88h80k5PWkEeWw==", - "dev": true, - "dependencies": { - "cbor": "^9.0.0", - "chalk": "^4.1.0", - "compare-versions": "^6.0.0", - "debug": "^4.1.1", - "ethereumjs-util": "^7.0.3", - "minimist": "^1.2.7", - "proper-lockfile": "^4.1.1", - "solidity-ast": "^0.4.26" - }, - "bin": { - "openzeppelin-upgrades-core": "dist/cli/cli.js" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@openzeppelin/upgrades-core/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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/color-name": { - "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 - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "dev": true, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dev": true, - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", - "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", - "dev": true, - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@scure/base": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", - "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", - "dev": true, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", - "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", - "dev": true, - "dependencies": { - "@noble/curves": "~1.1.0", - "@noble/hashes": "~1.3.1", - "@scure/base": "~1.1.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", - "dev": true, - "dependencies": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/minimal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", - "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/node": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", - "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", - "dev": true, - "dependencies": { - "@sentry/core": "5.30.0", - "@sentry/hub": "5.30.0", - "@sentry/tracing": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/tracing": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", - "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@types/bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-dkpZu0szUtn9UXTmw+e0AJFd4D2XAxDnsCLdc05SfqpqzPEBft8eQr8uaFitfo/dUUOZERaLec2hHMG87A4Dxg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", - "dev": true - }, - "node_modules/@types/chai-as-promised": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.6.tgz", - "integrity": "sha512-cQLhk8fFarRVZAXUQV1xEnZgMoPxqKojBvRkqPCKPQCzEhpbbSKl1Uu75kDng7k5Ln6LQLUmNBjLlFthCgm1NA==", - "dev": true, - "dependencies": { - "@types/chai": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, - "node_modules/@types/is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.1.0" - } - }, - "node_modules/@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "node_modules/@types/pbkdf2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", - "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true - }, - "node_modules/abitype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.0.tgz", - "integrity": "sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true, - "engines": { - "node": ">=0.3.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/antlr4": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", - "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/antlr4ts": { - "version": "0.5.0-alpha.4", - "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.3.tgz", - "integrity": "sha512-kcBubumjciBg4JKp5KTKtI7ec7tRefPk88yjkWJwaVKYd9QfTaxcsOxoMNKd7iBr447zCfDV0z1kOF47umv42g==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/ast-parents": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/ast-parents/-/ast-parents-0.0.1.tgz", - "integrity": "sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==", - "dev": true - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/better-path-resolve": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", - "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", - "dev": true, - "dependencies": { - "is-windows": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true - }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/boxen/node_modules/color-name": { - "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 - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/breakword": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/breakword/-/breakword-1.0.6.tgz", - "integrity": "sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==", - "dev": true, - "dependencies": { - "wcwidth": "^1.0.1" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "node_modules/brotli-wasm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brotli-wasm/-/brotli-wasm-2.0.1.tgz", - "integrity": "sha512-+3USgYsC7bzb5yU0/p2HnnynZl0ak0E6uoIm4UW4Aby/8s8HFCq6NCfrrf1E9c3O8OCSzq3oYO1tUVqIi61Nww==", - "dev": true - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dev": true, - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dev": true, - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "node_modules/bufferutil": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", - "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cbor": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.1.tgz", - "integrity": "sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ==", - "dev": true, - "dependencies": { - "nofilter": "^3.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/chai": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", - "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 5" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true - }, - "node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/compare-versions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", - "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dev": true, - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig/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/cosmiconfig/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/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/csv": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/csv/-/csv-5.5.3.tgz", - "integrity": "sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==", - "dev": true, - "dependencies": { - "csv-generate": "^3.4.3", - "csv-parse": "^4.16.3", - "csv-stringify": "^5.6.5", - "stream-transform": "^2.1.3" - }, - "engines": { - "node": ">= 0.1.90" - } - }, - "node_modules/csv-generate": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz", - "integrity": "sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==", - "dev": true - }, - "node_modules/csv-parse": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", - "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", - "dev": true - }, - "node_modules/csv-stringify": { - "version": "5.6.5", - "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.6.5.tgz", - "integrity": "sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==", - "dev": true - }, - "node_modules/dataloader": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", - "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", - "dev": true - }, - "node_modules/death": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", - "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dev": true, - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", - "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", - "dev": true, - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/difflib": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", - "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", - "dev": true, - "dependencies": { - "heap": ">= 0.2.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "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", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "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/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/enquirer/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/enquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", - "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.1", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", - "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", - "dev": true, - "dependencies": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=0.12.0" - }, - "optionalDependencies": { - "source-map": "~0.2.0" - } - }, - "node_modules/escodegen/node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", - "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", - "dev": true, - "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", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "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", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "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" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "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 - }, - "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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "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, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", - "dev": true, - "dependencies": { - "js-sha3": "^0.8.0" - } - }, - "node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/ethereumjs-abi": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", - "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - } - }, - "node_modules/ethereumjs-abi/node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "dependencies": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/ethereumjs-util/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/ethers": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz", - "integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@adraffy/ens-normalize": "1.9.2", - "@noble/hashes": "1.1.2", - "@noble/secp256k1": "1.7.1", - "@types/node": "18.15.13", - "aes-js": "4.0.0-beta.5", - "tslib": "2.4.0", - "ws": "8.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/node_modules/@noble/hashes": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz", - "integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/ethers/node_modules/@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", - "dev": true - }, - "node_modules/ethers/node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "dev": true - }, - "node_modules/ethers/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "node_modules/ethers/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", - "dev": true, - "dependencies": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/ethjs-unit/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true - }, - "node_modules/ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", - "dev": true, - "dependencies": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/extendable-error": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", - "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", - "dev": true - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "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==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-yarn-workspace-root2": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", - "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", - "dev": true, - "dependencies": { - "micromatch": "^4.0.2", - "pkg-dir": "^4.2.0" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "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==", - "dev": true, - "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" - }, - "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_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "dev": true, - "engines": { - "node": ">= 14.17" - } - }, - "node_modules/fp-ts": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", - "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ghost-testrpc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", - "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "node-emoji": "^1.10.0" - }, - "bin": { - "testrpc-sc": "index.js" - } - }, - "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "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" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/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", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "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", - "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/hardhat": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.2.tgz", - "integrity": "sha512-0xZ7MdCZ5sJem4MrvpQWLR3R3zGDoHw5lsR+pBFimqwagimIOn3bWuZv69KA+veXClwI1s/zpqgwPwiFrd4Dxw==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.3.1", - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-tx": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "@nomicfoundation/solidity-analyzer": "^0.1.0", - "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", - "adm-zip": "^0.4.16", - "aggregate-error": "^3.0.0", - "ansi-escapes": "^4.3.0", - "boxen": "^5.1.2", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", - "ci-info": "^2.0.0", - "debug": "^4.1.1", - "enquirer": "^2.3.0", - "env-paths": "^2.2.0", - "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", - "fp-ts": "1.19.3", - "fs-extra": "^7.0.1", - "glob": "7.2.0", - "immutable": "^4.0.0-rc.12", - "io-ts": "1.10.4", - "keccak": "^3.0.2", - "lodash": "^4.17.11", - "mnemonist": "^0.38.0", - "mocha": "^10.0.0", - "p-map": "^4.0.0", - "raw-body": "^2.4.1", - "resolve": "1.17.0", - "semver": "^6.3.0", - "solc": "0.7.3", - "source-map-support": "^0.5.13", - "stacktrace-parser": "^0.1.10", - "tsort": "0.0.1", - "undici": "^5.14.0", - "uuid": "^8.3.2", - "ws": "^7.4.6" - }, - "bin": { - "hardhat": "internal/cli/bootstrap.js" - }, - "peerDependencies": { - "ts-node": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/hardhat-exposed": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/hardhat-exposed/-/hardhat-exposed-0.3.15.tgz", - "integrity": "sha512-jqxErCnSWGYf4vAkLmh3H3u+IuLuCLw/EVeV13z1JKJMJAd/iO+G283n8T124S/Q2BF/BoA2zgzYAlqXgNyKew==", - "dev": true, - "dependencies": { - "micromatch": "^4.0.4", - "solidity-ast": "^0.4.52" - }, - "peerDependencies": { - "hardhat": "^2.3.0" - } - }, - "node_modules/hardhat-gas-reporter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-2.1.0.tgz", - "integrity": "sha512-d/WU/qHhBFnbweAm2fAAjcaaE0M7BKZ4r+/bqcFlfP6um28BXtlv2FrJ6oyQUGSFD0ttbmB7sH4ZFDzkYw5GzA==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/units": "^5.7.0", - "@solidity-parser/parser": "^0.18.0", - "axios": "^1.6.7", - "brotli-wasm": "^2.0.1", - "chalk": "4.1.2", - "cli-table3": "^0.6.3", - "ethereum-cryptography": "^2.1.3", - "glob": "^10.3.10", - "jsonschema": "^1.4.1", - "lodash": "^4.17.21", - "markdown-table": "2.0.0", - "sha1": "^1.1.1", - "viem": "2.7.14" - }, - "peerDependencies": { - "hardhat": "^2.16.0" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/@noble/curves": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", - "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.3.3" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "dev": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/@scure/bip32": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", - "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", - "dev": true, - "dependencies": { - "@noble/curves": "~1.3.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/@scure/bip39": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", - "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", - "dev": true, - "dependencies": { - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/@solidity-parser/parser": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", - "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", - "dev": true - }, - "node_modules/hardhat-gas-reporter/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/hardhat-gas-reporter/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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/cli-table3": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.4.tgz", - "integrity": "sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/color-name": { - "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 - }, - "node_modules/hardhat-gas-reporter/node_modules/ethereum-cryptography": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", - "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", - "dev": true, - "dependencies": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@scure/bip32": "1.3.3", - "@scure/bip39": "1.2.2" - } - }, - "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", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "dev": true, - "dependencies": { - "repeat-string": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "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", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hardhat-gas-reporter/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hardhat-ignore-warnings": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/hardhat-ignore-warnings/-/hardhat-ignore-warnings-0.2.11.tgz", - "integrity": "sha512-+nHnRbP6COFZaXE7HAY7TZNE3au5vHe5dkcnyq0XaP07ikT2fJ3NhFY0vn7Deh4Qbz0Z/9Xpnj2ki6Ktgk61pg==", - "dev": true, - "dependencies": { - "minimatch": "^5.1.0", - "node-interval-tree": "^2.0.1", - "solidity-comments": "^0.0.2" - } - }, - "node_modules/hardhat-ignore-warnings/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/hardhat-ignore-warnings/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hardhat/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/hardhat/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/hardhat/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/hardhat/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/hardhat/node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, - "node_modules/hardhat/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" - } - }, - "node_modules/hardhat/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/hardhat/node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/hardhat/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hardhat/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/hardhat/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/hardhat/node_modules/solc": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", - "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", - "dev": true, - "dependencies": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "bin": { - "solcjs": "solcjs" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/hardhat/node_modules/solc/node_modules/fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/hardhat/node_modules/solc/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/hardhat/node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", - "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "dev": true - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/http2-wrapper/node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-id": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/human-id/-/human-id-1.0.2.tgz", - "integrity": "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==", - "dev": true - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/io-ts": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", - "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", - "dev": true, - "dependencies": { - "fp-ts": "^1.0.0" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", - "dev": true, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "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", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-port-reachable": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-3.1.0.tgz", - "integrity": "sha512-vjc0SSRNZ32s9SbZBzGaiP6YVB+xglLShhgZD/FHMZUXBvQWaV9CtzgeVhjccFJrI6RAMV+LX7NYxueW/A8W5A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-subdir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", - "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", - "dev": true, - "dependencies": { - "better-path-resolve": "1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.11" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isows": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.3.tgz", - "integrity": "sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wagmi-dev" - } - ], - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", - "dev": true, - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/keccak": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", - "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", - "dev": true, - "dependencies": { - "package-json": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/load-yaml-file": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", - "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.5", - "js-yaml": "^3.13.0", - "pify": "^4.0.1", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.startcase": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "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 - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", - "dev": true - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/meow": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", - "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "^4.0.2", - "normalize-package-data": "^2.5.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.13.1", - "yargs-parser": "^18.1.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micro-ftch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", - "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", - "dev": true - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/mixme": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/mixme/-/mixme-0.5.9.tgz", - "integrity": "sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==", - "dev": true, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mnemonist": { - "version": "0.38.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", - "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", - "dev": true, - "dependencies": { - "obliterator": "^2.0.0" - } - }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/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/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/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, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/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/mocha/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, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp-build": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", - "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-interval-tree": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-interval-tree/-/node-interval-tree-2.1.2.tgz", - "integrity": "sha512-bJ9zMDuNGzVQg1xv0bCPzyEDxHgbrx7/xGj6CDokvizZZmastPsOh0JJLuY8wA5q2SfX1TLNMk7XNV8WxbGxzA==", - "dev": true, - "dependencies": { - "shallowequal": "^1.1.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", - "dev": true, - "dependencies": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/number-to-bn/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obliterator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "dev": true, - "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ordinal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", - "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", - "dev": true - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/outdent": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", - "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", - "dev": true - }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/p-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", - "dev": true, - "dependencies": { - "p-map": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-filter/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", - "dev": true, - "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "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==", - "dev": true, - "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "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-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", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/preferred-pm": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.1.2.tgz", - "integrity": "sha512-nk7dKrcW8hfCZ4H6klWcdRknBOXWzNQByJ0oJyX97BOupsYD+FzLS4hflgEu/uPUEHZCuRfMxzCBsuWd7OzT8Q==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0", - "find-yarn-workspace-root2": "1.2.16", - "path-exists": "^4.0.0", - "which-pm": "2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/preferred-pm/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, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/preferred-pm/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, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/preferred-pm/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-solidity": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz", - "integrity": "sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==", - "dev": true, - "dependencies": { - "@solidity-parser/parser": "^0.16.0", - "semver": "^7.3.8", - "solidity-comments-extractor": "^0.0.7" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "prettier": ">=2.3.0 || >=3.0.0-alpha.0" - } - }, - "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-yaml-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", - "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.5", - "js-yaml": "^3.6.1", - "pify": "^4.0.1", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dev": true, - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redent/node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/registry-auth-token": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", - "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", - "dev": true, - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dev": true, - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", - "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", - "dev": true, - "dependencies": { - "glob": "^10.2.5" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "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", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.0" - }, - "bin": { - "rlp": "bin/rlp" - } - }, - "node_modules/rlp/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sc-istanbul": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", - "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", - "dev": true, - "dependencies": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "istanbul": "lib/cli.js" - } - }, - "node_modules/sc-istanbul/node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sc-istanbul/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "dev": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/sc-istanbul/node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sc-istanbul/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true - }, - "node_modules/sc-istanbul/node_modules/supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "dependencies": { - "has-flag": "^1.0.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/sc-istanbul/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "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==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", - "dev": true, - "dependencies": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "dev": true - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/shelljs/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "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 - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smartwrap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/smartwrap/-/smartwrap-2.0.2.tgz", - "integrity": "sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==", - "dev": true, - "dependencies": { - "array.prototype.flat": "^1.2.3", - "breakword": "^1.0.5", - "grapheme-splitter": "^1.0.4", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1", - "yargs": "^15.1.0" - }, - "bin": { - "smartwrap": "src/terminal-adapter.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/smartwrap/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smartwrap/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/smartwrap/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/smartwrap/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/smartwrap/node_modules/color-name": { - "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 - }, - "node_modules/smartwrap/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/smartwrap/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/smartwrap/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/smartwrap/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/smartwrap/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/smartwrap/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/solhint": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/solhint/-/solhint-4.5.2.tgz", - "integrity": "sha512-o7MNYS5QPgE6l+PTGOTAUtCzo0ZLnffQsv586hntSHBe2JbSDfkoxfhAOcjZjN4OesTgaX4UEEjCjH9y/4BP5w==", - "dev": true, - "dependencies": { - "@solidity-parser/parser": "^0.18.0", - "ajv": "^6.12.6", - "antlr4": "^4.13.1-patch-1", - "ast-parents": "^0.0.1", - "chalk": "^4.1.2", - "commander": "^10.0.0", - "cosmiconfig": "^8.0.0", - "fast-diff": "^1.2.0", - "glob": "^8.0.3", - "ignore": "^5.2.4", - "js-yaml": "^4.1.0", - "latest-version": "^7.0.0", - "lodash": "^4.17.21", - "pluralize": "^8.0.0", - "semver": "^7.5.2", - "strip-ansi": "^6.0.1", - "table": "^6.8.1", - "text-table": "^0.2.0" - }, - "bin": { - "solhint": "solhint.js" - }, - "optionalDependencies": { - "prettier": "^2.8.3" - } - }, - "node_modules/solhint-plugin-openzeppelin": { - "resolved": "scripts/solhint-custom", - "link": true - }, - "node_modules/solhint/node_modules/@solidity-parser/parser": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", - "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", - "dev": true - }, - "node_modules/solhint/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/solhint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/solhint/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/solhint/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/solhint/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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/solhint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/solhint/node_modules/color-name": { - "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 - }, - "node_modules/solhint/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/solhint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/solhint/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/solhint/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/solhint/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "optional": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/solhint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/solhint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/solidity-ast": { - "version": "0.4.52", - "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.52.tgz", - "integrity": "sha512-iOya9BSiB9jhM8Vf40n8lGELGzwrUc57rl5BhfNtJ5cvAaMvRcNlHeAMNvqJJyjoUnczqRbHqdivEqK89du3Cw==", - "dev": true, - "dependencies": { - "array.prototype.findlast": "^1.2.2" - } - }, - "node_modules/solidity-comments": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments/-/solidity-comments-0.0.2.tgz", - "integrity": "sha512-G+aK6qtyUfkn1guS8uzqUeua1dURwPlcOjoTYW/TwmXAcE7z/1+oGCfZUdMSe4ZMKklNbVZNiG5ibnF8gkkFfw==", - "dev": true, - "engines": { - "node": ">= 12" - }, - "optionalDependencies": { - "solidity-comments-darwin-arm64": "0.0.2", - "solidity-comments-darwin-x64": "0.0.2", - "solidity-comments-freebsd-x64": "0.0.2", - "solidity-comments-linux-arm64-gnu": "0.0.2", - "solidity-comments-linux-arm64-musl": "0.0.2", - "solidity-comments-linux-x64-gnu": "0.0.2", - "solidity-comments-linux-x64-musl": "0.0.2", - "solidity-comments-win32-arm64-msvc": "0.0.2", - "solidity-comments-win32-ia32-msvc": "0.0.2", - "solidity-comments-win32-x64-msvc": "0.0.2" - } - }, - "node_modules/solidity-comments-darwin-arm64": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-darwin-arm64/-/solidity-comments-darwin-arm64-0.0.2.tgz", - "integrity": "sha512-HidWkVLSh7v+Vu0CA7oI21GWP/ZY7ro8g8OmIxE8oTqyMwgMbE8F1yc58Sj682Hj199HCZsjmtn1BE4PCbLiGA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-darwin-x64": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-darwin-x64/-/solidity-comments-darwin-x64-0.0.2.tgz", - "integrity": "sha512-Zjs0Ruz6faBTPT6fBecUt6qh4CdloT8Bwoc0+qxRoTn9UhYscmbPQkUgQEbS0FQPysYqVzzxJB4h1Ofbf4wwtA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-extractor": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", - "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", - "dev": true - }, - "node_modules/solidity-comments-freebsd-x64": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-freebsd-x64/-/solidity-comments-freebsd-x64-0.0.2.tgz", - "integrity": "sha512-8Qe4mpjuAxFSwZJVk7B8gAoLCdbtS412bQzBwk63L8dmlHogvE39iT70aAk3RHUddAppT5RMBunlPUCFYJ3ZTw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-linux-arm64-gnu": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-linux-arm64-gnu/-/solidity-comments-linux-arm64-gnu-0.0.2.tgz", - "integrity": "sha512-spkb0MZZnmrP+Wtq4UxP+nyPAVRe82idOjqndolcNR0S9Xvu4ebwq+LvF4HiUgjTDmeiqYiFZQ8T9KGdLSIoIg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-linux-arm64-musl": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-linux-arm64-musl/-/solidity-comments-linux-arm64-musl-0.0.2.tgz", - "integrity": "sha512-guCDbHArcjE+JDXYkxx5RZzY1YF6OnAKCo+sTC5fstyW/KGKaQJNPyBNWuwYsQiaEHpvhW1ha537IvlGek8GqA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-linux-x64-gnu": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-linux-x64-gnu/-/solidity-comments-linux-x64-gnu-0.0.2.tgz", - "integrity": "sha512-zIqLehBK/g7tvrFmQljrfZXfkEeLt2v6wbe+uFu6kH/qAHZa7ybt8Vc0wYcmjo2U0PeBm15d79ee3AkwbIjFdQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-linux-x64-musl": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-linux-x64-musl/-/solidity-comments-linux-x64-musl-0.0.2.tgz", - "integrity": "sha512-R9FeDloVlFGTaVkOlELDVC7+1Tjx5WBPI5L8r0AGOPHK3+jOcRh6sKYpI+VskSPDc3vOO46INkpDgUXrKydlIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-win32-arm64-msvc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-win32-arm64-msvc/-/solidity-comments-win32-arm64-msvc-0.0.2.tgz", - "integrity": "sha512-QnWJoCQcJj+rnutULOihN9bixOtYWDdF5Rfz9fpHejL1BtNjdLW1om55XNVHGAHPqBxV4aeQQ6OirKnp9zKsug==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-win32-ia32-msvc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-win32-ia32-msvc/-/solidity-comments-win32-ia32-msvc-0.0.2.tgz", - "integrity": "sha512-vUg4nADtm/NcOtlIymG23NWJUSuMsvX15nU7ynhGBsdKtt8xhdP3C/zA6vjDk8Jg+FXGQL6IHVQ++g/7rSQi0w==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-comments-win32-x64-msvc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/solidity-comments-win32-x64-msvc/-/solidity-comments-win32-x64-msvc-0.0.2.tgz", - "integrity": "sha512-36j+KUF4V/y0t3qatHm/LF5sCUCBx2UndxE1kq5bOzh/s+nQgatuyB+Pd5BfuPQHdWu2KaExYe20FlAa6NL7+Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/solidity-coverage": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.5.tgz", - "integrity": "sha512-6C6N6OV2O8FQA0FWA95FdzVH+L16HU94iFgg5wAFZ29UpLFkgNI/DRR2HotG1bC0F4gAc/OMs2BJI44Q/DYlKQ==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.16.0", - "chalk": "^2.4.2", - "death": "^1.1.0", - "detect-port": "^1.3.0", - "difflib": "^0.2.4", - "fs-extra": "^8.1.0", - "ghost-testrpc": "^0.0.2", - "global-modules": "^2.0.0", - "globby": "^10.0.1", - "jsonschema": "^1.2.4", - "lodash": "^4.17.15", - "mocha": "10.2.0", - "node-emoji": "^1.10.0", - "pify": "^4.0.1", - "recursive-readdir": "^2.2.2", - "sc-istanbul": "^0.4.5", - "semver": "^7.3.4", - "shelljs": "^0.8.3", - "web3-utils": "^1.3.6" - }, - "bin": { - "solidity-coverage": "plugins/bin.js" - }, - "peerDependencies": { - "hardhat": "^2.11.0" - } - }, - "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz", - "integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==", - "dev": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "node_modules/solidity-coverage/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/solidity-coverage/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "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" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/solidity-coverage/node_modules/globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/solidity-docgen": { - "version": "0.6.0-beta.36", - "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.36.tgz", - "integrity": "sha512-f/I5G2iJgU1h0XrrjRD0hHMr7C10u276vYvm//rw1TzFcYQ4xTOyAoi9oNAHRU0JU4mY9eTuxdVc2zahdMuhaQ==", - "dev": true, - "dependencies": { - "handlebars": "^4.7.7", - "solidity-ast": "^0.4.38" - }, - "peerDependencies": { - "hardhat": "^2.8.0" - } - }, - "node_modules/source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", - "dev": true, - "optional": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawndamnit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-2.0.0.tgz", - "integrity": "sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==", - "dev": true, - "dependencies": { - "cross-spawn": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/spawndamnit/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/spawndamnit/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/spawndamnit/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawndamnit/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spawndamnit/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/spawndamnit/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz", - "integrity": "sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-transform": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-2.1.3.tgz", - "integrity": "sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==", - "dev": true, - "dependencies": { - "mixme": "^0.5.1" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", - "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", - "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", - "dev": true, - "dependencies": { - "is-hex-prefixed": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/table/node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsort": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", - "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", - "dev": true - }, - "node_modules/tty-table": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/tty-table/-/tty-table-4.2.1.tgz", - "integrity": "sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "csv": "^5.5.3", - "kleur": "^4.1.5", - "smartwrap": "^2.0.2", - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.1", - "yargs": "^17.7.1" - }, - "bin": { - "tty-table": "adapters/terminal-adapter.js" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/tty-table/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tty-table/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/tty-table/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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/tty-table/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/tty-table/node_modules/color-name": { - "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 - }, - "node_modules/tty-table/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/tty-table/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tty-table/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "node_modules/tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.12.0.tgz", - "integrity": "sha512-d87yk8lqSFUYtR5fTFe2frpkMIrUEz+lgoJmhcL+J3StVl+8fj8ytE4lLnJOTPCE12YbumNGzf4LYsQyusdV5g==", - "dev": true, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/viem": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.7.14.tgz", - "integrity": "sha512-5b1KB1gXli02GOQHZIUsRluNUwssl2t4hqdFAzyWPwJ744N83jAOBOjOkrGz7K3qMIv9b0GQt3DoZIErSQTPkQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "dependencies": { - "@adraffy/ens-normalize": "1.10.0", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@scure/bip32": "1.3.2", - "@scure/bip39": "1.2.1", - "abitype": "1.0.0", - "isows": "1.0.3", - "ws": "8.13.0" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/viem/node_modules/@adraffy/ens-normalize": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", - "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", - "dev": true - }, - "node_modules/viem/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "dev": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip32": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", - "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", - "dev": true, - "dependencies": { - "@noble/curves": "~1.2.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/web3-utils": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.2.tgz", - "integrity": "sha512-TdApdzdse5YR+5GCX/b/vQnhhbj1KSAtfrDtRW7YS0kcWp1gkJsN62gw6GzCaNTeXookB7UrLtmDUuMv65qgow==", - "dev": true, - "dependencies": { - "@ethereumjs/util": "^8.1.0", - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereum-cryptography": "^2.1.2", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-utils/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/web3-utils/node_modules/ethereum-cryptography": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz", - "integrity": "sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug==", - "dev": true, - "dependencies": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@scure/bip32": "1.3.1", - "@scure/bip39": "1.2.1" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, - "node_modules/which-pm": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", - "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", - "dev": true, - "dependencies": { - "load-yaml-file": "^0.2.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8.15" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "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 - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "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 - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs-parser/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "scripts/solhint-custom": { - "name": "solhint-plugin-openzeppelin", - "version": "0.0.0", - "dev": true - } - } -} diff --git a/lib_openzeppelin_contracts/package.json b/lib_openzeppelin_contracts/package.json deleted file mode 100644 index 2a79994..0000000 --- a/lib_openzeppelin_contracts/package.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "openzeppelin-solidity", - "description": "Secure Smart Contract library for Solidity", - "version": "5.0.2", - "private": true, - "files": [ - "/contracts/**/*.sol", - "!/contracts/mocks/**/*" - ], - "scripts": { - "compile": "hardhat compile", - "compile:harnesses": "env SRC=./certora/harnesses hardhat compile", - "coverage": "env COVERAGE=true hardhat coverage", - "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-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: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", - "prepack": "scripts/prepack.sh", - "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", - "gas-report": "env ENABLE_GAS_REPORT=true npm run test", - "slither": "npm run clean && slither ." - }, - "repository": { - "type": "git", - "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" - }, - "keywords": [ - "solidity", - "ethereum", - "smart", - "contracts", - "security", - "zeppelin" - ], - "author": "OpenZeppelin Community ", - "license": "MIT", - "bugs": { - "url": "https://github.com/OpenZeppelin/openzeppelin-contracts/issues" - }, - "homepage": "https://openzeppelin.com/contracts/", - "devDependencies": { - "@changesets/changelog-github": "^0.5.0", - "@changesets/cli": "^2.26.0", - "@changesets/pre": "^2.0.0", - "@changesets/read": "^0.6.0", - "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", - "@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/upgrade-safe-transpiler": "^0.3.32", - "@openzeppelin/upgrades-core": "^1.20.6", - "chai": "^4.2.0", - "eslint": "^8.30.0", - "eslint-config-prettier": "^9.0.0", - "ethers": "^6.7.1", - "glob": "^10.3.5", - "graphlib": "^2.1.8", - "hardhat": "^2.22.2", - "hardhat-exposed": "^0.3.15", - "hardhat-gas-reporter": "^2.0.0", - "hardhat-ignore-warnings": "^0.2.11", - "lodash.startcase": "^4.4.0", - "micromatch": "^4.0.2", - "p-limit": "^3.1.0", - "prettier": "^3.0.0", - "prettier-plugin-solidity": "^1.1.0", - "rimraf": "^5.0.1", - "semver": "^7.3.5", - "solhint": "^4.0.0", - "solhint-plugin-openzeppelin": "file:scripts/solhint-custom", - "solidity-ast": "^0.4.50", - "solidity-coverage": "^0.8.5", - "solidity-docgen": "^0.6.0-beta.29", - "undici": "^6.11.1", - "yargs": "^17.0.0" - } -} diff --git a/lib_openzeppelin_contracts/remappings.txt b/lib_openzeppelin_contracts/remappings.txt deleted file mode 100644 index 304d138..0000000 --- a/lib_openzeppelin_contracts/remappings.txt +++ /dev/null @@ -1 +0,0 @@ -@openzeppelin/contracts/=contracts/ diff --git a/lib_openzeppelin_contracts/renovate.json b/lib_openzeppelin_contracts/renovate.json deleted file mode 100644 index c0b97d8..0000000 --- a/lib_openzeppelin_contracts/renovate.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": ["github>OpenZeppelin/configs"], - "labels": ["ignore-changeset"] -} diff --git a/lib_openzeppelin_contracts/requirements.txt b/lib_openzeppelin_contracts/requirements.txt deleted file mode 100644 index bdea09a..0000000 --- a/lib_openzeppelin_contracts/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -certora-cli==4.13.1 diff --git a/lib_openzeppelin_contracts/scripts/checks/compare-layout.js b/lib_openzeppelin_contracts/scripts/checks/compare-layout.js deleted file mode 100644 index 4368b77..0000000 --- a/lib_openzeppelin_contracts/scripts/checks/compare-layout.js +++ /dev/null @@ -1,20 +0,0 @@ -const fs = require('fs'); -const { getStorageUpgradeReport } = require('@openzeppelin/upgrades-core/dist/storage'); - -const { ref, head } = require('yargs').argv; - -const oldLayout = JSON.parse(fs.readFileSync(ref)); -const newLayout = JSON.parse(fs.readFileSync(head)); - -for (const name in oldLayout) { - if (name in newLayout) { - const report = getStorageUpgradeReport(oldLayout[name], newLayout[name], {}); - if (!report.ok) { - console.log(`Storage layout incompatilibity found in ${name}:`); - console.log(report.explain()); - process.exitCode = 1; - } - } else { - console.log(`WARNING: ${name} is missing from the current branch`); - } -} diff --git a/lib_openzeppelin_contracts/scripts/checks/compareGasReports.js b/lib_openzeppelin_contracts/scripts/checks/compareGasReports.js deleted file mode 100755 index 2c7b4dc..0000000 --- a/lib_openzeppelin_contracts/scripts/checks/compareGasReports.js +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const chalk = require('chalk'); -const { argv } = require('yargs') - .env() - .options({ - style: { - type: 'string', - choices: ['shell', 'markdown'], - default: 'shell', - }, - hideEqual: { - type: 'boolean', - default: true, - }, - strictTesting: { - type: 'boolean', - default: false, - }, - }); - -// Deduce base tx cost from the percentage denominator -const BASE_TX_COST = 21000; - -// Utilities -function sum(...args) { - return args.reduce((a, b) => a + b, 0); -} - -function average(...args) { - return sum(...args) / args.length; -} - -function variation(current, previous, offset = 0) { - return { - value: current, - delta: current - previous, - prcnt: (100 * (current - previous)) / (previous - offset), - }; -} - -// Report class -class Report { - // Read report file - static load(filepath) { - return JSON.parse(fs.readFileSync(filepath, 'utf8')); - } - - // Compare two reports - static compare(update, ref, opts = { hideEqual: true, strictTesting: false }) { - if (JSON.stringify(update.options?.solcInfo) !== JSON.stringify(ref.options?.solcInfo)) { - console.warn('WARNING: Reports produced with non matching metadata'); - } - - // gasReporter 1.0.0 uses ".info", but 2.0.0 uses ".data" - const updateInfo = update.info ?? update.data; - const refInfo = ref.info ?? ref.data; - - const deployments = updateInfo.deployments - .map(contract => - Object.assign(contract, { previousVersion: refInfo.deployments.find(({ name }) => name === contract.name) }), - ) - .filter(contract => contract.gasData?.length && contract.previousVersion?.gasData?.length) - .flatMap(contract => [ - { - contract: contract.name, - method: '[bytecode length]', - avg: variation(contract.bytecode.length / 2 - 1, contract.previousVersion.bytecode.length / 2 - 1), - }, - { - contract: contract.name, - method: '[construction cost]', - avg: variation( - ...[contract.gasData, contract.previousVersion.gasData].map(x => Math.round(average(...x))), - BASE_TX_COST, - ), - }, - ]) - .sort((a, b) => `${a.contract}:${a.method}`.localeCompare(`${b.contract}:${b.method}`)); - - const methods = Object.keys(updateInfo.methods) - .filter(key => refInfo.methods[key]) - .filter(key => updateInfo.methods[key].numberOfCalls > 0) - .filter( - key => !opts.strictTesting || updateInfo.methods[key].numberOfCalls === refInfo.methods[key].numberOfCalls, - ) - .map(key => ({ - contract: refInfo.methods[key].contract, - method: refInfo.methods[key].fnSig, - min: variation(...[updateInfo, refInfo].map(x => Math.min(...x.methods[key].gasData)), BASE_TX_COST), - max: variation(...[updateInfo, refInfo].map(x => Math.max(...x.methods[key].gasData)), BASE_TX_COST), - avg: variation(...[updateInfo, refInfo].map(x => Math.round(average(...x.methods[key].gasData))), BASE_TX_COST), - })) - .sort((a, b) => `${a.contract}:${a.method}`.localeCompare(`${b.contract}:${b.method}`)); - - return [] - .concat(deployments, methods) - .filter(row => !opts.hideEqual || row.min?.delta || row.max?.delta || row.avg?.delta); - } -} - -// Display -function center(text, length) { - return text.padStart((text.length + length) / 2).padEnd(length); -} - -function plusSign(num) { - return num > 0 ? '+' : ''; -} - -function formatCellShell(cell) { - const format = chalk[cell?.delta > 0 ? 'red' : cell?.delta < 0 ? 'green' : 'reset']; - return [ - format((!isFinite(cell?.value) ? '-' : cell.value.toString()).padStart(8)), - format((!isFinite(cell?.delta) ? '-' : plusSign(cell.delta) + cell.delta.toString()).padStart(8)), - format((!isFinite(cell?.prcnt) ? '-' : plusSign(cell.prcnt) + cell.prcnt.toFixed(2) + '%').padStart(8)), - ]; -} - -function formatCmpShell(rows) { - const contractLength = Math.max(8, ...rows.map(({ contract }) => contract.length)); - const methodLength = Math.max(7, ...rows.map(({ method }) => method.length)); - - const COLS = [ - { txt: '', length: 0 }, - { txt: 'Contract', length: contractLength }, - { txt: 'Method', length: methodLength }, - { txt: 'Min', length: 30 }, - { txt: 'Max', length: 30 }, - { txt: 'Avg', length: 30 }, - { txt: '', length: 0 }, - ]; - const HEADER = COLS.map(entry => chalk.bold(center(entry.txt, entry.length || 0))) - .join(' | ') - .trim(); - const SEPARATOR = COLS.map(({ length }) => (length > 0 ? '-'.repeat(length + 2) : '')) - .join('|') - .trim(); - - return [ - '', - HEADER, - ...rows.map(entry => - [ - '', - chalk.grey(entry.contract.padEnd(contractLength)), - entry.method.padEnd(methodLength), - ...formatCellShell(entry.min), - ...formatCellShell(entry.max), - ...formatCellShell(entry.avg), - '', - ] - .join(' | ') - .trim(), - ), - '', - ] - .join(`\n${SEPARATOR}\n`) - .trim(); -} - -function alignPattern(align) { - switch (align) { - case 'left': - case undefined: - return ':-'; - case 'right': - return '-:'; - case 'center': - return ':-:'; - } -} - -function trend(value) { - return value > 0 ? ':x:' : value < 0 ? ':heavy_check_mark:' : ':heavy_minus_sign:'; -} - -function formatCellMarkdown(cell) { - return [ - !isFinite(cell?.value) ? '-' : cell.value.toString(), - !isFinite(cell?.delta) ? '-' : plusSign(cell.delta) + cell.delta.toString(), - !isFinite(cell?.prcnt) ? '-' : plusSign(cell.prcnt) + cell.prcnt.toFixed(2) + '% ' + trend(cell.delta), - ]; -} - -function formatCmpMarkdown(rows) { - const COLS = [ - { txt: '' }, - { txt: 'Contract', align: 'left' }, - { txt: 'Method', align: 'left' }, - { txt: 'Min', align: 'right' }, - { txt: '(+/-)', align: 'right' }, - { txt: '%', align: 'right' }, - { txt: 'Max', align: 'right' }, - { txt: '(+/-)', align: 'right' }, - { txt: '%', align: 'right' }, - { txt: 'Avg', align: 'right' }, - { txt: '(+/-)', align: 'right' }, - { txt: '%', align: 'right' }, - { txt: '' }, - ]; - const HEADER = COLS.map(entry => entry.txt) - .join(' | ') - .trim(); - const SEPARATOR = COLS.map(entry => (entry.txt ? alignPattern(entry.align) : '')) - .join('|') - .trim(); - - return [ - '# Changes to gas costs', - '', - HEADER, - SEPARATOR, - rows - .map(entry => - [ - '', - entry.contract, - entry.method, - ...formatCellMarkdown(entry.min), - ...formatCellMarkdown(entry.max), - ...formatCellMarkdown(entry.avg), - '', - ] - .join(' | ') - .trim(), - ) - .join('\n'), - '', - ] - .join('\n') - .trim(); -} - -// MAIN -const report = Report.compare(Report.load(argv._[0]), Report.load(argv._[1]), argv); - -switch (argv.style) { - case 'markdown': - console.log(formatCmpMarkdown(report)); - break; - case 'shell': - default: - console.log(formatCmpShell(report)); - break; -} diff --git a/lib_openzeppelin_contracts/scripts/checks/extract-layout.js b/lib_openzeppelin_contracts/scripts/checks/extract-layout.js deleted file mode 100644 index d0b9965..0000000 --- a/lib_openzeppelin_contracts/scripts/checks/extract-layout.js +++ /dev/null @@ -1,38 +0,0 @@ -const fs = require('fs'); -const { findAll, astDereferencer, srcDecoder } = require('solidity-ast/utils'); -const { extractStorageLayout } = require('@openzeppelin/upgrades-core/dist/storage/extract'); - -const { _ } = require('yargs').argv; - -const skipPath = ['contracts/mocks/', 'contracts-exposed/']; -const skipKind = ['interface', 'library']; - -function extractLayouts(path) { - const layout = {}; - const { input, output } = JSON.parse(fs.readFileSync(path)); - - const decoder = srcDecoder(input, output); - const deref = astDereferencer(output); - - for (const src in output.contracts) { - if (skipPath.some(prefix => src.startsWith(prefix))) { - continue; - } - - for (const contractDef of findAll('ContractDefinition', output.sources[src].ast)) { - if (skipKind.includes(contractDef.contractKind)) { - continue; - } - - layout[contractDef.name] = extractStorageLayout( - contractDef, - decoder, - deref, - output.contracts[src][contractDef.name].storageLayout, - ); - } - } - return layout; -} - -console.log(JSON.stringify(Object.assign(..._.map(extractLayouts)))); diff --git a/lib_openzeppelin_contracts/scripts/checks/generation.sh b/lib_openzeppelin_contracts/scripts/checks/generation.sh deleted file mode 100755 index 00d609f..0000000 --- a/lib_openzeppelin_contracts/scripts/checks/generation.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -npm run generate -git diff -R --exit-code diff --git a/lib_openzeppelin_contracts/scripts/checks/inheritance-ordering.js b/lib_openzeppelin_contracts/scripts/checks/inheritance-ordering.js deleted file mode 100755 index 72aa37e..0000000 --- a/lib_openzeppelin_contracts/scripts/checks/inheritance-ordering.js +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const graphlib = require('graphlib'); -const { findAll } = require('solidity-ast/utils'); -const { _: artifacts } = require('yargs').argv; - -for (const artifact of artifacts) { - const { output: solcOutput } = require(path.resolve(__dirname, '../..', artifact)); - - const graph = new graphlib.Graph({ directed: true }); - const names = {}; - const linearized = []; - - for (const source in solcOutput.contracts) { - if (['contracts-exposed/', 'contracts/mocks/'].some(pattern => source.startsWith(pattern))) { - continue; - } - - for (const contractDef of findAll('ContractDefinition', solcOutput.sources[source].ast)) { - names[contractDef.id] = contractDef.name; - linearized.push(contractDef.linearizedBaseContracts); - - contractDef.linearizedBaseContracts.forEach((c1, i, contracts) => - contracts.slice(i + 1).forEach(c2 => { - graph.setEdge(c1, c2); - }), - ); - } - } - - /// graphlib.alg.findCycles will not find minimal cycles. - /// We are only interested int cycles of lengths 2 (needs proof) - graph.nodes().forEach((x, i, nodes) => - nodes - .slice(i + 1) - .filter(y => graph.hasEdge(x, y) && graph.hasEdge(y, x)) - .forEach(y => { - console.log(`Conflict between ${names[x]} and ${names[y]} detected in the following dependency chains:`); - linearized - .filter(chain => chain.includes(parseInt(x)) && chain.includes(parseInt(y))) - .forEach(chain => { - const comp = chain.indexOf(parseInt(x)) < chain.indexOf(parseInt(y)) ? '>' : '<'; - console.log(`- ${names[x]} ${comp} ${names[y]} in ${names[chain.find(Boolean)]}`); - // console.log(`- ${names[x]} ${comp} ${names[y]}: ${chain.reverse().map(id => names[id]).join(', ')}`); - }); - process.exitCode = 1; - }), - ); -} - -if (!process.exitCode) { - console.log('Contract ordering is consistent.'); -} diff --git a/lib_openzeppelin_contracts/scripts/gen-nav.js b/lib_openzeppelin_contracts/scripts/gen-nav.js deleted file mode 100644 index de3d0da..0000000 --- a/lib_openzeppelin_contracts/scripts/gen-nav.js +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const glob = require('glob'); -const startCase = require('lodash.startcase'); - -const baseDir = process.argv[2]; - -const files = glob.sync(baseDir + '/**/*.adoc').map(f => path.relative(baseDir, f)); - -console.log('.API'); - -function getPageTitle(directory) { - switch (directory) { - case 'metatx': - return 'Meta Transactions'; - case 'common': - return 'Common (Tokens)'; - default: - return startCase(directory); - } -} - -const links = files.map(file => { - const doc = file.replace(baseDir, ''); - const title = path.parse(file).name; - - return { - xref: `* xref:${doc}[${getPageTitle(title)}]`, - title, - }; -}); - -// Case-insensitive sort based on titles (so 'token/ERC20' gets sorted as 'erc20') -const sortedLinks = links.sort(function (a, b) { - return a.title.toLowerCase().localeCompare(b.title.toLowerCase(), undefined, { numeric: true }); -}); - -for (const link of sortedLinks) { - console.log(link.xref); -} diff --git a/lib_openzeppelin_contracts/scripts/generate/format-lines.js b/lib_openzeppelin_contracts/scripts/generate/format-lines.js deleted file mode 100644 index fa3d6b1..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/format-lines.js +++ /dev/null @@ -1,16 +0,0 @@ -function formatLines(...lines) { - return [...indentEach(0, lines)].join('\n') + '\n'; -} - -function* indentEach(indent, lines) { - for (const line of lines) { - if (Array.isArray(line)) { - yield* indentEach(indent + 1, line); - } else { - const padding = ' '.repeat(indent); - yield* line.split('\n').map(subline => (subline === '' ? '' : padding + subline)); - } - } -} - -module.exports = formatLines; diff --git a/lib_openzeppelin_contracts/scripts/generate/run.js b/lib_openzeppelin_contracts/scripts/generate/run.js deleted file mode 100755 index 1b7d810..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/run.js +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env node - -const cp = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const format = require('./format-lines'); - -function getVersion(path) { - try { - return fs.readFileSync(path, 'utf8').match(/\/\/ OpenZeppelin Contracts \(last updated v[^)]+\)/)[0]; - } catch (err) { - return null; - } -} - -function generateFromTemplate(file, template, outputPrefix = '') { - const script = path.relative(path.join(__dirname, '../..'), __filename); - const input = path.join(path.dirname(script), template); - const output = path.join(outputPrefix, file); - const version = getVersion(output); - const content = format( - '// SPDX-License-Identifier: MIT', - ...(version ? [version + ` (${file})`] : []), - `// This file was procedurally generated from ${input}.`, - '', - require(template), - ); - - fs.writeFileSync(output, content); - cp.execFileSync('prettier', ['--write', output]); -} - -// Contracts -for (const [file, template] of Object.entries({ - 'utils/math/SafeCast.sol': './templates/SafeCast.js', - 'utils/structs/EnumerableSet.sol': './templates/EnumerableSet.js', - 'utils/structs/EnumerableMap.sol': './templates/EnumerableMap.js', - 'utils/structs/Checkpoints.sol': './templates/Checkpoints.js', - 'utils/SlotDerivation.sol': './templates/SlotDerivation.js', - 'utils/StorageSlot.sol': './templates/StorageSlot.js', - 'utils/Arrays.sol': './templates/Arrays.js', - 'mocks/StorageSlotMock.sol': './templates/StorageSlotMock.js', -})) { - generateFromTemplate(file, template, './contracts/'); -} - -// Tests -for (const [file, template] of Object.entries({ - 'utils/structs/Checkpoints.t.sol': './templates/Checkpoints.t.js', - 'utils/SlotDerivation.t.sol': './templates/SlotDerivation.t.js', -})) { - generateFromTemplate(file, template, './test/'); -} diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/Arrays.js b/lib_openzeppelin_contracts/scripts/generate/templates/Arrays.js deleted file mode 100644 index 6166d6f..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/Arrays.js +++ /dev/null @@ -1,385 +0,0 @@ -const format = require('../format-lines'); -const { capitalize } = require('../../helpers'); -const { TYPES } = require('./Arrays.opts'); - -const header = `\ -pragma solidity ^0.8.20; - -import {SlotDerivation} from "./SlotDerivation.sol"; -import {StorageSlot} from "./StorageSlot.sol"; -import {Math} from "./math/Math.sol"; - -/** - * @dev Collection of functions related to array types. - */ -`; - -const sort = type => `\ - /** - * @dev Sort an array of ${type} (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. - * - * NOTE: this function's cost is \`O(n · log(n))\` in average and \`O(n²)\` in the worst case, with n the length of the - * 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. - */ - function sort( - ${type}[] memory array, - function(${type}, ${type}) pure returns (bool) comp - ) internal pure returns (${type}[] memory) { - ${ - type === 'bytes32' - ? '_quickSort(_begin(array), _end(array), comp);' - : 'sort(_castToBytes32Array(array), _castToBytes32Comp(comp));' - } - return array; - } - - /** - * @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);'} - return array; - } -`; - -const quickSort = ` -/** - * @dev Performs a quick sort of a segment of memory. The segment sorted starts at \`begin\` (inclusive), and stops - * at end (exclusive). Sorting follows the \`comp\` comparator. - * - * Invariant: \`begin <= end\`. This is the case when initially called by {sort} and is preserved in subcalls. - * - * 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 { - unchecked { - if (end - begin < 0x40) return; - - // Use first element as pivot - bytes32 pivot = _mload(begin); - // Position where the pivot should be at the end of the loop - uint256 pos = begin; - - for (uint256 it = begin + 0x20; it < end; it += 0x20) { - if (comp(_mload(it), pivot)) { - // If the value stored at the iterator's position comes before the pivot, we increment the - // position of the pivot and move the value there. - pos += 0x20; - _swap(pos, it); - } - } - - _swap(begin, pos); // Swap pivot into place - _quickSort(begin, pos, comp); // Sort the left side of the pivot - _quickSort(pos + 0x20, end, comp); // Sort the right side of the pivot - } -} - -/** - * @dev Pointer to the memory location of the first element of \`array\`. - */ -function _begin(bytes32[] memory array) private pure returns (uint256 ptr) { - /// @solidity memory-safe-assembly - assembly { - ptr := add(array, 0x20) - } -} - -/** - * @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) { - unchecked { - return _begin(array) + array.length * 0x20; - } -} - -/** - * @dev Load memory word (as a bytes32) at location \`ptr\`. - */ -function _mload(uint256 ptr) private pure returns (bytes32 value) { - assembly { - value := mload(ptr) - } -} - -/** - * @dev Swaps the elements memory location \`ptr1\` and \`ptr2\`. - */ -function _swap(uint256 ptr1, uint256 ptr2) private pure { - assembly { - let value1 := mload(ptr1) - let value2 := mload(ptr2) - mstore(ptr1, value2) - mstore(ptr2, value1) - } -} -`; - -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) { - assembly { - output := input - } - } -`; - -const castComparator = type => `\ - /// @dev Helper: low level cast ${type} comp function to bytes32 comp function - function _castToBytes32Comp( - function(${type}, ${type}) pure returns (bool) input - ) private pure returns (function(bytes32, bytes32) pure returns (bool) output) { - assembly { - output := input - } - } -`; - -const search = ` -/** - * @dev Searches a sorted \`array\` and returns the first index that contains - * a value greater or equal to \`element\`. If no such index exists (i.e. all - * values in the array are strictly less than \`element\`), the array length is - * returned. Time complexity O(log n). - * - * NOTE: The \`array\` is expected to be sorted in ascending order, and to - * contain no repeated elements. - * - * IMPORTANT: Deprecated. This implementation behaves as {lowerBound} but lacks - * support for repeated elements in the array. The {lowerBound} function should - * be used instead. - */ -function findUpperBound(uint256[] storage array, uint256 element) internal view returns (uint256) { - uint256 low = 0; - uint256 high = array.length; - - if (high == 0) { - return 0; - } - - while (low < high) { - uint256 mid = Math.average(low, high); - - // Note that mid will always be strictly less than high (i.e. it will be a valid array index) - // because Math.average rounds towards zero (it does integer division with truncation). - if (unsafeAccess(array, mid).value > element) { - high = mid; - } else { - low = mid + 1; - } - } - - // At this point \`low\` is the exclusive upper bound. We will return the inclusive upper bound. - if (low > 0 && unsafeAccess(array, low - 1).value == element) { - return low - 1; - } else { - return low; - } -} - -/** - * @dev Searches an \`array\` sorted in ascending order and returns the first - * index that contains a value greater or equal than \`element\`. If no such index - * exists (i.e. all values in the array are strictly less than \`element\`), the array - * length is returned. Time complexity O(log n). - * - * See C++'s https://en.cppreference.com/w/cpp/algorithm/lower_bound[lower_bound]. - */ -function lowerBound(uint256[] storage array, uint256 element) internal view returns (uint256) { - uint256 low = 0; - uint256 high = array.length; - - if (high == 0) { - return 0; - } - - while (low < high) { - uint256 mid = Math.average(low, high); - - // Note that mid will always be strictly less than high (i.e. it will be a valid array index) - // because Math.average rounds towards zero (it does integer division with truncation). - if (unsafeAccess(array, mid).value < element) { - // this cannot overflow because mid < high - unchecked { - low = mid + 1; - } - } else { - high = mid; - } - } - - return low; -} - -/** - * @dev Searches an \`array\` sorted in ascending order and returns the first - * index that contains a value strictly greater than \`element\`. If no such index - * exists (i.e. all values in the array are strictly less than \`element\`), the array - * length is returned. Time complexity O(log n). - * - * See C++'s https://en.cppreference.com/w/cpp/algorithm/upper_bound[upper_bound]. - */ -function upperBound(uint256[] storage array, uint256 element) internal view returns (uint256) { - uint256 low = 0; - uint256 high = array.length; - - if (high == 0) { - return 0; - } - - while (low < high) { - uint256 mid = Math.average(low, high); - - // Note that mid will always be strictly less than high (i.e. it will be a valid array index) - // because Math.average rounds towards zero (it does integer division with truncation). - if (unsafeAccess(array, mid).value > element) { - high = mid; - } else { - // this cannot overflow because mid < high - unchecked { - low = mid + 1; - } - } - } - - return low; -} - -/** - * @dev Same as {lowerBound}, but with an array in memory. - */ -function lowerBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) { - uint256 low = 0; - uint256 high = array.length; - - if (high == 0) { - return 0; - } - - while (low < high) { - uint256 mid = Math.average(low, high); - - // Note that mid will always be strictly less than high (i.e. it will be a valid array index) - // because Math.average rounds towards zero (it does integer division with truncation). - if (unsafeMemoryAccess(array, mid) < element) { - // this cannot overflow because mid < high - unchecked { - low = mid + 1; - } - } else { - high = mid; - } - } - - return low; -} - -/** - * @dev Same as {upperBound}, but with an array in memory. - */ -function upperBoundMemory(uint256[] memory array, uint256 element) internal pure returns (uint256) { - uint256 low = 0; - uint256 high = array.length; - - if (high == 0) { - return 0; - } - - while (low < high) { - uint256 mid = Math.average(low, high); - - // Note that mid will always be strictly less than high (i.e. it will be a valid array index) - // because Math.average rounds towards zero (it does integer division with truncation). - if (unsafeMemoryAccess(array, mid) > element) { - high = mid; - } else { - // this cannot overflow because mid < high - unchecked { - low = mid + 1; - } - } - } - - return low; -} -`; - -const unsafeAccessStorage = type => ` -/** -* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. -* -* WARNING: Only use if you are certain \`pos\` is lower than the array length. -*/ -function unsafeAccess(${type}[] storage arr, uint256 pos) internal pure returns (StorageSlot.${capitalize( - type, -)}Slot storage) { - bytes32 slot; - /// @solidity memory-safe-assembly - assembly { - slot := arr.slot - } - return slot.deriveArray().offset(pos).get${capitalize(type)}Slot(); -}`; - -const unsafeAccessMemory = type => ` -/** - * @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. - * - * WARNING: Only use if you are certain \`pos\` is lower than the array length. - */ -function unsafeMemoryAccess(${type}[] memory arr, uint256 pos) internal pure returns (${type} res) { - assembly { - res := mload(add(add(arr, 0x20), mul(pos, 0x20))) - } -} -`; - -const unsafeSetLength = type => ` -/** - * @dev Helper to set the length of an dynamic array. Directly writing to \`.length\` is forbidden. - * - * 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 { - sstore(array.slot, len) - } -}`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library Arrays {', - 'using SlotDerivation for bytes32;', - 'using StorageSlot for bytes32;', - // sorting, comparator, helpers and internal - sort('bytes32'), - TYPES.filter(type => type !== 'bytes32').map(sort), - quickSort, - defaultComparator, - TYPES.filter(type => type !== 'bytes32').map(castArray), - TYPES.filter(type => type !== 'bytes32').map(castComparator), - // lookup - search, - // unsafe (direct) storage and memory access - TYPES.map(unsafeAccessStorage), - TYPES.map(unsafeAccessMemory), - TYPES.map(unsafeSetLength), - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/Arrays.opts.js b/lib_openzeppelin_contracts/scripts/generate/templates/Arrays.opts.js deleted file mode 100644 index 67f3299..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/Arrays.opts.js +++ /dev/null @@ -1,3 +0,0 @@ -const TYPES = ['address', 'bytes32', 'uint256']; - -module.exports = { TYPES }; diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.js b/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.js deleted file mode 100644 index 0a677e2..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.js +++ /dev/null @@ -1,248 +0,0 @@ -const format = require('../format-lines'); -const { OPTS } = require('./Checkpoints.opts'); - -// TEMPLATE -const header = `\ -pragma solidity ^0.8.20; - -import {Math} from "../math/Math.sol"; - -/** - * @dev This library defines the \`Trace*\` struct, for checkpointing values as they change at different points in - * time, and later looking up past values by block number. See {Votes} as an example. - * - * To create a history of checkpoints define a variable type \`Checkpoints.Trace*\` in your contract, and store a new - * checkpoint for the current transaction block using the {push} function. - */ -`; - -const errors = `\ - /** - * @dev A value was attempted to be inserted on a past checkpoint. - */ - error CheckpointUnorderedInsertion(); -`; - -const template = opts => `\ -struct ${opts.historyTypeName} { - ${opts.checkpointTypeName}[] ${opts.checkpointFieldName}; -} - -struct ${opts.checkpointTypeName} { - ${opts.keyTypeName} ${opts.keyFieldName}; - ${opts.valueTypeName} ${opts.valueFieldName}; -} - -/** - * @dev Pushes a (\`key\`, \`value\`) pair into a ${opts.historyTypeName} so that it is stored as the checkpoint. - * - * Returns previous value and new value. - * - * 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}) { - return _insert(self.${opts.checkpointFieldName}, key, value); -} - -/** - * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if - * there is none. - */ -function lowerLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) { - uint256 len = self.${opts.checkpointFieldName}.length; - uint256 pos = _lowerBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len); - return pos == len ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos).${opts.valueFieldName}; -} - -/** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero - * if there is none. - */ -function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) { - uint256 len = self.${opts.checkpointFieldName}.length; - uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len); - return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; -} - -/** - * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero - * if there is none. - * - * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high - * keys). - */ -function upperLookupRecent(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) { - uint256 len = self.${opts.checkpointFieldName}.length; - - uint256 low = 0; - uint256 high = len; - - if (len > 5) { - uint256 mid = len - Math.sqrt(len); - if (key < _unsafeAccess(self.${opts.checkpointFieldName}, mid)._key) { - high = mid; - } else { - low = mid + 1; - } - } - - uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, low, high); - - return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; -} - -/** - * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. - */ -function latest(${opts.historyTypeName} storage self) internal view returns (${opts.valueTypeName}) { - uint256 pos = self.${opts.checkpointFieldName}.length; - return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName}; -} - -/** - * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value - * in the most recent checkpoint. - */ -function latestCheckpoint(${opts.historyTypeName} storage self) - internal - view - returns ( - bool exists, - ${opts.keyTypeName} ${opts.keyFieldName}, - ${opts.valueTypeName} ${opts.valueFieldName} - ) -{ - uint256 pos = self.${opts.checkpointFieldName}.length; - if (pos == 0) { - return (false, 0, 0); - } else { - ${opts.checkpointTypeName} storage ckpt = _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1); - return (true, ckpt.${opts.keyFieldName}, ckpt.${opts.valueFieldName}); - } -} - -/** - * @dev Returns the number of checkpoint. - */ -function length(${opts.historyTypeName} storage self) internal view returns (uint256) { - return self.${opts.checkpointFieldName}.length; -} - -/** - * @dev Returns checkpoint at given position. - */ -function at(${opts.historyTypeName} storage self, uint32 pos) internal view returns (${opts.checkpointTypeName} memory) { - return self.${opts.checkpointFieldName}[pos]; -} - -/** - * @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}) { - uint256 pos = self.length; - - if (pos > 0) { - ${opts.checkpointTypeName} storage last = _unsafeAccess(self, pos - 1); - ${opts.keyTypeName} lastKey = last.${opts.keyFieldName}; - ${opts.valueTypeName} lastValue = last.${opts.valueFieldName}; - - // Checkpoint keys must be non-decreasing. - if (lastKey > key) { - revert CheckpointUnorderedInsertion(); - } - - // Update or push new checkpoint - if (lastKey == key) { - _unsafeAccess(self, pos - 1).${opts.valueFieldName} = value; - } else { - self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value})); - } - return (lastValue, value); - } else { - self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value})); - return (0, value); - } -} - -/** - * @dev Return the index of the last (most recent) checkpoint with key lower 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. - */ -function _upperBinaryLookup( - ${opts.checkpointTypeName}[] storage self, - ${opts.keyTypeName} key, - uint256 low, - uint256 high -) private view returns (uint256) { - while (low < high) { - uint256 mid = Math.average(low, high); - if (_unsafeAccess(self, mid).${opts.keyFieldName} > key) { - high = mid; - } else { - low = mid + 1; - } - } - return high; -} - -/** - * @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\`. - * - * WARNING: \`high\` should not be greater than the array's length. - */ -function _lowerBinaryLookup( - ${opts.checkpointTypeName}[] storage self, - ${opts.keyTypeName} key, - uint256 low, - uint256 high -) private view returns (uint256) { - while (low < high) { - uint256 mid = Math.average(low, high); - if (_unsafeAccess(self, mid).${opts.keyFieldName} < key) { - low = mid + 1; - } else { - high = mid; - } - } - return high; -} - -/** - * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. - */ -function _unsafeAccess(${opts.checkpointTypeName}[] storage self, uint256 pos) - private - pure - returns (${opts.checkpointTypeName} storage result) -{ - assembly { - mstore(0, self.slot) - result.slot := add(keccak256(0, 0x20), pos) - } -} -`; -/* eslint-enable max-len */ - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library Checkpoints {', - errors, - OPTS.flatMap(opts => template(opts)), - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.opts.js b/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.opts.js deleted file mode 100644 index 08b7b91..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.opts.js +++ /dev/null @@ -1,17 +0,0 @@ -// OPTIONS -const VALUE_SIZES = [224, 208, 160]; - -const defaultOpts = size => ({ - historyTypeName: `Trace${size}`, - checkpointTypeName: `Checkpoint${size}`, - checkpointFieldName: '_checkpoints', - keyTypeName: `uint${256 - size}`, - keyFieldName: '_key', - valueTypeName: `uint${size}`, - valueFieldName: '_value', -}); - -module.exports = { - VALUE_SIZES, - OPTS: VALUE_SIZES.map(size => defaultOpts(size)), -}; diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.t.js b/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.t.js deleted file mode 100644 index baea5c3..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/Checkpoints.t.js +++ /dev/null @@ -1,146 +0,0 @@ -const format = require('../format-lines'); -const { capitalize } = require('../../helpers'); -const { OPTS } = require('./Checkpoints.opts.js'); - -// TEMPLATE -const header = `\ -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; -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}; - -// Maximum gap between keys used during the fuzzing tests: the \`_prepareKeys\` function with make sure that -// key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. -uint8 internal constant _KEY_MAX_GAP = 64; - -Checkpoints.${opts.historyTypeName} internal _ckpts; - -// helpers -function _bound${capitalize(opts.keyTypeName)}( - ${opts.keyTypeName} x, - ${opts.keyTypeName} min, - ${opts.keyTypeName} max -) internal pure returns (${opts.keyTypeName}) { - return SafeCast.to${capitalize(opts.keyTypeName)}(bound(uint256(x), uint256(min), uint256(max))); -} - -function _prepareKeys( - ${opts.keyTypeName}[] memory keys, - ${opts.keyTypeName} maxSpread -) internal pure { - ${opts.keyTypeName} lastKey = 0; - for (uint256 i = 0; i < keys.length; ++i) { - ${opts.keyTypeName} key = _bound${capitalize(opts.keyTypeName)}(keys[i], lastKey, lastKey + maxSpread); - keys[i] = key; - lastKey = key; - } -} - -function _assertLatestCheckpoint( - bool exist, - ${opts.keyTypeName} key, - ${opts.valueTypeName} value -) internal { - (bool _exist, ${opts.keyTypeName} _key, ${opts.valueTypeName} _value) = _ckpts.latestCheckpoint(); - assertEq(_exist, exist); - assertEq(_key, key); - assertEq(_value, value); -} - -// tests -function testPush( - ${opts.keyTypeName}[] memory keys, - ${opts.valueTypeName}[] memory values, - ${opts.keyTypeName} pastKey -) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - // initial state - assertEq(_ckpts.length(), 0); - assertEq(_ckpts.latest(), 0); - _assertLatestCheckpoint(false, 0, 0); - - uint256 duplicates = 0; - for (uint256 i = 0; i < keys.length; ++i) { - ${opts.keyTypeName} key = keys[i]; - ${opts.valueTypeName} value = values[i % values.length]; - if (i > 0 && key == keys[i-1]) ++duplicates; - - // push - _ckpts.push(key, value); - - // check length & latest - assertEq(_ckpts.length(), i + 1 - duplicates); - assertEq(_ckpts.latest(), value); - _assertLatestCheckpoint(true, key, value); - } - - if (keys.length > 0) { - ${opts.keyTypeName} lastKey = keys[keys.length - 1]; - if (lastKey > 0) { - pastKey = _bound${capitalize(opts.keyTypeName)}(pastKey, 0, lastKey - 1); - - vm.expectRevert(); - this.push(pastKey, values[keys.length % values.length]); - } - } -} - -// used to test reverts -function push(${opts.keyTypeName} key, ${opts.valueTypeName} value) external { - _ckpts.push(key, value); -} - -function testLookup( - ${opts.keyTypeName}[] memory keys, - ${opts.valueTypeName}[] memory values, - ${opts.keyTypeName} lookup -) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - ${opts.keyTypeName} lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; - lookup = _bound${capitalize(opts.keyTypeName)}(lookup, 0, lastKey + _KEY_MAX_GAP); - - ${opts.valueTypeName} upper = 0; - ${opts.valueTypeName} lower = 0; - ${opts.keyTypeName} lowerKey = type(${opts.keyTypeName}).max; - for (uint256 i = 0; i < keys.length; ++i) { - ${opts.keyTypeName} key = keys[i]; - ${opts.valueTypeName} value = values[i % values.length]; - - // push - _ckpts.push(key, value); - - // track expected result of lookups - if (key <= lookup) { - upper = value; - } - // find the first key that is not smaller than the lookup key - if (key >= lookup && (i == 0 || keys[i-1] < lookup)) { - lowerKey = key; - } - if (key == lowerKey) { - lower = value; - } - } - - // check lookup - assertEq(_ckpts.lowerLookup(lookup), lower); - assertEq(_ckpts.upperLookup(lookup), upper); - assertEq(_ckpts.upperLookupRecent(lookup), upper); -} -`; - -// GENERATE -module.exports = format( - header, - ...OPTS.flatMap(opts => [`contract Checkpoints${opts.historyTypeName}Test is Test {`, [template(opts)], '}']), -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableMap.js b/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableMap.js deleted file mode 100644 index cb77557..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableMap.js +++ /dev/null @@ -1,281 +0,0 @@ -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; - -import {EnumerableSet} from "./EnumerableSet.sol"; - -/** - * @dev Library for managing an enumerable variant of Solidity's - * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[\`mapping\`] - * type. - * - * Maps have the following properties: - * - * - Entries are added, removed, and checked for existence in constant time - * (O(1)). - * - Entries are enumerated in O(n). No guarantees are made on the ordering. - * - * \`\`\`solidity - * contract Example { - * // Add the library methods - * using EnumerableMap for EnumerableMap.UintToAddressMap; - * - * // Declare a set state variable - * EnumerableMap.UintToAddressMap private myMap; - * } - * \`\`\` - * - * The following map types are supported: - * - * - \`uint256 -> address\` (\`UintToAddressMap\`) since v3.0.0 - * - \`address -> uint256\` (\`AddressToUintMap\`) since v4.6.0 - * - \`bytes32 -> bytes32\` (\`Bytes32ToBytes32Map\`) since v4.6.0 - * - \`uint256 -> uint256\` (\`UintToUintMap\`) since v4.7.0 - * - \`bytes32 -> uint256\` (\`Bytes32ToUintMap\`) since v4.7.0 - * - \`uint256 -> bytes32\` (\`UintToBytes32Map\`) since v5.1.0 - * - \`address -> address\` (\`AddressToAddressMap\`) since v5.1.0 - * - \`address -> bytes32\` (\`AddressToBytes32Map\`) since v5.1.0 - * - \`bytes32 -> address\` (\`Bytes32ToAddressMap\`) since v5.1.0 - * - * [WARNING] - * ==== - * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure - * unusable. - * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. - * - * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an - * array of EnumerableMap. - * ==== - */ -`; -/* eslint-enable max-len */ - -const defaultMap = () => `\ -// To implement this library for multiple types with as little code repetition as possible, we write it in -// terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions, -// and user-facing implementations such as \`UintToAddressMap\` are just wrappers around the underlying Map. -// This means that we can only create new EnumerableMaps for types that fit in bytes32. - -/** - * @dev Query for a nonexistent map key. - */ -error EnumerableMapNonexistentKey(bytes32 key); - -struct Bytes32ToBytes32Map { - // Storage of keys - EnumerableSet.Bytes32Set _keys; - mapping(bytes32 key => bytes32) _values; -} - -/** - * @dev Adds a key-value pair to a map, or updates the value for an existing - * key. O(1). - * - * Returns true if the key was added to the map, that is if it was not - * already present. - */ -function set( - Bytes32ToBytes32Map storage map, - bytes32 key, - bytes32 value -) internal returns (bool) { - map._values[key] = value; - return map._keys.add(key); -} - -/** - * @dev Removes a key-value pair from a map. O(1). - * - * Returns true if the key was removed from the map, that is if it was present. - */ -function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) { - delete map._values[key]; - return map._keys.remove(key); -} - -/** - * @dev Returns true if the key is in the map. O(1). - */ -function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) { - return map._keys.contains(key); -} - -/** - * @dev Returns the number of key-value pairs in the map. O(1). - */ -function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) { - return map._keys.length(); -} - -/** - * @dev Returns the key-value pair stored at position \`index\` in the map. O(1). - * - * Note that there are no guarantees on the ordering of entries inside the - * array, and it may change when more entries are added or removed. - * - * Requirements: - * - * - \`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]); -} - -/** - * @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)) { - return (contains(map, key), bytes32(0)); - } else { - return (true, value); - } -} - -/** - * @dev Returns the value associated with \`key\`. O(1). - * - * Requirements: - * - * - \`key\` must be in the map. - */ -function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { - bytes32 value = map._values[key]; - if(value == 0 && !contains(map, key)) { - revert EnumerableMapNonexistentKey(key); - } - return value; -} - -/** - * @dev Return the an array containing all the keys - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. - */ -function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) { - return map._keys.values(); -} -`; - -const customMap = ({ name, keyType, valueType }) => `\ -// ${name} - -struct ${name} { - Bytes32ToBytes32Map _inner; -} - -/** - * @dev Adds a key-value pair to a map, or updates the value for an existing - * key. O(1). - * - * Returns true if the key was added to the map, that is if it was not - * already present. - */ -function set( - ${name} storage map, - ${keyType} key, - ${valueType} value -) internal returns (bool) { - return set(map._inner, ${toBytes32(keyType, 'key')}, ${toBytes32(valueType, 'value')}); -} - -/** - * @dev Removes a value from a map. O(1). - * - * Returns true if the key was removed from the map, that is if it was present. - */ -function remove(${name} storage map, ${keyType} key) internal returns (bool) { - return remove(map._inner, ${toBytes32(keyType, 'key')}); -} - -/** - * @dev Returns true if the key is in the map. O(1). - */ -function contains(${name} storage map, ${keyType} key) internal view returns (bool) { - return contains(map._inner, ${toBytes32(keyType, 'key')}); -} - -/** - * @dev Returns the number of elements in the map. O(1). - */ -function length(${name} storage map) internal view returns (uint256) { - return length(map._inner); -} - -/** - * @dev Returns the element stored at position \`index\` in the map. O(1). - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - \`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')}); -} - -/** - * @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')}); -} - -/** - * @dev Returns the value associated with \`key\`. O(1). - * - * Requirements: - * - * - \`key\` must be in the map. - */ -function get(${name} storage map, ${keyType} key) internal view returns (${valueType}) { - return ${fromBytes32(valueType, `get(map._inner, ${toBytes32(keyType, 'key')})`)}; -} - -/** - * @dev Return the an array containing all the keys - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. - */ -function keys(${name} storage map) internal view returns (${keyType}[] memory) { - bytes32[] memory store = keys(map._inner); - ${keyType}[] memory result; - - /// @solidity memory-safe-assembly - assembly { - result := store - } - - return result; -} -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library EnumerableMap {', - [ - 'using EnumerableSet for EnumerableSet.Bytes32Set;', - '', - defaultMap(), - TYPES.map(details => customMap(details).trimEnd()).join('\n\n'), - ], - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableMap.opts.js b/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableMap.opts.js deleted file mode 100644 index d26ab05..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableMap.opts.js +++ /dev/null @@ -1,19 +0,0 @@ -const { capitalize } = require('../../helpers'); - -const mapType = str => (str == 'uint256' ? 'Uint' : capitalize(str)); - -const formatType = (keyType, valueType) => ({ - name: `${mapType(keyType)}To${mapType(valueType)}Map`, - keyType, - valueType, -}); - -const TYPES = ['uint256', 'address', 'bytes32'] - .flatMap((key, _, array) => array.map(value => [key, value])) - .slice(0, -1) // remove bytes32 → byte32 (last one) that is already defined - .map(args => formatType(...args)); - -module.exports = { - TYPES, - formatType, -}; diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableSet.js b/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableSet.js deleted file mode 100644 index e25242c..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableSet.js +++ /dev/null @@ -1,245 +0,0 @@ -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; - -/** - * @dev Library for managing - * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive - * types. - * - * Sets have the following properties: - * - * - Elements are added, removed, and checked for existence in constant time - * (O(1)). - * - Elements are enumerated in O(n). No guarantees are made on the ordering. - * - * \`\`\`solidity - * contract Example { - * // Add the library methods - * using EnumerableSet for EnumerableSet.AddressSet; - * - * // Declare a set state variable - * EnumerableSet.AddressSet private mySet; - * } - * \`\`\` - * - * As of v3.3.0, sets of type \`bytes32\` (\`Bytes32Set\`), \`address\` (\`AddressSet\`) - * and \`uint256\` (\`UintSet\`) are supported. - * - * [WARNING] - * ==== - * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure - * unusable. - * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. - * - * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an - * array of EnumerableSet. - * ==== - */ -`; -/* eslint-enable max-len */ - -const defaultSet = () => `\ -// To implement this library for multiple types with as little code -// repetition as possible, we write it in terms of a generic Set type with -// bytes32 values. -// The Set implementation uses private functions, and user-facing -// implementations (such as AddressSet) are just wrappers around the -// underlying Set. -// This means that we can only create new EnumerableSets for types that fit -// in bytes32. - -struct Set { - // Storage of set values - bytes32[] _values; - // Position is the index of the value in the \`values\` array plus 1. - // Position 0 is used to mean a value is not in the set. - mapping(bytes32 value => uint256) _positions; -} - -/** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ -function _add(Set storage set, bytes32 value) private returns (bool) { - if (!_contains(set, value)) { - set._values.push(value); - // The value is stored at length-1, but we add 1 to all indexes - // and use 0 as a sentinel value - set._positions[value] = set._values.length; - return true; - } else { - return false; - } -} - -/** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ -function _remove(Set storage set, bytes32 value) private returns (bool) { - // We cache the value's position to prevent multiple reads from the same storage slot - uint256 position = set._positions[value]; - - if (position != 0) { - // Equivalent to contains(set, value) - // To delete an element from the _values array in O(1), we swap the element to delete with the last one in - // the array, and then remove the last element (sometimes called as 'swap and pop'). - // This modifies the order of the array, as noted in {at}. - - uint256 valueIndex = position - 1; - uint256 lastIndex = set._values.length - 1; - - if (valueIndex != lastIndex) { - bytes32 lastValue = set._values[lastIndex]; - - // Move the lastValue to the index where the value to delete is - set._values[valueIndex] = lastValue; - // Update the tracked position of the lastValue (that was just moved) - set._positions[lastValue] = position; - } - - // Delete the slot where the moved value was stored - set._values.pop(); - - // Delete the tracked position for the deleted slot - delete set._positions[value]; - - return true; - } else { - return false; - } -} - -/** - * @dev Returns true if the value is in the set. O(1). - */ -function _contains(Set storage set, bytes32 value) private view returns (bool) { - return set._positions[value] != 0; -} - -/** - * @dev Returns the number of values on the set. O(1). - */ -function _length(Set storage set) private view returns (uint256) { - return set._values.length; -} - -/** - * @dev Returns the value stored at position \`index\` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - \`index\` must be strictly less than {length}. - */ -function _at(Set storage set, uint256 index) private view returns (bytes32) { - return set._values[index]; -} - -/** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ -function _values(Set storage set) private view returns (bytes32[] memory) { - return set._values; -} -`; - -const customSet = ({ name, type }) => `\ -// ${name} - -struct ${name} { - Set _inner; -} - -/** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ -function add(${name} storage set, ${type} value) internal returns (bool) { - return _add(set._inner, ${toBytes32(type, 'value')}); -} - -/** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ -function remove(${name} storage set, ${type} value) internal returns (bool) { - return _remove(set._inner, ${toBytes32(type, 'value')}); -} - -/** - * @dev Returns true if the value is in the set. O(1). - */ -function contains(${name} storage set, ${type} value) internal view returns (bool) { - return _contains(set._inner, ${toBytes32(type, 'value')}); -} - -/** - * @dev Returns the number of values in the set. O(1). - */ -function length(${name} storage set) internal view returns (uint256) { - return _length(set._inner); -} - -/** - * @dev Returns the value stored at position \`index\` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - \`index\` must be strictly less than {length}. - */ -function at(${name} storage set, uint256 index) internal view returns (${type}) { - return ${fromBytes32(type, '_at(set._inner, index)')}; -} - -/** - * @dev Return the entire set in an array - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ -function values(${name} storage set) internal view returns (${type}[] memory) { - bytes32[] memory store = _values(set._inner); - ${type}[] memory result; - - /// @solidity memory-safe-assembly - assembly { - result := store - } - - return result; -} -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library EnumerableSet {', - [defaultSet(), TYPES.map(details => customSet(details).trimEnd()).join('\n\n')], - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableSet.opts.js b/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableSet.opts.js deleted file mode 100644 index 739f0ac..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/EnumerableSet.opts.js +++ /dev/null @@ -1,12 +0,0 @@ -const { capitalize } = require('../../helpers'); - -const mapType = str => (str == 'uint256' ? 'Uint' : capitalize(str)); - -const formatType = type => ({ - name: `${mapType(type)}Set`, - type, -}); - -const TYPES = ['bytes32', 'address', 'uint256'].map(formatType); - -module.exports = { TYPES, formatType }; diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/SafeCast.js b/lib_openzeppelin_contracts/scripts/generate/templates/SafeCast.js deleted file mode 100644 index a10ee75..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/SafeCast.js +++ /dev/null @@ -1,138 +0,0 @@ -const format = require('../format-lines'); -const { range } = require('../../helpers'); - -const LENGTHS = range(8, 256, 8).reverse(); // 248 → 8 (in steps of 8) - -const header = `\ -pragma solidity ^0.8.20; - -/** - * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow - * checks. - * - * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can - * easily result in undesired exploitation or bugs, since developers usually - * assume that overflows raise errors. \`SafeCast\` restores this intuition by - * reverting the transaction when such an operation overflows. - * - * Using this library instead of the unchecked operations eliminates an entire - * class of bugs, so it's recommended to use it always. - */ -`; - -const errors = `\ - /** - * @dev Value doesn't fit in an uint of \`bits\` size. - */ - error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); - - /** - * @dev An int value doesn't fit in an uint of \`bits\` size. - */ - error SafeCastOverflowedIntToUint(int256 value); - - /** - * @dev Value doesn't fit in an int of \`bits\` size. - */ - error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); - - /** - * @dev An uint value doesn't fit in an int of \`bits\` size. - */ - error SafeCastOverflowedUintToInt(uint256 value); -`; - -const toUintDownCast = length => `\ -/** - * @dev Returns the downcasted uint${length} from uint256, reverting on - * overflow (when the input is greater than largest uint${length}). - * - * Counterpart to Solidity's \`uint${length}\` operator. - * - * Requirements: - * - * - input must fit into ${length} bits - */ -function toUint${length}(uint256 value) internal pure returns (uint${length}) { - if (value > type(uint${length}).max) { - revert SafeCastOverflowedUintDowncast(${length}, value); - } - return uint${length}(value); -} -`; - -/* eslint-disable max-len */ -const toIntDownCast = length => `\ -/** - * @dev Returns the downcasted int${length} from int256, reverting on - * overflow (when the input is less than smallest int${length} or - * greater than largest int${length}). - * - * Counterpart to Solidity's \`int${length}\` operator. - * - * Requirements: - * - * - input must fit into ${length} bits - */ -function toInt${length}(int256 value) internal pure returns (int${length} downcasted) { - downcasted = int${length}(value); - if (downcasted != value) { - revert SafeCastOverflowedIntDowncast(${length}, value); - } -} -`; -/* eslint-enable max-len */ - -const toInt = length => `\ -/** - * @dev Converts an unsigned uint${length} into a signed int${length}. - * - * Requirements: - * - * - input must be less than or equal to maxInt${length}. - */ -function toInt${length}(uint${length} value) internal pure returns (int${length}) { - // Note: Unsafe cast below is okay because \`type(int${length}).max\` is guaranteed to be positive - if (value > uint${length}(type(int${length}).max)) { - revert SafeCastOverflowedUintToInt(value); - } - return int${length}(value); -} -`; - -const toUint = length => `\ -/** - * @dev Converts a signed int${length} into an unsigned uint${length}. - * - * Requirements: - * - * - input must be greater than or equal to 0. - */ -function toUint${length}(int${length} value) internal pure returns (uint${length}) { - if (value < 0) { - revert SafeCastOverflowedIntToUint(value); - } - return uint${length}(value); -} -`; - -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 { - u := iszero(iszero(b)) - } - } -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library SafeCast {', - errors, - [...LENGTHS.map(toUintDownCast), toUint(256), ...LENGTHS.map(toIntDownCast), toInt(256), boolToUint], - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/Slot.opts.js b/lib_openzeppelin_contracts/scripts/generate/templates/Slot.opts.js deleted file mode 100644 index aed1f98..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/Slot.opts.js +++ /dev/null @@ -1,13 +0,0 @@ -const { capitalize } = require('../../helpers'); - -const TYPES = [ - { type: 'address', isValueType: true }, - { type: 'bool', isValueType: true, name: 'Boolean' }, - { type: 'bytes32', isValueType: true, variants: ['bytes4'] }, - { type: 'uint256', isValueType: true, variants: ['uint32'] }, - { type: 'int256', isValueType: true, variants: ['int32'] }, - { type: 'string', isValueType: false }, - { type: 'bytes', isValueType: false }, -].map(type => Object.assign(type, { name: type.name ?? capitalize(type.type) })); - -module.exports = { TYPES }; diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/SlotDerivation.js b/lib_openzeppelin_contracts/scripts/generate/templates/SlotDerivation.js deleted file mode 100644 index 5afe9a3..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/SlotDerivation.js +++ /dev/null @@ -1,116 +0,0 @@ -const format = require('../format-lines'); -const { TYPES } = require('./Slot.opts'); - -const header = `\ -pragma solidity ^0.8.20; - -/** - * @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots - * corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by - * the solidity language / compiler. - * - * See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.]. - * - * Example usage: - * \`\`\`solidity - * contract Example { - * // Add the library methods - * using StorageSlot for bytes32; - * using SlotDerivation for bytes32; - * - * // Declare a namespace - * string private constant _NAMESPACE = "" // eg. OpenZeppelin.Slot - * - * function setValueInNamespace(uint256 key, address newValue) internal { - * _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue; - * } - * - * function getValueInNamespace(uint256 key) internal view returns (address) { - * return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value; - * } - * } - * \`\`\` - * - * TIP: Consider using this library along with {StorageSlot}. - * - * 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. - */ -`; - -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 { - mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1)) - slot := and(keccak256(0x00, 0x20), not(0xff)) - } -} -`; - -const array = `\ -/** - * @dev Add an offset to a slot to get the n-th element of a structure or an array. - */ -function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) { - unchecked { - return bytes32(uint256(slot) + pos); - } -} - -/** - * @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 { - mstore(0x00, slot) - result := keccak256(0x00, 0x20) - } -} -`; - -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 { - mstore(0x00, key) - mstore(0x20, slot) - result := keccak256(0x00, 0x40) - } -} -`; - -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 { - let length := mload(key) - let begin := add(key, 0x20) - let end := add(begin, length) - let cache := mload(end) - mstore(end, slot) - result := keccak256(begin, add(length, 0x20)) - mstore(end, cache) - } -} -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library SlotDerivation {', - namespace, - array, - TYPES.map(type => (type.isValueType ? mapping(type) : mapping2(type))), - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/SlotDerivation.t.js b/lib_openzeppelin_contracts/scripts/generate/templates/SlotDerivation.t.js deleted file mode 100644 index 052ada8..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/SlotDerivation.t.js +++ /dev/null @@ -1,73 +0,0 @@ -const format = require('../format-lines'); -const { capitalize } = require('../../helpers'); -const { TYPES } = require('./Slot.opts'); - -const header = `\ -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; - -import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; -`; - -const array = `\ -bytes[] private _array; - -function testDeriveArray(uint256 length, uint256 offset) public { - length = bound(length, 1, type(uint256).max); - offset = bound(offset, 0, length - 1); - - bytes32 baseSlot; - assembly { - baseSlot := _array.slot - sstore(baseSlot, length) // store length so solidity access does not revert - } - - bytes storage derived = _array[offset]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveArray().offset(offset), derivedSlot); -} -`; - -const mapping = ({ type, name, isValueType }) => `\ -mapping(${type} => bytes) private _${type}Mapping; - -function testDeriveMapping${name}(${type} ${isValueType ? '' : 'memory'} key) public { - bytes32 baseSlot; - assembly { - baseSlot := _${type}Mapping.slot - } - - bytes storage derived = _${type}Mapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); -} -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'contract SlotDerivationTest is Test {', - 'using SlotDerivation for bytes32;', - '', - array, - TYPES.flatMap(type => - [].concat( - type, - (type.variants ?? []).map(variant => ({ - type: variant, - name: capitalize(variant), - isValueType: type.isValueType, - })), - ), - ).map(type => mapping(type)), - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/StorageSlot.js b/lib_openzeppelin_contracts/scripts/generate/templates/StorageSlot.js deleted file mode 100644 index 54cb4ec..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/StorageSlot.js +++ /dev/null @@ -1,127 +0,0 @@ -const format = require('../format-lines'); -const { TYPES } = require('./Slot.opts'); - -const header = `\ -pragma solidity ^0.8.24; - -/** - * @dev Library for reading and writing primitive types to specific storage slots. - * - * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. - * This library helps with reading and writing to such slots without the need for inline assembly. - * - * The functions in this library return Slot structs that contain a \`value\` member that can be used to read or write. - * - * Example usage to set ERC-1967 implementation slot: - * \`\`\`solidity - * contract ERC1967 { - * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. - * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - * - * function _getImplementation() internal view returns (address) { - * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; - * } - * - * function _setImplementation(address newImplementation) internal { - * require(newImplementation.code.length > 0); - * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; - * } - * } - * \`\`\` - * - * 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}. - */ -`; - -const struct = ({ type, name }) => `\ -struct ${name}Slot { - ${type} value; -} -`; - -const get = ({ name }) => `\ -/** - * @dev Returns an \`${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 { - r.slot := slot - } -} -`; - -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 { - r.slot := store.slot - } -} -`; - -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) { - /// @solidity memory-safe-assembly - assembly { - value := tload(slot) - } -} -/** - * @dev Store \`value\` at location \`slot\` in transient storage. - */ -function tstore(${name}SlotType slot, ${type} value) internal { - /// @solidity memory-safe-assembly - assembly { - tstore(slot, value) - } -} -`; - -// GENERATE -module.exports = format( - header.trimEnd(), - 'library StorageSlot {', - TYPES.map(type => struct(type)), - TYPES.flatMap(type => [get(type), type.isValueType ? '' : getStorage(type)]), - TYPES.filter(type => type.isValueType).map(type => udvt(type)), - TYPES.filter(type => type.isValueType).map(type => transient(type)), - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/StorageSlotMock.js b/lib_openzeppelin_contracts/scripts/generate/templates/StorageSlotMock.js deleted file mode 100644 index 62506e1..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/StorageSlotMock.js +++ /dev/null @@ -1,65 +0,0 @@ -const format = require('../format-lines'); -const { TYPES } = require('./Slot.opts'); - -const header = `\ -pragma solidity ^0.8.24; - -import {Multicall} from "../utils/Multicall.sol"; -import {StorageSlot} from "../utils/StorageSlot.sol"; -`; - -const storageSetValueType = ({ type, name }) => `\ -function set${name}Slot(bytes32 slot, ${type} value) public { - slot.get${name}Slot().value = value; -} -`; - -const storageGetValueType = ({ type, name }) => `\ -function get${name}Slot(bytes32 slot) public view returns (${type}) { - return slot.get${name}Slot().value; -} -`; - -const storageSetNonValueType = ({ type, name }) => `\ -mapping(uint256 key => ${type}) public ${type}Map; - -function set${name}Slot(bytes32 slot, ${type} calldata value) public { - slot.get${name}Slot().value = value; -} - -function set${name}Storage(uint256 key, ${type} calldata value) public { - ${type}Map[key].get${name}Slot().value = value; -} - -function get${name}Slot(bytes32 slot) public view returns (${type} memory) { - return slot.get${name}Slot().value; -} - -function get${name}Storage(uint256 key) public view returns (${type} memory) { - return ${type}Map[key].get${name}Slot().value; -} -`; - -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.trimEnd(), - 'contract StorageSlotMock is Multicall {', - 'using StorageSlot for *;', - 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)), - '}', -); diff --git a/lib_openzeppelin_contracts/scripts/generate/templates/conversion.js b/lib_openzeppelin_contracts/scripts/generate/templates/conversion.js deleted file mode 100644 index 9221f7c..0000000 --- a/lib_openzeppelin_contracts/scripts/generate/templates/conversion.js +++ /dev/null @@ -1,30 +0,0 @@ -function toBytes32(type, value) { - switch (type) { - case 'bytes32': - return value; - case 'uint256': - return `bytes32(${value})`; - case 'address': - return `bytes32(uint256(uint160(${value})))`; - default: - throw new Error(`Conversion from ${type} to bytes32 not supported`); - } -} - -function fromBytes32(type, value) { - switch (type) { - case 'bytes32': - return value; - case 'uint256': - return `uint256(${value})`; - case 'address': - return `address(uint160(uint256(${value})))`; - default: - throw new Error(`Conversion from bytes32 to ${type} not supported`); - } -} - -module.exports = { - toBytes32, - fromBytes32, -}; diff --git a/lib_openzeppelin_contracts/scripts/git-user-config.sh b/lib_openzeppelin_contracts/scripts/git-user-config.sh deleted file mode 100644 index e7b81c3..0000000 --- a/lib_openzeppelin_contracts/scripts/git-user-config.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -x - -git config user.name 'github-actions' -git config user.email '41898282+github-actions[bot]@users.noreply.github.com' diff --git a/lib_openzeppelin_contracts/scripts/helpers.js b/lib_openzeppelin_contracts/scripts/helpers.js deleted file mode 100644 index d28c086..0000000 --- a/lib_openzeppelin_contracts/scripts/helpers.js +++ /dev/null @@ -1,7 +0,0 @@ -const iterate = require('../test/helpers/iterate'); -const strings = require('../test/helpers/strings'); - -module.exports = { - ...iterate, - ...strings, -}; diff --git a/lib_openzeppelin_contracts/scripts/prepack.sh b/lib_openzeppelin_contracts/scripts/prepack.sh deleted file mode 100755 index 6af1032..0000000 --- a/lib_openzeppelin_contracts/scripts/prepack.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -shopt -s globstar - -# cross platform `mkdir -p` -mkdirp() { - node -e "fs.mkdirSync('$1', { recursive: true })" -} - -# cd to the root of the repo -cd "$(git rev-parse --show-toplevel)" - -npm run clean - -env COMPILE_MODE=production npm run compile - -mkdirp contracts/build/contracts -cp artifacts/contracts/**/*.json contracts/build/contracts -rm contracts/build/contracts/*.dbg.json -node scripts/remove-ignored-artifacts.js - -cp README.md contracts/ diff --git a/lib_openzeppelin_contracts/scripts/prepare-docs.sh b/lib_openzeppelin_contracts/scripts/prepare-docs.sh deleted file mode 100755 index d1317b0..0000000 --- a/lib_openzeppelin_contracts/scripts/prepare-docs.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -shopt -s globstar - -OUTDIR="$(node -p 'require("./docs/config.js").outputDir')" - -if [ ! -d node_modules ]; then - npm ci -fi - -rm -rf "$OUTDIR" - -hardhat docgen - -# copy examples and adjust imports -examples_source_dir="contracts/mocks/docs" -examples_target_dir="docs/modules/api/examples" - -for f in "$examples_source_dir"/**/*.sol; do - name="${f/#"$examples_source_dir/"/}" - mkdir -p "$examples_target_dir/$(dirname "$name")" - sed -Ee '/^import/s|"(\.\./)+|"@openzeppelin/contracts/|' "$f" > "$examples_target_dir/$name" -done - -node scripts/gen-nav.js "$OUTDIR" > "$OUTDIR/../nav.adoc" diff --git a/lib_openzeppelin_contracts/scripts/release/format-changelog.js b/lib_openzeppelin_contracts/scripts/release/format-changelog.js deleted file mode 100755 index b8bcc8c..0000000 --- a/lib_openzeppelin_contracts/scripts/release/format-changelog.js +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env node - -// Adjusts the format of the changelog that changesets generates. -// This is run automatically when npm version is run. - -const fs = require('fs'); -const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); - -// Groups: -// - 1: Pull Request Number and URL -// - 2: Changeset entry -const RELEASE_LINE_REGEX = /^- (\[#.*?\]\(.*?\))?.*?! - (.*)$/gm; - -// Captures vX.Y.Z or vX.Y.Z-rc.W -const VERSION_TITLE_REGEX = /^## (\d+\.\d+\.\d+(-rc\.\d+)?)$/gm; - -const isPrerelease = process.env.PRERELEASE === 'true'; - -const formatted = changelog - // Remove titles - .replace(/^### Major Changes\n\n/gm, '') - .replace(/^### Minor Changes\n\n/gm, '') - .replace(/^### Patch Changes\n\n/gm, '') - // Remove extra whitespace between items - .replace(/^(- \[.*\n)\n(?=-)/gm, '$1') - // Format each release line - .replace(RELEASE_LINE_REGEX, (_, pr, entry) => (pr ? `- ${entry} (${pr})` : `- ${entry}`)) - // Add date to new version - .replace(VERSION_TITLE_REGEX, `\n## $1 (${new Date().toISOString().split('T')[0]})`) - // Conditionally allow vX.Y.Z.rc-.W sections only in prerelease - .replace(/^## \d\.\d\.\d-rc\S+[^]+?(?=^#)/gm, section => (isPrerelease ? section : '')); - -fs.writeFileSync('CHANGELOG.md', formatted); diff --git a/lib_openzeppelin_contracts/scripts/release/synchronize-versions.js b/lib_openzeppelin_contracts/scripts/release/synchronize-versions.js deleted file mode 100755 index 15aa259..0000000 --- a/lib_openzeppelin_contracts/scripts/release/synchronize-versions.js +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -// Synchronizes the version in contracts/package.json with the one in package.json. -// This is run automatically when npm version is run. - -const fs = require('fs'); - -setVersion('package.json', 'contracts/package.json'); - -function setVersion(from, to) { - const fromJson = JSON.parse(fs.readFileSync(from)); - const toJson = JSON.parse(fs.readFileSync(to)); - toJson.version = fromJson.version; - fs.writeFileSync(to, JSON.stringify(toJson, null, 2) + '\n'); -} diff --git a/lib_openzeppelin_contracts/scripts/release/update-comment.js b/lib_openzeppelin_contracts/scripts/release/update-comment.js deleted file mode 100755 index 9d6df26..0000000 --- a/lib_openzeppelin_contracts/scripts/release/update-comment.js +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env node -const fs = require('fs'); -const proc = require('child_process'); -const semver = require('semver'); -const run = (cmd, ...args) => proc.execFileSync(cmd, args, { encoding: 'utf8' }).trim(); - -const gitStatus = run('git', 'status', '--porcelain', '-uno', 'contracts/**/*.sol'); -if (gitStatus.length > 0) { - console.error('Contracts directory is not clean'); - process.exit(1); -} - -const { version } = require('../../package.json'); - -// Get latest tag according to semver. -const [tag] = run('git', 'tag') - .split(/\r?\n/) - .filter(semver.coerce) // check version can be processed - .filter(v => semver.satisfies(v, `< ${version}`)) // ignores prereleases unless currently a prerelease - .sort(semver.rcompare); - -// Ordering tag → HEAD is important here. -const files = run('git', 'diff', tag, 'HEAD', '--name-only', 'contracts/**/*.sol') - .split(/\r?\n/) - .filter(file => file && !file.match(/mock/i) && fs.existsSync(file)); - -for (const file of files) { - const current = fs.readFileSync(file, 'utf8'); - const updated = current.replace( - /(\/\/ SPDX-License-Identifier:.*)$(\n\/\/ OpenZeppelin Contracts .*$)?/m, - `$1\n// OpenZeppelin Contracts (last updated v${version}) (${file.replace('contracts/', '')})`, - ); - fs.writeFileSync(file, updated); -} diff --git a/lib_openzeppelin_contracts/scripts/release/version.sh b/lib_openzeppelin_contracts/scripts/release/version.sh deleted file mode 100755 index 7b0ddea..0000000 --- a/lib_openzeppelin_contracts/scripts/release/version.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -changeset version - -scripts/release/format-changelog.js -scripts/release/synchronize-versions.js -scripts/release/update-comment.js - -oz-docs update-version diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/exit-prerelease.sh b/lib_openzeppelin_contracts/scripts/release/workflow/exit-prerelease.sh deleted file mode 100644 index bcf9b9a..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/exit-prerelease.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -npx changeset pre exit rc -git add . -git commit -m "Exit release candidate" -git push origin diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/github-release.js b/lib_openzeppelin_contracts/scripts/release/workflow/github-release.js deleted file mode 100644 index f213106..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/github-release.js +++ /dev/null @@ -1,48 +0,0 @@ -const { readFileSync } = require('fs'); -const { join } = require('path'); -const { version } = require(join(__dirname, '../../../package.json')); - -module.exports = async ({ github, context }) => { - const changelog = readFileSync('CHANGELOG.md', 'utf8'); - - await github.rest.repos.createRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - tag_name: `v${version}`, - target_commitish: github.ref_name, - body: extractSection(changelog, version), - prerelease: process.env.PRERELEASE === 'true', - }); -}; - -// From https://github.com/frangio/extract-changelog/blob/master/src/utils/word-regexp.ts -function makeWordRegExp(word) { - const start = word.length > 0 && /\b/.test(word[0]) ? '\\b' : ''; - const end = word.length > 0 && /\b/.test(word[word.length - 1]) ? '\\b' : ''; - return new RegExp(start + [...word].map(c => (/[a-z0-9]/i.test(c) ? c : '\\' + c)).join('') + end); -} - -// From https://github.com/frangio/extract-changelog/blob/master/src/core.ts -function extractSection(document, wantedHeading) { - // ATX Headings as defined in GitHub Flavored Markdown (https://github.github.com/gfm/#atx-headings) - const heading = /^ {0,3}(?#{1,6})(?: [ \t\v\f]*(?.*?)[ \t\v\f]*)?(?:[\n\r]+|$)/gm; - - const wantedHeadingRe = makeWordRegExp(wantedHeading); - - let start, end; - - for (const m of document.matchAll(heading)) { - if (!start) { - if (m.groups.text.search(wantedHeadingRe) === 0) { - start = m; - } - } else if (m.groups.lead.length <= start.groups.lead.length) { - end = m; - break; - } - } - - if (start) { - return document.slice(start.index + start[0].length, end?.index); - } -} diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/integrity-check.sh b/lib_openzeppelin_contracts/scripts/release/workflow/integrity-check.sh deleted file mode 100644 index 86e99f9..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/integrity-check.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -CHECKSUMS="$RUNNER_TEMP/checksums.txt" - -# Extract tarball content into a tmp directory -tar xf "$TARBALL" -C "$RUNNER_TEMP" - -# Move to extracted directory -cd "$RUNNER_TEMP/package" - -# Checksum all Solidity files -find . -type f -name "*.sol" | xargs shasum > "$CHECKSUMS" - -# Back to directory with git contents -cd "$GITHUB_WORKSPACE/contracts" - -# Check against tarball contents -shasum -c "$CHECKSUMS" diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/pack.sh b/lib_openzeppelin_contracts/scripts/release/workflow/pack.sh deleted file mode 100644 index ce30712..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/pack.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -dist_tag() { - PACKAGE_JSON_NAME="$(jq -r .name ./package.json)" - LATEST_NPM_VERSION="$(npm info "$PACKAGE_JSON_NAME" version)" - PACKAGE_JSON_VERSION="$(jq -r .version ./package.json)" - - if [ "$PRERELEASE" = "true" ]; then - echo "next" - elif npx semver -r ">$LATEST_NPM_VERSION" "$PACKAGE_JSON_VERSION" > /dev/null; then - echo "latest" - else - # This is a patch for an older version - # npm can't publish without a tag - echo "tmp" - fi -} - -cd contracts -TARBALL="$(npm pack | tee /dev/stderr | tail -1)" -echo "tarball_name=$TARBALL" >> $GITHUB_OUTPUT -echo "tarball=$(pwd)/$TARBALL" >> $GITHUB_OUTPUT -echo "tag=$(dist_tag)" >> $GITHUB_OUTPUT -cd .. diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/publish.sh b/lib_openzeppelin_contracts/scripts/release/workflow/publish.sh deleted file mode 100644 index e490e5d..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/publish.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -PACKAGE_JSON_NAME="$(tar xfO "$TARBALL" package/package.json | jq -r .name)" -PACKAGE_JSON_VERSION="$(tar xfO "$TARBALL" package/package.json | jq -r .version)" - -# Intentionally escape $ to avoid interpolation and writing the token to disk -echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc - -# Actual publish -npm publish "$TARBALL" --tag "$TAG" - -# Clean up tags -delete_tag() { - npm dist-tag rm "$PACKAGE_JSON_NAME" "$1" -} - -if [ "$TAG" = tmp ]; then - delete_tag "$TAG" -elif [ "$TAG" = latest ]; then - # Delete the next tag if it exists and is a prerelease for what is currently being published - if npm dist-tag ls "$PACKAGE_JSON_NAME" | grep -q "next: $PACKAGE_JSON_VERSION"; then - delete_tag next - fi -fi diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/rerun.js b/lib_openzeppelin_contracts/scripts/release/workflow/rerun.js deleted file mode 100644 index f48ce6e..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/rerun.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = ({ github, context }) => - github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'release-cycle.yml', - ref: process.env.REF || process.env.GITHUB_REF_NAME, - }); diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/set-changesets-pr-title.js b/lib_openzeppelin_contracts/scripts/release/workflow/set-changesets-pr-title.js deleted file mode 100644 index 59b03b2..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/set-changesets-pr-title.js +++ /dev/null @@ -1,17 +0,0 @@ -const { coerce, inc, rsort } = require('semver'); -const { join } = require('path'); -const { version } = require(join(__dirname, '../../../package.json')); - -module.exports = async ({ core }) => { - // Variables not in the context - const refName = process.env.GITHUB_REF_NAME; - - // Compare package.json version's next patch vs. first version patch - // A recently opened branch will give the next patch for the previous minor - // So, we get the max against the patch 0 of the release branch's version - const branchPatch0 = coerce(refName.replace('release-v', '')).version; - const packageJsonNextPatch = inc(version, 'patch'); - const [nextVersion] = rsort([branchPatch0, packageJsonNextPatch], false); - - core.exportVariable('TITLE', `Release v${nextVersion}`); -}; diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/start.sh b/lib_openzeppelin_contracts/scripts/release/workflow/start.sh deleted file mode 100644 index 7683ec5..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/start.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Set changeset status location -# This is needed because `changeset status --output` only works with relative routes -CHANGESETS_STATUS_JSON="$(realpath --relative-to=. "$RUNNER_TEMP/status.json")" - -# Save changeset status to temp file -npx changeset status --output="$CHANGESETS_STATUS_JSON" - -# Defensive assertion. SHOULD NOT BE REACHED -if [ "$(jq '.releases | length' "$CHANGESETS_STATUS_JSON")" != 1 ]; then - echo "::error file=$CHANGESETS_STATUS_JSON::The status doesn't contain only 1 release" - exit 1; -fi; - -# Create branch -BRANCH_SUFFIX="$(jq -r '.releases[0].newVersion | gsub("\\.\\d+$"; "")' $CHANGESETS_STATUS_JSON)" -RELEASE_BRANCH="release-v$BRANCH_SUFFIX" -git checkout -b "$RELEASE_BRANCH" - -# Output branch -echo "branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT - -# Enter in prerelease state -npx changeset pre enter rc -git add . -git commit -m "Start release candidate" - -# Push branch -if ! git push origin "$RELEASE_BRANCH"; then - echo "::error file=scripts/release/start.sh::Can't push $RELEASE_BRANCH. Did you forget to run this workflow from $RELEASE_BRANCH?" - exit 1 -fi diff --git a/lib_openzeppelin_contracts/scripts/release/workflow/state.js b/lib_openzeppelin_contracts/scripts/release/workflow/state.js deleted file mode 100644 index 914e8de..0000000 --- a/lib_openzeppelin_contracts/scripts/release/workflow/state.js +++ /dev/null @@ -1,112 +0,0 @@ -const { readPreState } = require('@changesets/pre'); -const { default: readChangesets } = require('@changesets/read'); -const { join } = require('path'); -const { fetch } = require('undici'); -const { version, name: packageName } = require(join(__dirname, '../../../contracts/package.json')); - -module.exports = async ({ github, context, core }) => { - const state = await getState({ github, context, core }); - - function setOutput(key, value) { - core.info(`State ${key} = ${value}`); - core.setOutput(key, value); - } - - // Jobs to trigger - setOutput('start', shouldRunStart(state)); - setOutput('promote', shouldRunPromote(state)); - setOutput('changesets', shouldRunChangesets(state)); - setOutput('publish', shouldRunPublish(state)); - setOutput('merge', shouldRunMerge(state)); - - // Global Variables - setOutput('is_prerelease', state.prerelease); -}; - -function shouldRunStart({ isMaster, isWorkflowDispatch, botRun }) { - return isMaster && isWorkflowDispatch && !botRun; -} - -function shouldRunPromote({ isReleaseBranch, isWorkflowDispatch, botRun }) { - return isReleaseBranch && isWorkflowDispatch && !botRun; -} - -function shouldRunChangesets({ isReleaseBranch, isPush, isWorkflowDispatch, botRun }) { - return (isReleaseBranch && isPush) || (isReleaseBranch && isWorkflowDispatch && botRun); -} - -function shouldRunPublish({ isReleaseBranch, isPush, hasPendingChangesets, isPublishedOnNpm }) { - return isReleaseBranch && isPush && !hasPendingChangesets && !isPublishedOnNpm; -} - -function shouldRunMerge({ - isReleaseBranch, - isPush, - prerelease, - isCurrentFinalVersion, - hasPendingChangesets, - prBackExists, -}) { - return isReleaseBranch && isPush && !prerelease && isCurrentFinalVersion && !hasPendingChangesets && !prBackExists; -} - -async function getState({ github, context, core }) { - // Variables not in the context - const refName = process.env.GITHUB_REF_NAME; - const botRun = process.env.TRIGGERING_ACTOR === 'github-actions[bot]'; - - const { changesets, preState } = await readChangesetState(); - - // Static vars - const state = { - refName, - hasPendingChangesets: changesets.length > 0, - prerelease: preState?.mode === 'pre', - isMaster: refName === 'master', - isReleaseBranch: refName.startsWith('release-v'), - isWorkflowDispatch: context.eventName === 'workflow_dispatch', - isPush: context.eventName === 'push', - isCurrentFinalVersion: !version.includes('-rc.'), - botRun, - }; - - // Async vars - const { data: prs } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - head: `${context.repo.owner}:merge/${state.refName}`, - base: 'master', - state: 'open', - }); - - state.prBackExists = prs.length !== 0; - - state.isPublishedOnNpm = await isPublishedOnNpm(packageName, version); - - // Log every state value in debug mode - if (core.isDebug()) for (const [key, value] of Object.entries(state)) core.debug(`${key}: ${value}`); - - return state; -} - -// From https://github.com/changesets/action/blob/v1.4.1/src/readChangesetState.ts -async function readChangesetState(cwd = process.cwd()) { - const preState = await readPreState(cwd); - const isInPreMode = preState !== undefined && preState.mode === 'pre'; - - let changesets = await readChangesets(cwd); - - if (isInPreMode) { - changesets = changesets.filter(x => !preState.changesets.includes(x.id)); - } - - return { - preState: isInPreMode ? preState : undefined, - changesets, - }; -} - -async function isPublishedOnNpm(package, version) { - const res = await fetch(`https://registry.npmjs.com/${package}/${version}`); - return res.ok; -} diff --git a/lib_openzeppelin_contracts/scripts/remove-ignored-artifacts.js b/lib_openzeppelin_contracts/scripts/remove-ignored-artifacts.js deleted file mode 100644 index e156032..0000000 --- a/lib_openzeppelin_contracts/scripts/remove-ignored-artifacts.js +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node - -// This script removes the build artifacts of ignored contracts. - -const fs = require('fs'); -const path = require('path'); -const match = require('micromatch'); - -function readJSON(path) { - return JSON.parse(fs.readFileSync(path)); -} - -const pkgFiles = readJSON('package.json').files; - -// Get only negated patterns. -const ignorePatterns = pkgFiles - .filter(pat => pat.startsWith('!')) - // Remove the negation part. Makes micromatch usage more intuitive. - .map(pat => pat.slice(1)); - -const ignorePatternsSubtrees = ignorePatterns - // Add **/* to ignore all files contained in the directories. - .concat(ignorePatterns.map(pat => path.join(pat, '**/*'))) - .map(p => p.replace(/^\//, '')); - -const artifactsDir = 'contracts/build/contracts'; -const buildinfo = 'artifacts/build-info'; -const filenames = fs.readdirSync(buildinfo); - -let n = 0; - -for (const filename of filenames) { - const solcOutput = readJSON(path.join(buildinfo, filename)).output; - for (const sourcePath in solcOutput.contracts) { - const ignore = match.any(sourcePath, ignorePatternsSubtrees); - if (ignore) { - for (const contract in solcOutput.contracts[sourcePath]) { - fs.unlinkSync(path.join(artifactsDir, contract + '.json')); - n += 1; - } - } - } -} - -console.error(`Removed ${n} mock artifacts`); diff --git a/lib_openzeppelin_contracts/scripts/solhint-custom/index.js b/lib_openzeppelin_contracts/scripts/solhint-custom/index.js deleted file mode 100644 index 9625027..0000000 --- a/lib_openzeppelin_contracts/scripts/solhint-custom/index.js +++ /dev/null @@ -1,84 +0,0 @@ -const path = require('path'); -const minimatch = require('minimatch'); - -// Files matching these patterns will be ignored unless a rule has `static global = true` -const ignore = ['contracts/mocks/**/*', 'test/**/*']; - -class Base { - constructor(reporter, config, source, fileName) { - this.reporter = reporter; - this.ignored = this.constructor.global || ignore.some(p => minimatch(path.normalize(fileName), p)); - this.ruleId = this.constructor.ruleId; - if (this.ruleId === undefined) { - throw Error('missing ruleId static property'); - } - } - - error(node, message) { - if (!this.ignored) { - this.reporter.error(node, this.ruleId, message); - } - } -} - -module.exports = [ - class extends Base { - static ruleId = 'interface-names'; - - ContractDefinition(node) { - if (node.kind === 'interface' && !/^I[A-Z]/.test(node.name)) { - this.error(node, 'Interface names should have a capital I prefix'); - } - } - }, - - class extends Base { - static ruleId = 'private-variables'; - - VariableDeclaration(node) { - const constantOrImmutable = node.isDeclaredConst || node.isImmutable; - if (node.isStateVar && !constantOrImmutable && node.visibility !== 'private') { - this.error(node, 'State variables must be private'); - } - } - }, - - class extends Base { - static ruleId = 'leading-underscore'; - - VariableDeclaration(node) { - if (node.isDeclaredConst) { - // TODO: expand visibility and fix - if (node.visibility === 'private' && /^_/.test(node.name)) { - this.error(node, 'Constant variables should not have leading underscore'); - } - } else if (node.visibility === 'private' && !/^_/.test(node.name)) { - this.error(node, 'Non-constant private variables must have leading underscore'); - } - } - - FunctionDefinition(node) { - if (node.visibility === 'private' || (node.visibility === 'internal' && node.parent.kind !== 'library')) { - if (!/^_/.test(node.name)) { - this.error(node, 'Private and internal functions must have leading underscore'); - } - } - if (node.visibility === 'internal' && node.parent.kind === 'library') { - if (/^_/.test(node.name)) { - this.error(node, 'Library internal functions should not have leading underscore'); - } - } - } - }, - - // TODO: re-enable and fix - // class extends Base { - // static ruleId = 'no-external-virtual'; - // - // FunctionDefinition(node) { - // if (node.visibility == 'external' && node.isVirtual) { - // this.error(node, 'Functions should not be external and virtual'); - // } - // } - // }, -]; diff --git a/lib_openzeppelin_contracts/scripts/solhint-custom/package.json b/lib_openzeppelin_contracts/scripts/solhint-custom/package.json deleted file mode 100644 index 075eb92..0000000 --- a/lib_openzeppelin_contracts/scripts/solhint-custom/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "solhint-plugin-openzeppelin", - "version": "0.0.0", - "private": true -} diff --git a/lib_openzeppelin_contracts/scripts/update-docs-branch.js b/lib_openzeppelin_contracts/scripts/update-docs-branch.js deleted file mode 100644 index 324ba0c..0000000 --- a/lib_openzeppelin_contracts/scripts/update-docs-branch.js +++ /dev/null @@ -1,65 +0,0 @@ -const proc = require('child_process'); -const read = cmd => proc.execSync(cmd, { encoding: 'utf8' }).trim(); -const run = cmd => { - proc.execSync(cmd, { stdio: 'inherit' }); -}; -const tryRead = cmd => { - try { - return read(cmd); - } catch (e) { - return undefined; - } -}; - -const releaseBranchRegex = /^release-v(?(?\d+)\.(?\d+)(?:\.(?\d+))?)$/; - -const currentBranch = read('git rev-parse --abbrev-ref HEAD'); -const match = currentBranch.match(releaseBranchRegex); - -if (!match) { - console.error('Not currently on a release branch'); - process.exit(1); -} - -const pkgVersion = require('../package.json').version; - -if (pkgVersion.includes('-') && !pkgVersion.includes('.0.0-')) { - console.error('Refusing to update docs: non-major prerelease detected'); - process.exit(0); -} - -const current = match.groups; -const docsBranch = `docs-v${current.major}.x`; - -// Fetch remotes and find the docs branch if it exists -run('git fetch --all --no-tags'); -const matchingDocsBranches = tryRead(`git rev-parse --glob='*/${docsBranch}'`); - -if (!matchingDocsBranches) { - // Create the branch - run(`git checkout --orphan ${docsBranch}`); -} else { - const [publishedRef, ...others] = new Set(matchingDocsBranches.split('\n')); - if (others.length > 0) { - console.error( - `Found conflicting ${docsBranch} branches.\n` + - 'Either local branch is outdated or there are multiple matching remote branches.', - ); - process.exit(1); - } - const publishedVersion = JSON.parse(read(`git show ${publishedRef}:package.json`)).version; - const publishedMinor = publishedVersion.match(/\d+\.(?\d+)\.\d+/).groups.minor; - if (current.minor < publishedMinor) { - console.error('Refusing to update docs: newer version is published'); - process.exit(0); - } - - run('git checkout --quiet --detach'); - run(`git reset --soft ${publishedRef}`); - run(`git checkout ${docsBranch}`); -} - -run('npm run prepare-docs'); -run('git add -f docs'); // --force needed because generated docs files are gitignored -run('git commit -m "Update docs"'); -run(`git checkout ${currentBranch}`); diff --git a/lib_openzeppelin_contracts/scripts/upgradeable/README.md b/lib_openzeppelin_contracts/scripts/upgradeable/README.md deleted file mode 100644 index 2309f9e..0000000 --- a/lib_openzeppelin_contracts/scripts/upgradeable/README.md +++ /dev/null @@ -1,21 +0,0 @@ -The upgradeable variant of OpenZeppelin Contracts is automatically generated from the original Solidity code. We call this process "transpilation" and it is implemented by our [Upgradeability Transpiler](https://github.com/OpenZeppelin/openzeppelin-transpiler/). - -When the `master` branch or `release-v*` branches are updated, the code is transpiled and pushed to [OpenZeppelin/openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable) by the `upgradeable.yml` workflow. - -## `transpile.sh` - -Applies patches and invokes the transpiler with the command line flags we need for our requirements (for example, excluding certain files). - -## `transpile-onto.sh` - -``` -bash scripts/upgradeable/transpile-onto.sh [] -``` - -Transpiles the contents of the current git branch and commits the result as a new commit on branch ``. If branch `` doesn't exist, it will copy the commit history of `[]` (this is used in GitHub Actions, but is usually not necessary locally). - -## `patch-apply.sh` & `patch-save.sh` - -Some of the upgradeable contract variants require ad-hoc changes that are not implemented by the transpiler. These changes are implemented by patches stored in `upgradeable.patch` in this directory. `patch-apply.sh` applies these patches. - -If the patches fail to apply due to changes in the repo, the conflicts have to be resolved manually. Once fixed, `patch-save.sh` will take the changes staged in Git and update `upgradeable.patch` to match. diff --git a/lib_openzeppelin_contracts/scripts/upgradeable/patch-apply.sh b/lib_openzeppelin_contracts/scripts/upgradeable/patch-apply.sh deleted file mode 100755 index d9e1758..0000000 --- a/lib_openzeppelin_contracts/scripts/upgradeable/patch-apply.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" -PATCH="$DIRNAME/upgradeable.patch" - -error() { - echo Error: "$*" >&2 - exit 1 -} - -if ! git diff-files --quiet ":!$PATCH" || ! git diff-index --quiet HEAD ":!$PATCH"; then - error "Repository must have no staged or unstaged changes" -fi - -if ! git apply -3 "$PATCH"; then - error "Fix conflicts and run $DIRNAME/patch-save.sh" -fi diff --git a/lib_openzeppelin_contracts/scripts/upgradeable/patch-save.sh b/lib_openzeppelin_contracts/scripts/upgradeable/patch-save.sh deleted file mode 100755 index 111e6f1..0000000 --- a/lib_openzeppelin_contracts/scripts/upgradeable/patch-save.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" -PATCH="$DIRNAME/upgradeable.patch" - -error() { - echo Error: "$*" >&2 - exit 1 -} - -if ! git diff-files --quiet ":!$PATCH"; then - error "Unstaged changes. Stage to include in patch or temporarily stash." -fi - -git diff-index --cached --patch --output="$PATCH" HEAD -git restore --staged --worktree ":!$PATCH" diff --git a/lib_openzeppelin_contracts/scripts/upgradeable/transpile-onto.sh b/lib_openzeppelin_contracts/scripts/upgradeable/transpile-onto.sh deleted file mode 100644 index b8508f0..0000000 --- a/lib_openzeppelin_contracts/scripts/upgradeable/transpile-onto.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [ $# -lt 1 ]; then - echo "usage: bash $0 []" >&2 - exit 1 -fi - -set -x - -target="$1" -base="${2-}" - -bash scripts/upgradeable/transpile.sh - -commit="$(git rev-parse --short HEAD)" -start_branch="$(git rev-parse --abbrev-ref HEAD)" - -git add contracts - -# detach from the current branch to avoid making changes to it -git checkout --quiet --detach - -# switch to the target branch, creating it if necessary -if git rev-parse -q --verify "$target"; then - # if the branch exists, make it the current HEAD without checking out its contents - git reset --soft "$target" - git checkout "$target" -else - # if the branch doesn't exist, create it as an orphan and check it out - git checkout --orphan "$target" - if [ -n "$base" ] && git rev-parse -q --verify "$base"; then - # if base was specified and it exists, set it as the branch history - git reset --soft "$base" - fi -fi - -# abort if there are no changes to commit at this point -if git diff --quiet --cached; then - exit -fi - -if [[ -v SUBMODULE_REMOTE ]]; then - lib=lib/openzeppelin-contracts - git submodule add -b "${base#origin/}" "$SUBMODULE_REMOTE" "$lib" - git -C "$lib" checkout "$commit" - git add "$lib" -fi - -git commit -m "Transpile $commit" - -# return to original branch -git checkout "$start_branch" diff --git a/lib_openzeppelin_contracts/scripts/upgradeable/transpile.sh b/lib_openzeppelin_contracts/scripts/upgradeable/transpile.sh deleted file mode 100644 index f7c848c..0000000 --- a/lib_openzeppelin_contracts/scripts/upgradeable/transpile.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -x - -VERSION="$(jq -r .version contracts/package.json)" -DIRNAME="$(dirname -- "${BASH_SOURCE[0]}")" - -bash "$DIRNAME/patch-apply.sh" -sed -i'' -e "s//$VERSION/g" "contracts/package.json" -git add contracts/package.json - -npm run clean -npm run compile - -build_info=($(jq -r '.input.sources | keys | if any(test("^contracts/mocks/.*\\bunreachable\\b")) then empty else input_filename end' artifacts/build-info/*)) -build_info_num=${#build_info[@]} - -if [ $build_info_num -ne 1 ]; then - echo "found $build_info_num relevant build info files but expected just 1" - exit 1 -fi - -# -D: delete original and excluded files -# -b: use this build info file -# -i: use included Initializable -# -x: exclude proxy-related contracts with a few exceptions -# -p: emit public initializer -# -n: use namespaces -# -N: exclude from namespaces transformation -# -q: partial transpilation using @openzeppelin/contracts as peer project -npx @openzeppelin/upgrade-safe-transpiler -D \ - -b "$build_info" \ - -i contracts/proxy/utils/Initializable.sol \ - -x 'contracts-exposed/**/*' \ - -x 'contracts/proxy/**/*' \ - -x '!contracts/proxy/Clones.sol' \ - -x '!contracts/proxy/ERC1967/ERC1967Storage.sol' \ - -x '!contracts/proxy/ERC1967/ERC1967Utils.sol' \ - -x '!contracts/proxy/utils/UUPSUpgradeable.sol' \ - -x '!contracts/proxy/beacon/IBeacon.sol' \ - -p 'contracts/access/manager/AccessManager.sol' \ - -p 'contracts/finance/VestingWallet.sol' \ - -p 'contracts/governance/TimelockController.sol' \ - -p 'contracts/metatx/ERC2771Forwarder.sol' \ - -n \ - -N 'contracts/mocks/**/*' \ - -q '@openzeppelin/' - -# delete compilation artifacts of vanilla code -npm run clean diff --git a/lib_openzeppelin_contracts/scripts/upgradeable/upgradeable.patch b/lib_openzeppelin_contracts/scripts/upgradeable/upgradeable.patch deleted file mode 100644 index 458ecd4..0000000 --- a/lib_openzeppelin_contracts/scripts/upgradeable/upgradeable.patch +++ /dev/null @@ -1,361 +0,0 @@ -diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md -deleted file mode 100644 -index 35ad097ff..000000000 ---- a/.github/ISSUE_TEMPLATE/bug_report.md -+++ /dev/null -@@ -1,21 +0,0 @@ ----- --name: Bug report --about: Report a bug in OpenZeppelin Contracts -- ----- -- -- -- -- -- --**💻 Environment** -- -- -- --**📝 Details** -- -- -- --**🔢 Code to reproduce bug** -- -- -diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml -index 4018cef29..d343a53d8 100644 ---- a/.github/ISSUE_TEMPLATE/config.yml -+++ b/.github/ISSUE_TEMPLATE/config.yml -@@ -1,4 +1,8 @@ -+blank_issues_enabled: false - contact_links: -+ - name: Bug Reports & Feature Requests -+ url: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/new/choose -+ about: Visit the OpenZeppelin Contracts repository - - name: Questions & Support Requests - url: https://forum.openzeppelin.com/c/support/contracts/18 - about: Ask in the OpenZeppelin Forum -diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md -deleted file mode 100644 -index ff596b0c3..000000000 ---- a/.github/ISSUE_TEMPLATE/feature_request.md -+++ /dev/null -@@ -1,14 +0,0 @@ ----- --name: Feature request --about: Suggest an idea for OpenZeppelin Contracts -- ----- -- --**🧐 Motivation** -- -- --**📝 Details** -- -- -- -- -diff --git a/README.md b/README.md -index fa7b4e31e..4799b6376 100644 ---- a/README.md -+++ b/README.md -@@ -19,6 +19,9 @@ - > [!IMPORTANT] - > OpenZeppelin Contracts uses semantic versioning to communicate backwards compatibility of its API and storage layout. For upgradeable contracts, the storage layout of different major versions should be assumed incompatible, for example, it is unsafe to upgrade from 4.9.3 to 5.0.0. Learn more at [Backwards Compatibility](https://docs.openzeppelin.com/contracts/backwards-compatibility). - -++> [!NOTE] -++> You are looking at the upgradeable variant of OpenZeppelin Contracts. Be sure to review the documentation on [Using OpenZeppelin Contracts with Upgrades](https://docs.openzeppelin.com/contracts/upgradeable). -++ - ## Overview - - ### Installation -@@ -26,7 +29,7 @@ - #### Hardhat (npm) - - ``` --$ npm install @openzeppelin/contracts -+$ npm install @openzeppelin/contracts-upgradeable - ``` - - #### Foundry (git) -@@ -38,10 +41,10 @@ $ npm install @openzeppelin/contracts - > Foundry installs the latest version initially, but subsequent `forge update` commands will use the `master` branch. - - ``` --$ forge install OpenZeppelin/openzeppelin-contracts -+$ forge install OpenZeppelin/openzeppelin-contracts-upgradeable - ``` - --Add `@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/` in `remappings.txt.` -+Add `@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/` in `remappings.txt.` - - ### Usage - -@@ -50,10 +53,11 @@ Once installed, you can use the contracts in the library by importing them: - ```solidity - pragma solidity ^0.8.20; - --import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -+import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; - --contract MyCollectible is ERC721 { -- constructor() ERC721("MyCollectible", "MCO") { -+contract MyCollectible is ERC721Upgradeable { -+ function initialize() initializer public { -+ __ERC721_init("MyCollectible", "MCO"); - } - } - ``` -diff --git a/contracts/package.json b/contracts/package.json -index 845e8c403..8dc181b91 100644 ---- a/contracts/package.json -+++ b/contracts/package.json -@@ -1,5 +1,5 @@ - { -- "name": "@openzeppelin/contracts", -+ "name": "@openzeppelin/contracts-upgradeable", - "description": "Secure Smart Contract library for Solidity", - "version": "5.0.2", - "files": [ -@@ -13,7 +13,7 @@ - }, - "repository": { - "type": "git", -- "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" -+ "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git" - }, - "keywords": [ - "solidity", -@@ -28,5 +28,8 @@ - "bugs": { - "url": "https://github.com/OpenZeppelin/openzeppelin-contracts/issues" - }, -- "homepage": "https://openzeppelin.com/contracts/" -+ "homepage": "https://openzeppelin.com/contracts/", -+ "peerDependencies": { -+ "@openzeppelin/contracts": "" -+ } - } -diff --git a/contracts/utils/cryptography/EIP712.sol b/contracts/utils/cryptography/EIP712.sol -index 77c4c8990..602467f40 100644 ---- a/contracts/utils/cryptography/EIP712.sol -+++ b/contracts/utils/cryptography/EIP712.sol -@@ -4,7 +4,6 @@ - pragma solidity ^0.8.20; - - import {MessageHashUtils} from "./MessageHashUtils.sol"; --import {ShortStrings, ShortString} from "../ShortStrings.sol"; - import {IERC5267} from "../../interfaces/IERC5267.sol"; - - /** -@@ -28,28 +27,18 @@ import {IERC5267} from "../../interfaces/IERC5267.sol"; - * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain - * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the - * separator from the immutable values, which is cheaper than accessing a cached version in cold storage. -- * -- * @custom:oz-upgrades-unsafe-allow state-variable-immutable - */ - abstract contract EIP712 is IERC5267 { -- using ShortStrings for *; -- - bytes32 private constant TYPE_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - -- // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to -- // invalidate the cached domain separator if the chain id changes. -- bytes32 private immutable _cachedDomainSeparator; -- uint256 private immutable _cachedChainId; -- address private immutable _cachedThis; -- -+ /// @custom:oz-renamed-from _HASHED_NAME - bytes32 private immutable _hashedName; -+ /// @custom:oz-renamed-from _HASHED_VERSION - bytes32 private immutable _hashedVersion; - -- ShortString private immutable _name; -- ShortString private immutable _version; -- string private _nameFallback; -- string private _versionFallback; -+ string private _name; -+ string private _version; - - /** - * @dev Initializes the domain separator and parameter caches. -@@ -64,29 +53,23 @@ abstract contract EIP712 is IERC5267 { - * contract upgrade]. - */ - constructor(string memory name, string memory version) { -- _name = name.toShortStringWithFallback(_nameFallback); -- _version = version.toShortStringWithFallback(_versionFallback); -- _hashedName = keccak256(bytes(name)); -- _hashedVersion = keccak256(bytes(version)); -- -- _cachedChainId = block.chainid; -- _cachedDomainSeparator = _buildDomainSeparator(); -- _cachedThis = address(this); -+ _name = name; -+ _version = version; -+ -+ // Reset prior values in storage if upgrading -+ _hashedName = 0; -+ _hashedVersion = 0; - } - - /** - * @dev Returns the domain separator for the current chain. - */ - function _domainSeparatorV4() internal view returns (bytes32) { -- if (address(this) == _cachedThis && block.chainid == _cachedChainId) { -- return _cachedDomainSeparator; -- } else { -- return _buildDomainSeparator(); -- } -+ return _buildDomainSeparator(); - } - - function _buildDomainSeparator() private view returns (bytes32) { -- return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this))); -+ return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this))); - } - - /** -@@ -125,6 +108,10 @@ abstract contract EIP712 is IERC5267 { - uint256[] memory extensions - ) - { -+ // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized -+ // and the EIP712 domain is not reliable, as it will be missing name and version. -+ require(_hashedName == 0 && _hashedVersion == 0, "EIP712: Uninitialized"); -+ - return ( - hex"0f", // 01111 - _EIP712Name(), -@@ -139,22 +126,62 @@ abstract contract EIP712 is IERC5267 { - /** - * @dev The name parameter for the EIP712 domain. - * -- * NOTE: By default this function reads _name which is an immutable value. -- * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). -+ * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs -+ * are a concern. - */ -- // solhint-disable-next-line func-name-mixedcase -- function _EIP712Name() internal view returns (string memory) { -- return _name.toStringWithFallback(_nameFallback); -+ function _EIP712Name() internal view virtual returns (string memory) { -+ return _name; - } - - /** - * @dev The version parameter for the EIP712 domain. - * -- * NOTE: By default this function reads _version which is an immutable value. -- * It only reads from storage if necessary (in case the value is too large to fit in a ShortString). -+ * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs -+ * are a concern. - */ -- // solhint-disable-next-line func-name-mixedcase -- function _EIP712Version() internal view returns (string memory) { -- return _version.toStringWithFallback(_versionFallback); -+ function _EIP712Version() internal view virtual returns (string memory) { -+ return _version; -+ } -+ -+ /** -+ * @dev The hash of the name parameter for the EIP712 domain. -+ * -+ * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead. -+ */ -+ function _EIP712NameHash() internal view returns (bytes32) { -+ string memory name = _EIP712Name(); -+ if (bytes(name).length > 0) { -+ return keccak256(bytes(name)); -+ } else { -+ // If the name is empty, the contract may have been upgraded without initializing the new storage. -+ // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design. -+ bytes32 hashedName = _hashedName; -+ if (hashedName != 0) { -+ return hashedName; -+ } else { -+ return keccak256(""); -+ } -+ } -+ } -+ -+ /** -+ * @dev The hash of the version parameter for the EIP712 domain. -+ * -+ * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead. -+ */ -+ function _EIP712VersionHash() internal view returns (bytes32) { -+ string memory version = _EIP712Version(); -+ if (bytes(version).length > 0) { -+ return keccak256(bytes(version)); -+ } else { -+ // If the version is empty, the contract may have been upgraded without initializing the new storage. -+ // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design. -+ bytes32 hashedVersion = _hashedVersion; -+ if (hashedVersion != 0) { -+ return hashedVersion; -+ } else { -+ return keccak256(""); -+ } -+ } - } - } -diff --git a/package.json b/package.json -index c4b358e10..96ab2559c 100644 ---- a/package.json -+++ b/package.json -@@ -32,7 +32,7 @@ - }, - "repository": { - "type": "git", -- "url": "https://github.com/OpenZeppelin/openzeppelin-contracts.git" -+ "url": "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git" - }, - "keywords": [ - "solidity", -diff --git a/remappings.txt b/remappings.txt -index 304d1386a..a1cd63bee 100644 ---- a/remappings.txt -+++ b/remappings.txt -@@ -1 +1,2 @@ --@openzeppelin/contracts/=contracts/ -+@openzeppelin/contracts-upgradeable/=contracts/ -+@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ -diff --git a/test/utils/cryptography/EIP712.test.js b/test/utils/cryptography/EIP712.test.js -index 2b6e7fa97..268e0d29d 100644 ---- a/test/utils/cryptography/EIP712.test.js -+++ b/test/utils/cryptography/EIP712.test.js -@@ -47,27 +47,6 @@ describe('EIP712', function () { - const rebuildDomain = await getDomain(this.eip712); - expect(rebuildDomain).to.be.deep.equal(this.domain); - }); -- -- if (shortOrLong === 'short') { -- // Long strings are in storage, and the proxy will not be properly initialized unless -- // the upgradeable contract variant is used and the initializer is invoked. -- -- it('adjusts when behind proxy', async function () { -- const factory = await ethers.deployContract('$Clones'); -- -- const clone = await factory -- .$clone(this.eip712) -- .then(tx => tx.wait()) -- .then(receipt => receipt.logs.find(ev => ev.fragment.name == 'return$clone_address').args.instance) -- .then(address => ethers.getContractAt('$EIP712Verifier', address)); -- -- const expectedDomain = { ...this.domain, verifyingContract: clone.target }; -- expect(await getDomain(clone)).to.be.deep.equal(expectedDomain); -- -- const expectedSeparator = await domainSeparator(expectedDomain); -- expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator); -- }); -- } - }); - - it('hash digest', async function () { diff --git a/lib_openzeppelin_contracts/slither.config.json b/lib_openzeppelin_contracts/slither.config.json deleted file mode 100644 index 069da1f..0000000 --- a/lib_openzeppelin_contracts/slither.config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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", - "compile_force_framework": "hardhat" -} diff --git a/lib_openzeppelin_contracts/solhint.config.js b/lib_openzeppelin_contracts/solhint.config.js deleted file mode 100644 index f0bd799..0000000 --- a/lib_openzeppelin_contracts/solhint.config.js +++ /dev/null @@ -1,26 +0,0 @@ -const customRules = require('./scripts/solhint-custom'); - -const rules = [ - 'avoid-tx-origin', - 'const-name-snakecase', - 'contract-name-camelcase', - 'event-name-camelcase', - 'explicit-types', - 'func-name-mixedcase', - 'func-param-name-mixedcase', - 'imports-on-top', - 'modifier-name-mixedcase', - 'no-console', - 'no-global-import', - 'no-unused-vars', - 'quotes', - 'use-forbidden-name', - 'var-name-mixedcase', - 'visibility-modifier-order', - ...customRules.map(r => `openzeppelin/${r.ruleId}`), -]; - -module.exports = { - plugins: ['openzeppelin'], - rules: Object.fromEntries(rules.map(r => [r, 'error'])), -}; diff --git a/lib_openzeppelin_contracts/test/TESTING.md b/lib_openzeppelin_contracts/test/TESTING.md deleted file mode 100644 index a5ee932..0000000 --- a/lib_openzeppelin_contracts/test/TESTING.md +++ /dev/null @@ -1,3 +0,0 @@ -## Testing - -Unit test are critical to OpenZeppelin Contracts. They help ensure code quality and mitigate against security vulnerabilities. The directory structure within the `/test` directory corresponds to the `/contracts` directory. diff --git a/lib_openzeppelin_contracts/test/access/AccessControl.behavior.js b/lib_openzeppelin_contracts/test/access/AccessControl.behavior.js deleted file mode 100644 index b7ae2a9..0000000 --- a/lib_openzeppelin_contracts/test/access/AccessControl.behavior.js +++ /dev/null @@ -1,874 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -const time = require('../helpers/time'); - -const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior'); - -const DEFAULT_ADMIN_ROLE = ethers.ZeroHash; -const ROLE = ethers.id('ROLE'); -const OTHER_ROLE = ethers.id('OTHER_ROLE'); - -function shouldBehaveLikeAccessControl() { - beforeEach(async function () { - [this.authorized, this.other, this.otherAdmin] = this.accounts; - }); - - shouldSupportInterfaces(['AccessControl']); - - describe('default admin', function () { - it('deployer has default admin role', async function () { - expect(await this.mock.hasRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin)).to.be.true; - }); - - it("other roles's admin is the default admin role", async function () { - expect(await this.mock.getRoleAdmin(ROLE)).to.equal(DEFAULT_ADMIN_ROLE); - }); - - it("default admin role's admin is itself", async function () { - expect(await this.mock.getRoleAdmin(DEFAULT_ADMIN_ROLE)).to.equal(DEFAULT_ADMIN_ROLE); - }); - }); - - describe('granting', function () { - beforeEach(async function () { - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized); - }); - - it('non-admin cannot grant role to other accounts', async function () { - await expect(this.mock.connect(this.other).grantRole(ROLE, this.authorized)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, DEFAULT_ADMIN_ROLE); - }); - - it('accounts can be granted a role multiple times', async function () { - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized); - await expect(this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized)).to.not.emit( - this.mock, - 'RoleGranted', - ); - }); - }); - - describe('revoking', function () { - it('roles that are not had can be revoked', async function () { - expect(await this.mock.hasRole(ROLE, this.authorized)).to.be.false; - - await expect(this.mock.connect(this.defaultAdmin).revokeRole(ROLE, this.authorized)).to.not.emit( - this.mock, - 'RoleRevoked', - ); - }); - - describe('with granted role', function () { - beforeEach(async function () { - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized); - }); - - it('admin can revoke role', async function () { - await expect(this.mock.connect(this.defaultAdmin).revokeRole(ROLE, this.authorized)) - .to.emit(this.mock, 'RoleRevoked') - .withArgs(ROLE, this.authorized, this.defaultAdmin); - - expect(await this.mock.hasRole(ROLE, this.authorized)).to.be.false; - }); - - it('non-admin cannot revoke role', async function () { - await expect(this.mock.connect(this.other).revokeRole(ROLE, this.authorized)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, DEFAULT_ADMIN_ROLE); - }); - - it('a role can be revoked multiple times', async function () { - await this.mock.connect(this.defaultAdmin).revokeRole(ROLE, this.authorized); - - await expect(this.mock.connect(this.defaultAdmin).revokeRole(ROLE, this.authorized)).to.not.emit( - this.mock, - 'RoleRevoked', - ); - }); - }); - }); - - describe('renouncing', function () { - it('roles that are not had can be renounced', async function () { - await expect(this.mock.connect(this.authorized).renounceRole(ROLE, this.authorized)).to.not.emit( - this.mock, - 'RoleRevoked', - ); - }); - - describe('with granted role', function () { - beforeEach(async function () { - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized); - }); - - it('bearer can renounce role', async function () { - await expect(this.mock.connect(this.authorized).renounceRole(ROLE, this.authorized)) - .to.emit(this.mock, 'RoleRevoked') - .withArgs(ROLE, this.authorized, this.authorized); - - expect(await this.mock.hasRole(ROLE, this.authorized)).to.be.false; - }); - - it('only the sender can renounce their roles', async function () { - await expect( - this.mock.connect(this.defaultAdmin).renounceRole(ROLE, this.authorized), - ).to.be.revertedWithCustomError(this.mock, 'AccessControlBadConfirmation'); - }); - - it('a role can be renounced multiple times', async function () { - await this.mock.connect(this.authorized).renounceRole(ROLE, this.authorized); - - await expect(this.mock.connect(this.authorized).renounceRole(ROLE, this.authorized)).not.to.emit( - this.mock, - 'RoleRevoked', - ); - }); - }); - }); - - describe('setting role admin', function () { - beforeEach(async function () { - await expect(this.mock.$_setRoleAdmin(ROLE, OTHER_ROLE)) - .to.emit(this.mock, 'RoleAdminChanged') - .withArgs(ROLE, DEFAULT_ADMIN_ROLE, OTHER_ROLE); - - await this.mock.connect(this.defaultAdmin).grantRole(OTHER_ROLE, this.otherAdmin); - }); - - it("a role's admin role can be changed", async function () { - expect(await this.mock.getRoleAdmin(ROLE)).to.equal(OTHER_ROLE); - }); - - it('the new admin can grant roles', async function () { - await expect(this.mock.connect(this.otherAdmin).grantRole(ROLE, this.authorized)) - .to.emit(this.mock, 'RoleGranted') - .withArgs(ROLE, this.authorized, this.otherAdmin); - }); - - it('the new admin can revoke roles', async function () { - await this.mock.connect(this.otherAdmin).grantRole(ROLE, this.authorized); - await expect(this.mock.connect(this.otherAdmin).revokeRole(ROLE, this.authorized)) - .to.emit(this.mock, 'RoleRevoked') - .withArgs(ROLE, this.authorized, this.otherAdmin); - }); - - it("a role's previous admins no longer grant roles", async function () { - await expect(this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.defaultAdmin, OTHER_ROLE); - }); - - it("a role's previous admins no longer revoke roles", async function () { - await expect(this.mock.connect(this.defaultAdmin).revokeRole(ROLE, this.authorized)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.defaultAdmin, OTHER_ROLE); - }); - }); - - describe('onlyRole modifier', function () { - beforeEach(async function () { - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized); - }); - - it('do not revert if sender has role', async function () { - await this.mock.connect(this.authorized).$_checkRole(ROLE); - }); - - it("revert if sender doesn't have role #1", async function () { - await expect(this.mock.connect(this.other).$_checkRole(ROLE)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, ROLE); - }); - - it("revert if sender doesn't have role #2", async function () { - await expect(this.mock.connect(this.authorized).$_checkRole(OTHER_ROLE)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.authorized, OTHER_ROLE); - }); - }); - - describe('internal functions', function () { - describe('_grantRole', function () { - it('return true if the account does not have the role', async function () { - await expect(this.mock.$_grantRole(ROLE, this.authorized)) - .to.emit(this.mock, 'return$_grantRole') - .withArgs(true); - }); - - it('return false if the account has the role', async function () { - await this.mock.$_grantRole(ROLE, this.authorized); - - await expect(this.mock.$_grantRole(ROLE, this.authorized)) - .to.emit(this.mock, 'return$_grantRole') - .withArgs(false); - }); - }); - - describe('_revokeRole', function () { - it('return true if the account has the role', async function () { - await this.mock.$_grantRole(ROLE, this.authorized); - - await expect(this.mock.$_revokeRole(ROLE, this.authorized)) - .to.emit(this.mock, 'return$_revokeRole') - .withArgs(true); - }); - - it('return false if the account does not have the role', async function () { - await expect(this.mock.$_revokeRole(ROLE, this.authorized)) - .to.emit(this.mock, 'return$_revokeRole') - .withArgs(false); - }); - }); - }); -} - -function shouldBehaveLikeAccessControlEnumerable() { - beforeEach(async function () { - [this.authorized, this.other, this.otherAdmin, this.otherAuthorized] = this.accounts; - }); - - shouldSupportInterfaces(['AccessControlEnumerable']); - - describe('enumerating', function () { - it('role bearers can be enumerated', async function () { - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.authorized); - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.other); - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.otherAuthorized); - await this.mock.connect(this.defaultAdmin).revokeRole(ROLE, this.other); - - const expectedMembers = [this.authorized.address, this.otherAuthorized.address]; - - const memberCount = await this.mock.getRoleMemberCount(ROLE); - const members = []; - for (let i = 0; i < memberCount; ++i) { - members.push(await this.mock.getRoleMember(ROLE, i)); - } - - expect(memberCount).to.equal(expectedMembers.length); - expect(members).to.deep.equal(expectedMembers); - expect(await this.mock.getRoleMembers(ROLE)).to.deep.equal(expectedMembers); - }); - - it('role enumeration should be in sync after renounceRole call', async function () { - expect(await this.mock.getRoleMemberCount(ROLE)).to.equal(0); - await this.mock.connect(this.defaultAdmin).grantRole(ROLE, this.defaultAdmin); - expect(await this.mock.getRoleMemberCount(ROLE)).to.equal(1); - await this.mock.connect(this.defaultAdmin).renounceRole(ROLE, this.defaultAdmin); - expect(await this.mock.getRoleMemberCount(ROLE)).to.equal(0); - }); - }); -} - -function shouldBehaveLikeAccessControlDefaultAdminRules() { - shouldSupportInterfaces(['AccessControlDefaultAdminRules']); - - beforeEach(async function () { - [this.newDefaultAdmin, this.other] = this.accounts; - }); - - for (const getter of ['owner', 'defaultAdmin']) { - describe(`${getter}()`, function () { - it('has a default set to the initial default admin', async function () { - const value = await this.mock[getter](); - expect(value).to.equal(this.defaultAdmin); - expect(await this.mock.hasRole(DEFAULT_ADMIN_ROLE, value)).to.be.true; - }); - - it('changes if the default admin changes', async function () { - // Starts an admin transfer - await this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.newDefaultAdmin); - - // Wait for acceptance - await time.increaseBy.timestamp(this.delay + 1n, false); - await this.mock.connect(this.newDefaultAdmin).acceptDefaultAdminTransfer(); - - const value = await this.mock[getter](); - expect(value).to.equal(this.newDefaultAdmin); - }); - }); - } - - describe('pendingDefaultAdmin()', function () { - it('returns 0 if no pending default admin transfer', async function () { - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(ethers.ZeroAddress); - expect(schedule).to.equal(0); - }); - - describe('when there is a scheduled default admin transfer', function () { - beforeEach('begins admin transfer', async function () { - await this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.newDefaultAdmin); - }); - - for (const [fromSchedule, tag] of [ - [-1n, 'before'], - [0n, 'exactly when'], - [1n, 'after'], - ]) { - it(`returns pending admin and schedule ${tag} it passes if not accepted`, async function () { - // Wait until schedule + fromSchedule - const { schedule: firstSchedule } = await this.mock.pendingDefaultAdmin(); - await time.increaseTo.timestamp(firstSchedule + fromSchedule); - - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(this.newDefaultAdmin); - expect(schedule).to.equal(firstSchedule); - }); - } - - it('returns 0 after schedule passes and the transfer was accepted', async function () { - // Wait after schedule - const { schedule: firstSchedule } = await this.mock.pendingDefaultAdmin(); - await time.increaseTo.timestamp(firstSchedule + 1n, false); - - // Accepts - await this.mock.connect(this.newDefaultAdmin).acceptDefaultAdminTransfer(); - - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(ethers.ZeroAddress); - expect(schedule).to.equal(0); - }); - }); - }); - - describe('defaultAdminDelay()', function () { - it('returns the current delay', async function () { - expect(await this.mock.defaultAdminDelay()).to.equal(this.delay); - }); - - describe('when there is a scheduled delay change', function () { - const newDelay = 0x1337n; // Any change - - beforeEach('begins delay change', async function () { - await this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(newDelay); - }); - - for (const [fromSchedule, tag, expectNew, delayTag] of [ - [-1n, 'before', false, 'old'], - [0n, 'exactly when', false, 'old'], - [1n, 'after', true, 'new'], - ]) { - it(`returns ${delayTag} delay ${tag} delay schedule passes`, async function () { - // Wait until schedule + fromSchedule - const { schedule } = await this.mock.pendingDefaultAdminDelay(); - await time.increaseTo.timestamp(schedule + fromSchedule); - - const currentDelay = await this.mock.defaultAdminDelay(); - expect(currentDelay).to.equal(expectNew ? newDelay : this.delay); - }); - } - }); - }); - - describe('pendingDefaultAdminDelay()', function () { - it('returns 0 if not set', async function () { - const { newDelay, schedule } = await this.mock.pendingDefaultAdminDelay(); - expect(newDelay).to.equal(0); - expect(schedule).to.equal(0); - }); - - describe('when there is a scheduled delay change', function () { - const newDelay = 0x1337n; // Any change - - beforeEach('begins admin transfer', async function () { - await this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(newDelay); - }); - - for (const [fromSchedule, tag, expectedDelay, delayTag, expectZeroSchedule] of [ - [-1n, 'before', newDelay, 'new'], - [0n, 'exactly when', newDelay, 'new'], - [1n, 'after', 0, 'zero', true], - ]) { - it(`returns ${delayTag} delay ${tag} delay schedule passes`, async function () { - // Wait until schedule + fromSchedule - const { schedule: firstSchedule } = await this.mock.pendingDefaultAdminDelay(); - await time.increaseTo.timestamp(firstSchedule + fromSchedule); - - const { newDelay, schedule } = await this.mock.pendingDefaultAdminDelay(); - expect(newDelay).to.equal(expectedDelay); - expect(schedule).to.equal(expectZeroSchedule ? 0 : firstSchedule); - }); - } - }); - }); - - describe('defaultAdminDelayIncreaseWait()', function () { - it('should return 5 days (default)', async function () { - expect(await this.mock.defaultAdminDelayIncreaseWait()).to.equal(time.duration.days(5)); - }); - }); - - it('should revert if granting default admin role', async function () { - await expect( - this.mock.connect(this.defaultAdmin).grantRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin), - ).to.be.revertedWithCustomError(this.mock, 'AccessControlEnforcedDefaultAdminRules'); - }); - - it('should revert if revoking default admin role', async function () { - await expect( - this.mock.connect(this.defaultAdmin).revokeRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin), - ).to.be.revertedWithCustomError(this.mock, 'AccessControlEnforcedDefaultAdminRules'); - }); - - it("should revert if defaultAdmin's admin is changed", async function () { - await expect(this.mock.$_setRoleAdmin(DEFAULT_ADMIN_ROLE, OTHER_ROLE)).to.be.revertedWithCustomError( - this.mock, - 'AccessControlEnforcedDefaultAdminRules', - ); - }); - - it('should not grant the default admin role twice', async function () { - await expect(this.mock.$_grantRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin)).to.be.revertedWithCustomError( - this.mock, - 'AccessControlEnforcedDefaultAdminRules', - ); - }); - - describe('begins a default admin transfer', function () { - it('reverts if called by non default admin accounts', async function () { - await expect(this.mock.connect(this.other).beginDefaultAdminTransfer(this.newDefaultAdmin)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, DEFAULT_ADMIN_ROLE); - }); - - describe('when there is no pending delay nor pending admin transfer', function () { - it('should set pending default admin and schedule', async function () { - const nextBlockTimestamp = (await time.clock.timestamp()) + 1n; - const acceptSchedule = nextBlockTimestamp + this.delay; - - await time.increaseTo.timestamp(nextBlockTimestamp, false); // set timestamp but don't mine the block yet - await expect(this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.newDefaultAdmin)) - .to.emit(this.mock, 'DefaultAdminTransferScheduled') - .withArgs(this.newDefaultAdmin, acceptSchedule); - - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(this.newDefaultAdmin); - expect(schedule).to.equal(acceptSchedule); - }); - }); - - describe('when there is a pending admin transfer', function () { - beforeEach('sets a pending default admin transfer', async function () { - await this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.newDefaultAdmin); - this.acceptSchedule = (await time.clock.timestamp()) + this.delay; - }); - - for (const [fromSchedule, tag] of [ - [-1n, 'before'], - [0n, 'exactly when'], - [1n, 'after'], - ]) { - it(`should be able to begin a transfer again ${tag} acceptSchedule passes`, async function () { - // Wait until schedule + fromSchedule - await time.increaseTo.timestamp(this.acceptSchedule + fromSchedule, false); - - // defaultAdmin changes its mind and begin again to another address - await expect(this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.other)).to.emit( - this.mock, - 'DefaultAdminTransferCanceled', // Cancellation is always emitted since it was never accepted - ); - const newSchedule = (await time.clock.timestamp()) + this.delay; - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(this.other); - expect(schedule).to.equal(newSchedule); - }); - } - - it('should not emit a cancellation event if the new default admin accepted', async function () { - // Wait until the acceptSchedule has passed - await time.increaseTo.timestamp(this.acceptSchedule + 1n, false); - - // Accept and restart - await this.mock.connect(this.newDefaultAdmin).acceptDefaultAdminTransfer(); - await expect(this.mock.connect(this.newDefaultAdmin).beginDefaultAdminTransfer(this.other)).to.not.emit( - this.mock, - 'DefaultAdminTransferCanceled', - ); - }); - }); - - describe('when there is a pending delay', function () { - const newDelay = time.duration.hours(3); - - beforeEach('schedule a delay change', async function () { - await this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(newDelay); - ({ schedule: this.effectSchedule } = await this.mock.pendingDefaultAdminDelay()); - }); - - for (const [fromSchedule, schedulePassed, expectNewDelay] of [ - [-1n, 'before', false], - [0n, 'exactly when', false], - [1n, 'after', true], - ]) { - it(`should set the ${ - expectNewDelay ? 'new' : 'old' - } delay and apply it to next default admin transfer schedule ${schedulePassed} effectSchedule passed`, async function () { - // Wait until the expected fromSchedule time - const nextBlockTimestamp = this.effectSchedule + fromSchedule; - await time.increaseTo.timestamp(nextBlockTimestamp, false); - - // Start the new default admin transfer and get its schedule - const expectedDelay = expectNewDelay ? newDelay : this.delay; - const expectedAcceptSchedule = nextBlockTimestamp + expectedDelay; - await expect(this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.newDefaultAdmin)) - .to.emit(this.mock, 'DefaultAdminTransferScheduled') - .withArgs(this.newDefaultAdmin, expectedAcceptSchedule); - - // Check that the schedule corresponds with the new delay - const { newAdmin, schedule: transferSchedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(this.newDefaultAdmin); - expect(transferSchedule).to.equal(expectedAcceptSchedule); - }); - } - }); - }); - - describe('accepts transfer admin', function () { - beforeEach(async function () { - await this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.newDefaultAdmin); - this.acceptSchedule = (await time.clock.timestamp()) + this.delay; - }); - - it('should revert if caller is not pending default admin', async function () { - await time.increaseTo.timestamp(this.acceptSchedule + 1n, false); - await expect(this.mock.connect(this.other).acceptDefaultAdminTransfer()) - .to.be.revertedWithCustomError(this.mock, 'AccessControlInvalidDefaultAdmin') - .withArgs(this.other); - }); - - describe('when caller is pending default admin and delay has passed', function () { - beforeEach(async function () { - await time.increaseTo.timestamp(this.acceptSchedule + 1n, false); - }); - - it('accepts a transfer and changes default admin', async function () { - // Emit events - await expect(this.mock.connect(this.newDefaultAdmin).acceptDefaultAdminTransfer()) - .to.emit(this.mock, 'RoleRevoked') - .withArgs(DEFAULT_ADMIN_ROLE, this.defaultAdmin, this.newDefaultAdmin) - .to.emit(this.mock, 'RoleGranted') - .withArgs(DEFAULT_ADMIN_ROLE, this.newDefaultAdmin, this.newDefaultAdmin); - - // Storage changes - expect(await this.mock.hasRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin)).to.be.false; - expect(await this.mock.hasRole(DEFAULT_ADMIN_ROLE, this.newDefaultAdmin)).to.be.true; - expect(await this.mock.owner()).to.equal(this.newDefaultAdmin); - - // Resets pending default admin and schedule - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(ethers.ZeroAddress); - expect(schedule).to.equal(0); - }); - }); - - describe('schedule not passed', function () { - for (const [fromSchedule, tag] of [ - [-1n, 'less'], - [0n, 'equal'], - ]) { - it(`should revert if block.timestamp is ${tag} to schedule`, async function () { - await time.increaseTo.timestamp(this.acceptSchedule + fromSchedule, false); - await expect(this.mock.connect(this.newDefaultAdmin).acceptDefaultAdminTransfer()) - .to.be.revertedWithCustomError(this.mock, 'AccessControlEnforcedDefaultAdminDelay') - .withArgs(this.acceptSchedule); - }); - } - }); - }); - - describe('cancels a default admin transfer', function () { - it('reverts if called by non default admin accounts', async function () { - await expect(this.mock.connect(this.other).cancelDefaultAdminTransfer()) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, DEFAULT_ADMIN_ROLE); - }); - - describe('when there is a pending default admin transfer', function () { - beforeEach(async function () { - await this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(this.newDefaultAdmin); - this.acceptSchedule = (await time.clock.timestamp()) + this.delay; - }); - - for (const [fromSchedule, tag] of [ - [-1n, 'before'], - [0n, 'exactly when'], - [1n, 'after'], - ]) { - it(`resets pending default admin and schedule ${tag} transfer schedule passes`, async function () { - // Advance until passed delay - await time.increaseTo.timestamp(this.acceptSchedule + fromSchedule, false); - - await expect(this.mock.connect(this.defaultAdmin).cancelDefaultAdminTransfer()).to.emit( - this.mock, - 'DefaultAdminTransferCanceled', - ); - - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(ethers.ZeroAddress); - expect(schedule).to.equal(0); - }); - } - - it('should revert if the previous default admin tries to accept', async function () { - await this.mock.connect(this.defaultAdmin).cancelDefaultAdminTransfer(); - - // Advance until passed delay - await time.increaseTo.timestamp(this.acceptSchedule + 1n, false); - - // Previous pending default admin should not be able to accept after cancellation. - await expect(this.mock.connect(this.newDefaultAdmin).acceptDefaultAdminTransfer()) - .to.be.revertedWithCustomError(this.mock, 'AccessControlInvalidDefaultAdmin') - .withArgs(this.newDefaultAdmin); - }); - }); - - describe('when there is no pending default admin transfer', function () { - it('should succeed without changes', async function () { - await expect(this.mock.connect(this.defaultAdmin).cancelDefaultAdminTransfer()).to.not.emit( - this.mock, - 'DefaultAdminTransferCanceled', - ); - - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(ethers.ZeroAddress); - expect(schedule).to.equal(0); - }); - }); - }); - - describe('renounces admin', function () { - beforeEach(async function () { - await this.mock.connect(this.defaultAdmin).beginDefaultAdminTransfer(ethers.ZeroAddress); - this.expectedSchedule = (await time.clock.timestamp()) + this.delay; - }); - - it('reverts if caller is not default admin', async function () { - await time.increaseBy.timestamp(this.delay + 1n, false); - await expect( - this.mock.connect(this.defaultAdmin).renounceRole(DEFAULT_ADMIN_ROLE, this.other), - ).to.be.revertedWithCustomError(this.mock, 'AccessControlBadConfirmation'); - }); - - it("renouncing the admin role when not an admin doesn't affect the schedule", async function () { - await time.increaseBy.timestamp(this.delay + 1n, false); - await this.mock.connect(this.other).renounceRole(DEFAULT_ADMIN_ROLE, this.other); - - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(ethers.ZeroAddress); - expect(schedule).to.equal(this.expectedSchedule); - }); - - it('keeps defaultAdmin consistent with hasRole if another non-defaultAdmin user renounces the DEFAULT_ADMIN_ROLE', async function () { - await time.increaseBy.timestamp(this.delay + 1n, false); - - // This passes because it's a noop - await this.mock.connect(this.other).renounceRole(DEFAULT_ADMIN_ROLE, this.other); - - expect(await this.mock.hasRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin)).to.be.true; - expect(await this.mock.defaultAdmin()).to.equal(this.defaultAdmin); - }); - - it('renounces role', async function () { - await time.increaseBy.timestamp(this.delay + 1n, false); - await expect(this.mock.connect(this.defaultAdmin).renounceRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin)) - .to.emit(this.mock, 'RoleRevoked') - .withArgs(DEFAULT_ADMIN_ROLE, this.defaultAdmin, this.defaultAdmin); - - expect(await this.mock.hasRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin)).to.be.false; - expect(await this.mock.defaultAdmin()).to.equal(ethers.ZeroAddress); - expect(await this.mock.owner()).to.equal(ethers.ZeroAddress); - - const { newAdmin, schedule } = await this.mock.pendingDefaultAdmin(); - expect(newAdmin).to.equal(ethers.ZeroAddress); - expect(schedule).to.equal(0); - }); - - it('allows to recover access using the internal _grantRole', async function () { - await time.increaseBy.timestamp(this.delay + 1n, false); - await this.mock.connect(this.defaultAdmin).renounceRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin); - - await expect(this.mock.connect(this.defaultAdmin).$_grantRole(DEFAULT_ADMIN_ROLE, this.other)) - .to.emit(this.mock, 'RoleGranted') - .withArgs(DEFAULT_ADMIN_ROLE, this.other, this.defaultAdmin); - }); - - describe('schedule not passed', function () { - for (const [fromSchedule, tag] of [ - [-1n, 'less'], - [0n, 'equal'], - ]) { - it(`reverts if block.timestamp is ${tag} to schedule`, async function () { - await time.increaseBy.timestamp(this.delay + fromSchedule, false); - await expect(this.mock.connect(this.defaultAdmin).renounceRole(DEFAULT_ADMIN_ROLE, this.defaultAdmin)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlEnforcedDefaultAdminDelay') - .withArgs(this.expectedSchedule); - }); - } - }); - }); - - describe('changes delay', function () { - it('reverts if called by non default admin accounts', async function () { - await expect(this.mock.connect(this.other).changeDefaultAdminDelay(time.duration.hours(4))) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, DEFAULT_ADMIN_ROLE); - }); - - for (const [delayDifference, delayChangeType] of [ - [time.duration.hours(-1), 'decreased'], - [time.duration.hours(1), 'increased'], - [time.duration.days(5), 'increased to more than 5 days'], - ]) { - describe(`when the delay is ${delayChangeType}`, function () { - beforeEach(function () { - this.newDefaultAdminDelay = this.delay + delayDifference; - }); - - it('begins the delay change to the new delay', async function () { - // Calculate expected values - const capWait = await this.mock.defaultAdminDelayIncreaseWait(); - const minWait = capWait < this.newDefaultAdminDelay ? capWait : this.newDefaultAdminDelay; - const changeDelay = - this.newDefaultAdminDelay <= this.delay ? this.delay - this.newDefaultAdminDelay : minWait; - const nextBlockTimestamp = (await time.clock.timestamp()) + 1n; - const effectSchedule = nextBlockTimestamp + changeDelay; - - await time.increaseTo.timestamp(nextBlockTimestamp, false); - - // Begins the change - await expect(this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(this.newDefaultAdminDelay)) - .to.emit(this.mock, 'DefaultAdminDelayChangeScheduled') - .withArgs(this.newDefaultAdminDelay, effectSchedule); - - // Assert - const { newDelay, schedule } = await this.mock.pendingDefaultAdminDelay(); - expect(newDelay).to.equal(this.newDefaultAdminDelay); - expect(schedule).to.equal(effectSchedule); - }); - - describe('scheduling again', function () { - beforeEach('schedule once', async function () { - await this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(this.newDefaultAdminDelay); - }); - - for (const [fromSchedule, tag] of [ - [-1n, 'before'], - [0n, 'exactly when'], - [1n, 'after'], - ]) { - const passed = fromSchedule > 0; - - it(`succeeds ${tag} the delay schedule passes`, async function () { - // Wait until schedule + fromSchedule - const { schedule: firstSchedule } = await this.mock.pendingDefaultAdminDelay(); - const nextBlockTimestamp = firstSchedule + fromSchedule; - await time.increaseTo.timestamp(nextBlockTimestamp, false); - - // Calculate expected values - const anotherNewDefaultAdminDelay = this.newDefaultAdminDelay + time.duration.hours(2); - const capWait = await this.mock.defaultAdminDelayIncreaseWait(); - const minWait = capWait < anotherNewDefaultAdminDelay ? capWait : anotherNewDefaultAdminDelay; - const effectSchedule = nextBlockTimestamp + minWait; - - // Default admin changes its mind and begins another delay change - await expect(this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(anotherNewDefaultAdminDelay)) - .to.emit(this.mock, 'DefaultAdminDelayChangeScheduled') - .withArgs(anotherNewDefaultAdminDelay, effectSchedule); - - // Assert - const { newDelay, schedule } = await this.mock.pendingDefaultAdminDelay(); - expect(newDelay).to.equal(anotherNewDefaultAdminDelay); - expect(schedule).to.equal(effectSchedule); - }); - - const emit = passed ? 'not emit' : 'emit'; - it(`should ${emit} a cancellation event ${tag} the delay schedule passes`, async function () { - // Wait until schedule + fromSchedule - const { schedule: firstSchedule } = await this.mock.pendingDefaultAdminDelay(); - await time.increaseTo.timestamp(firstSchedule + fromSchedule, false); - - // Default admin changes its mind and begins another delay change - const anotherNewDefaultAdminDelay = this.newDefaultAdminDelay + time.duration.hours(2); - - const expected = expect( - this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(anotherNewDefaultAdminDelay), - ); - if (passed) { - await expected.to.not.emit(this.mock, 'DefaultAdminDelayChangeCanceled'); - } else { - await expected.to.emit(this.mock, 'DefaultAdminDelayChangeCanceled'); - } - }); - } - }); - }); - } - }); - - describe('rollbacks a delay change', function () { - it('reverts if called by non default admin accounts', async function () { - await expect(this.mock.connect(this.other).rollbackDefaultAdminDelay()) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, DEFAULT_ADMIN_ROLE); - }); - - describe('when there is a pending delay', function () { - beforeEach('set pending delay', async function () { - await this.mock.connect(this.defaultAdmin).changeDefaultAdminDelay(time.duration.hours(12)); - }); - - for (const [fromSchedule, tag] of [ - [-1n, 'before'], - [0n, 'exactly when'], - [1n, 'after'], - ]) { - const passed = fromSchedule > 0; - - it(`resets pending delay and schedule ${tag} delay change schedule passes`, async function () { - // Wait until schedule + fromSchedule - const { schedule: firstSchedule } = await this.mock.pendingDefaultAdminDelay(); - await time.increaseTo.timestamp(firstSchedule + fromSchedule, false); - - await this.mock.connect(this.defaultAdmin).rollbackDefaultAdminDelay(); - - const { newDelay, schedule } = await this.mock.pendingDefaultAdminDelay(); - expect(newDelay).to.equal(0); - expect(schedule).to.equal(0); - }); - - const emit = passed ? 'not emit' : 'emit'; - it(`should ${emit} a cancellation event ${tag} the delay schedule passes`, async function () { - // Wait until schedule + fromSchedule - const { schedule: firstSchedule } = await this.mock.pendingDefaultAdminDelay(); - await time.increaseTo.timestamp(firstSchedule + fromSchedule, false); - - const expected = expect(this.mock.connect(this.defaultAdmin).rollbackDefaultAdminDelay()); - if (passed) { - await expected.to.not.emit(this.mock, 'DefaultAdminDelayChangeCanceled'); - } else { - await expected.to.emit(this.mock, 'DefaultAdminDelayChangeCanceled'); - } - }); - } - }); - - describe('when there is no pending delay', function () { - it('succeeds without changes', async function () { - await this.mock.connect(this.defaultAdmin).rollbackDefaultAdminDelay(); - - const { newDelay, schedule } = await this.mock.pendingDefaultAdminDelay(); - expect(newDelay).to.equal(0); - expect(schedule).to.equal(0); - }); - }); - }); -} - -module.exports = { - DEFAULT_ADMIN_ROLE, - shouldBehaveLikeAccessControl, - shouldBehaveLikeAccessControlEnumerable, - shouldBehaveLikeAccessControlDefaultAdminRules, -}; diff --git a/lib_openzeppelin_contracts/test/access/AccessControl.test.js b/lib_openzeppelin_contracts/test/access/AccessControl.test.js deleted file mode 100644 index 5c70cdc..0000000 --- a/lib_openzeppelin_contracts/test/access/AccessControl.test.js +++ /dev/null @@ -1,19 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { DEFAULT_ADMIN_ROLE, shouldBehaveLikeAccessControl } = require('./AccessControl.behavior'); - -async function fixture() { - const [defaultAdmin, ...accounts] = await ethers.getSigners(); - const mock = await ethers.deployContract('$AccessControl'); - await mock.$_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); - return { mock, defaultAdmin, accounts }; -} - -describe('AccessControl', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeAccessControl(); -}); diff --git a/lib_openzeppelin_contracts/test/access/Ownable.test.js b/lib_openzeppelin_contracts/test/access/Ownable.test.js deleted file mode 100644 index 2d9b561..0000000 --- a/lib_openzeppelin_contracts/test/access/Ownable.test.js +++ /dev/null @@ -1,79 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [owner, other] = await ethers.getSigners(); - const ownable = await ethers.deployContract('$Ownable', [owner]); - return { owner, other, ownable }; -} - -describe('Ownable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('emits ownership transfer events during construction', async function () { - await expect(this.ownable.deploymentTransaction()) - .to.emit(this.ownable, 'OwnershipTransferred') - .withArgs(ethers.ZeroAddress, this.owner); - }); - - it('rejects zero address for initialOwner', async function () { - await expect(ethers.deployContract('$Ownable', [ethers.ZeroAddress])) - .to.be.revertedWithCustomError({ interface: this.ownable.interface }, 'OwnableInvalidOwner') - .withArgs(ethers.ZeroAddress); - }); - - it('has an owner', async function () { - expect(await this.ownable.owner()).to.equal(this.owner); - }); - - describe('transfer ownership', function () { - it('changes owner after transfer', async function () { - await expect(this.ownable.connect(this.owner).transferOwnership(this.other)) - .to.emit(this.ownable, 'OwnershipTransferred') - .withArgs(this.owner, this.other); - - expect(await this.ownable.owner()).to.equal(this.other); - }); - - it('prevents non-owners from transferring', async function () { - await expect(this.ownable.connect(this.other).transferOwnership(this.other)) - .to.be.revertedWithCustomError(this.ownable, 'OwnableUnauthorizedAccount') - .withArgs(this.other); - }); - - it('guards ownership against stuck state', async function () { - await expect(this.ownable.connect(this.owner).transferOwnership(ethers.ZeroAddress)) - .to.be.revertedWithCustomError(this.ownable, 'OwnableInvalidOwner') - .withArgs(ethers.ZeroAddress); - }); - }); - - describe('renounce ownership', function () { - it('loses ownership after renouncement', async function () { - await expect(this.ownable.connect(this.owner).renounceOwnership()) - .to.emit(this.ownable, 'OwnershipTransferred') - .withArgs(this.owner, ethers.ZeroAddress); - - expect(await this.ownable.owner()).to.equal(ethers.ZeroAddress); - }); - - it('prevents non-owners from renouncement', async function () { - await expect(this.ownable.connect(this.other).renounceOwnership()) - .to.be.revertedWithCustomError(this.ownable, 'OwnableUnauthorizedAccount') - .withArgs(this.other); - }); - - it('allows to recover access using the internal _transferOwnership', async function () { - await this.ownable.connect(this.owner).renounceOwnership(); - - await expect(this.ownable.$_transferOwnership(this.other)) - .to.emit(this.ownable, 'OwnershipTransferred') - .withArgs(ethers.ZeroAddress, this.other); - - expect(await this.ownable.owner()).to.equal(this.other); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/access/Ownable2Step.test.js b/lib_openzeppelin_contracts/test/access/Ownable2Step.test.js deleted file mode 100644 index 4c49e21..0000000 --- a/lib_openzeppelin_contracts/test/access/Ownable2Step.test.js +++ /dev/null @@ -1,85 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [owner, accountA, accountB] = await ethers.getSigners(); - const ownable2Step = await ethers.deployContract('$Ownable2Step', [owner]); - return { - ownable2Step, - owner, - accountA, - accountB, - }; -} - -describe('Ownable2Step', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('transfer ownership', function () { - it('starting a transfer does not change owner', async function () { - await expect(this.ownable2Step.connect(this.owner).transferOwnership(this.accountA)) - .to.emit(this.ownable2Step, 'OwnershipTransferStarted') - .withArgs(this.owner, this.accountA); - - expect(await this.ownable2Step.owner()).to.equal(this.owner); - expect(await this.ownable2Step.pendingOwner()).to.equal(this.accountA); - }); - - it('changes owner after transfer', async function () { - await this.ownable2Step.connect(this.owner).transferOwnership(this.accountA); - - await expect(this.ownable2Step.connect(this.accountA).acceptOwnership()) - .to.emit(this.ownable2Step, 'OwnershipTransferred') - .withArgs(this.owner, this.accountA); - - expect(await this.ownable2Step.owner()).to.equal(this.accountA); - expect(await this.ownable2Step.pendingOwner()).to.equal(ethers.ZeroAddress); - }); - - it('guards transfer against invalid user', async function () { - await this.ownable2Step.connect(this.owner).transferOwnership(this.accountA); - - await expect(this.ownable2Step.connect(this.accountB).acceptOwnership()) - .to.be.revertedWithCustomError(this.ownable2Step, 'OwnableUnauthorizedAccount') - .withArgs(this.accountB); - }); - }); - - describe('renouncing ownership', function () { - it('changes owner after renouncing ownership', async function () { - await expect(this.ownable2Step.connect(this.owner).renounceOwnership()) - .to.emit(this.ownable2Step, 'OwnershipTransferred') - .withArgs(this.owner, ethers.ZeroAddress); - - // If renounceOwnership is removed from parent an alternative is needed ... - // without it is difficult to cleanly renounce with the two step process - // see: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3620#discussion_r957930388 - expect(await this.ownable2Step.owner()).to.equal(ethers.ZeroAddress); - }); - - it('pending owner resets after renouncing ownership', async function () { - await this.ownable2Step.connect(this.owner).transferOwnership(this.accountA); - expect(await this.ownable2Step.pendingOwner()).to.equal(this.accountA); - - await this.ownable2Step.connect(this.owner).renounceOwnership(); - expect(await this.ownable2Step.pendingOwner()).to.equal(ethers.ZeroAddress); - - await expect(this.ownable2Step.connect(this.accountA).acceptOwnership()) - .to.be.revertedWithCustomError(this.ownable2Step, 'OwnableUnauthorizedAccount') - .withArgs(this.accountA); - }); - - it('allows to recover access using the internal _transferOwnership', async function () { - await this.ownable2Step.connect(this.owner).renounceOwnership(); - - await expect(this.ownable2Step.$_transferOwnership(this.accountA)) - .to.emit(this.ownable2Step, 'OwnershipTransferred') - .withArgs(ethers.ZeroAddress, this.accountA); - - expect(await this.ownable2Step.owner()).to.equal(this.accountA); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/access/extensions/AccessControlDefaultAdminRules.test.js b/lib_openzeppelin_contracts/test/access/extensions/AccessControlDefaultAdminRules.test.js deleted file mode 100644 index 48036fd..0000000 --- a/lib_openzeppelin_contracts/test/access/extensions/AccessControlDefaultAdminRules.test.js +++ /dev/null @@ -1,32 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const time = require('../../helpers/time'); - -const { - shouldBehaveLikeAccessControl, - shouldBehaveLikeAccessControlDefaultAdminRules, -} = require('../AccessControl.behavior'); - -async function fixture() { - const delay = time.duration.hours(10); - const [defaultAdmin, ...accounts] = await ethers.getSigners(); - const mock = await ethers.deployContract('$AccessControlDefaultAdminRules', [delay, defaultAdmin]); - return { mock, defaultAdmin, delay, accounts }; -} - -describe('AccessControlDefaultAdminRules', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('initial admin not zero', async function () { - await expect(ethers.deployContract('$AccessControlDefaultAdminRules', [this.delay, ethers.ZeroAddress])) - .to.be.revertedWithCustomError(this.mock, 'AccessControlInvalidDefaultAdmin') - .withArgs(ethers.ZeroAddress); - }); - - shouldBehaveLikeAccessControl(); - shouldBehaveLikeAccessControlDefaultAdminRules(); -}); diff --git a/lib_openzeppelin_contracts/test/access/extensions/AccessControlEnumerable.test.js b/lib_openzeppelin_contracts/test/access/extensions/AccessControlEnumerable.test.js deleted file mode 100644 index ea1a8c4..0000000 --- a/lib_openzeppelin_contracts/test/access/extensions/AccessControlEnumerable.test.js +++ /dev/null @@ -1,24 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { - DEFAULT_ADMIN_ROLE, - shouldBehaveLikeAccessControl, - shouldBehaveLikeAccessControlEnumerable, -} = require('../AccessControl.behavior'); - -async function fixture() { - const [defaultAdmin, ...accounts] = await ethers.getSigners(); - const mock = await ethers.deployContract('$AccessControlEnumerable'); - await mock.$_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); - return { mock, defaultAdmin, accounts }; -} - -describe('AccessControlEnumerable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeAccessControl(); - shouldBehaveLikeAccessControlEnumerable(); -}); diff --git a/lib_openzeppelin_contracts/test/access/manager/AccessManaged.test.js b/lib_openzeppelin_contracts/test/access/manager/AccessManaged.test.js deleted file mode 100644 index d666b5e..0000000 --- a/lib_openzeppelin_contracts/test/access/manager/AccessManaged.test.js +++ /dev/null @@ -1,146 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { impersonate } = require('../../helpers/account'); -const time = require('../../helpers/time'); - -async function fixture() { - const [admin, roleMember, other] = await ethers.getSigners(); - - const authority = await ethers.deployContract('$AccessManager', [admin]); - const managed = await ethers.deployContract('$AccessManagedTarget', [authority]); - - const anotherAuthority = await ethers.deployContract('$AccessManager', [admin]); - const authorityObserveIsConsuming = await ethers.deployContract('$AuthorityObserveIsConsuming'); - - await impersonate(authority.target); - const authorityAsSigner = await ethers.getSigner(authority.target); - - return { - roleMember, - other, - authorityAsSigner, - authority, - managed, - authorityObserveIsConsuming, - anotherAuthority, - }; -} - -describe('AccessManaged', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('sets authority and emits AuthorityUpdated event during construction', async function () { - await expect(this.managed.deploymentTransaction()) - .to.emit(this.managed, 'AuthorityUpdated') - .withArgs(this.authority); - }); - - describe('restricted modifier', function () { - beforeEach(async function () { - this.selector = this.managed.fnRestricted.getFragment().selector; - this.role = 42n; - await this.authority.$_setTargetFunctionRole(this.managed, this.selector, this.role); - await this.authority.$_grantRole(this.role, this.roleMember, 0, 0); - }); - - it('succeeds when role is granted without execution delay', async function () { - await this.managed.connect(this.roleMember)[this.selector](); - }); - - it('reverts when role is not granted', async function () { - await expect(this.managed.connect(this.other)[this.selector]()) - .to.be.revertedWithCustomError(this.managed, 'AccessManagedUnauthorized') - .withArgs(this.other); - }); - - it('panics in short calldata', async function () { - // We avoid adding the `restricted` modifier to the fallback function because other tests may depend on it - // being accessible without restrictions. We check for the internal `_checkCanCall` instead. - await expect(this.managed.$_checkCanCall(this.roleMember, '0x1234')).to.be.reverted; - }); - - describe('when role is granted with execution delay', function () { - beforeEach(async function () { - const executionDelay = 911n; - await this.authority.$_grantRole(this.role, this.roleMember, 0, executionDelay); - }); - - it('reverts if the operation is not scheduled', async function () { - const fn = this.managed.interface.getFunction(this.selector); - const calldata = this.managed.interface.encodeFunctionData(fn, []); - const opId = await this.authority.hashOperation(this.roleMember, this.managed, calldata); - - await expect(this.managed.connect(this.roleMember)[this.selector]()) - .to.be.revertedWithCustomError(this.authority, 'AccessManagerNotScheduled') - .withArgs(opId); - }); - - it('succeeds if the operation is scheduled', async function () { - // Arguments - const delay = time.duration.hours(12); - const fn = this.managed.interface.getFunction(this.selector); - const calldata = this.managed.interface.encodeFunctionData(fn, []); - - // Schedule - const scheduledAt = (await time.clock.timestamp()) + 1n; - const when = scheduledAt + delay; - await time.increaseTo.timestamp(scheduledAt, false); - await this.authority.connect(this.roleMember).schedule(this.managed, calldata, when); - - // Set execution date - await time.increaseTo.timestamp(when, false); - - // Shouldn't revert - await this.managed.connect(this.roleMember)[this.selector](); - }); - }); - }); - - describe('setAuthority', function () { - it('reverts if the caller is not the authority', async function () { - await expect(this.managed.connect(this.other).setAuthority(this.other)) - .to.be.revertedWithCustomError(this.managed, 'AccessManagedUnauthorized') - .withArgs(this.other); - }); - - it('reverts if the new authority is not a valid authority', async function () { - await expect(this.managed.connect(this.authorityAsSigner).setAuthority(this.other)) - .to.be.revertedWithCustomError(this.managed, 'AccessManagedInvalidAuthority') - .withArgs(this.other); - }); - - it('sets authority and emits AuthorityUpdated event', async function () { - await expect(this.managed.connect(this.authorityAsSigner).setAuthority(this.anotherAuthority)) - .to.emit(this.managed, 'AuthorityUpdated') - .withArgs(this.anotherAuthority); - - expect(await this.managed.authority()).to.equal(this.anotherAuthority); - }); - }); - - describe('isConsumingScheduledOp', function () { - beforeEach(async function () { - await this.managed.connect(this.authorityAsSigner).setAuthority(this.authorityObserveIsConsuming); - }); - - it('returns bytes4(0) when not consuming operation', async function () { - expect(await this.managed.isConsumingScheduledOp()).to.equal('0x00000000'); - }); - - it('returns isConsumingScheduledOp selector when consuming operation', async function () { - const isConsumingScheduledOp = this.managed.interface.getFunction('isConsumingScheduledOp()'); - const fnRestricted = this.managed.fnRestricted.getFragment(); - await expect(this.managed.connect(this.other).fnRestricted()) - .to.emit(this.authorityObserveIsConsuming, 'ConsumeScheduledOpCalled') - .withArgs( - this.other, - this.managed.interface.encodeFunctionData(fnRestricted, []), - isConsumingScheduledOp.selector, - ); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/access/manager/AccessManager.behavior.js b/lib_openzeppelin_contracts/test/access/manager/AccessManager.behavior.js deleted file mode 100644 index c9e236e..0000000 --- a/lib_openzeppelin_contracts/test/access/manager/AccessManager.behavior.js +++ /dev/null @@ -1,201 +0,0 @@ -const { expect } = require('chai'); - -const { - LIKE_COMMON_IS_EXECUTING, - LIKE_COMMON_GET_ACCESS, - LIKE_COMMON_SCHEDULABLE, - testAsSchedulableOperation, - testAsRestrictedOperation, - testAsDelayedOperation, - testAsCanCall, - testAsHasRole, -} = require('./AccessManager.predicate'); - -// ============ ADMIN OPERATION ============ - -/** - * @requires this.{manager,roles,calldata,role} - */ -function shouldBehaveLikeDelayedAdminOperation() { - const getAccessPath = LIKE_COMMON_GET_ACCESS; - testAsDelayedOperation.mineDelay = true; - getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = - testAsDelayedOperation; - getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = function () { - beforeEach('set execution delay', async function () { - this.scheduleIn = this.executionDelay; // For testAsDelayedOperation - }); - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }; - - beforeEach('set target as manager', function () { - this.target = this.manager; - }); - - testAsRestrictedOperation({ - callerIsTheManager: LIKE_COMMON_IS_EXECUTING, - callerIsNotTheManager() { - testAsHasRole({ - publicRoleIsRequired() { - it('reverts as AccessManagerUnauthorizedAccount', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.target, 'AccessManagerUnauthorizedAccount') - .withArgs( - this.caller, - this.roles.ADMIN.id, // Although PUBLIC is required, target function role doesn't apply to admin ops - ); - }); - }, - specificRoleIsRequired: getAccessPath, - }); - }, - }); -} - -/** - * @requires this.{manager,roles,calldata,role} - */ -function shouldBehaveLikeNotDelayedAdminOperation() { - const getAccessPath = LIKE_COMMON_GET_ACCESS; - - function testScheduleOperation(mineDelay) { - return function self() { - self.mineDelay = mineDelay; - beforeEach('set execution delay', async function () { - this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation - }); - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }; - } - - getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = - testScheduleOperation(true); - getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = testScheduleOperation(false); - - beforeEach('set target as manager', function () { - this.target = this.manager; - }); - - testAsRestrictedOperation({ - callerIsTheManager: LIKE_COMMON_IS_EXECUTING, - callerIsNotTheManager() { - testAsHasRole({ - publicRoleIsRequired() { - it('reverts as AccessManagerUnauthorizedAccount', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.target, 'AccessManagerUnauthorizedAccount') - .withArgs( - this.caller, - this.roles.ADMIN.id, // Although PUBLIC_ROLE is required, admin ops are not subject to target function roles - ); - }); - }, - specificRoleIsRequired: getAccessPath, - }); - }, - }); -} - -/** - * @requires this.{manager,roles,calldata,role} - */ -function shouldBehaveLikeRoleAdminOperation(roleAdmin) { - const getAccessPath = LIKE_COMMON_GET_ACCESS; - - function afterGrantDelay() { - afterGrantDelay.mineDelay = true; - beforeEach('set execution delay', async function () { - this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation - }); - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - } - - getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = afterGrantDelay; - getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = afterGrantDelay; - - beforeEach('set target as manager', function () { - this.target = this.manager; - }); - - testAsRestrictedOperation({ - callerIsTheManager: LIKE_COMMON_IS_EXECUTING, - callerIsNotTheManager() { - testAsHasRole({ - publicRoleIsRequired() { - it('reverts as AccessManagerUnauthorizedAccount', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.target, 'AccessManagerUnauthorizedAccount') - .withArgs(this.caller, roleAdmin); - }); - }, - specificRoleIsRequired: getAccessPath, - }); - }, - }); -} - -// ============ RESTRICTED OPERATION ============ - -/** - * @requires this.{manager,roles,calldata,role} - */ -function shouldBehaveLikeAManagedRestrictedOperation() { - function revertUnauthorized() { - it('reverts as AccessManagedUnauthorized', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.target, 'AccessManagedUnauthorized') - .withArgs(this.caller); - }); - } - - const getAccessPath = LIKE_COMMON_GET_ACCESS; - - getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.beforeGrantDelay = - revertUnauthorized; - getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasNoExecutionDelay.beforeGrantDelay = - revertUnauthorized; - getAccessPath.requiredRoleIsNotGranted = revertUnauthorized; - - function testScheduleOperation(mineDelay) { - return function self() { - self.mineDelay = mineDelay; - beforeEach('sets execution delay', async function () { - this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation - }); - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }; - } - - getAccessPath.requiredRoleIsGranted.roleGrantingIsDelayed.callerHasAnExecutionDelay.afterGrantDelay = - testScheduleOperation(true); - getAccessPath.requiredRoleIsGranted.roleGrantingIsNotDelayed.callerHasAnExecutionDelay = testScheduleOperation(false); - - const isExecutingPath = LIKE_COMMON_IS_EXECUTING; - isExecutingPath.notExecuting = revertUnauthorized; - - testAsCanCall({ - closed: revertUnauthorized, - open: { - callerIsTheManager: isExecutingPath, - callerIsNotTheManager: { - publicRoleIsRequired() { - it('succeeds called directly', async function () { - await this.caller.sendTransaction({ to: this.target, data: this.calldata }); - }); - - it('succeeds via execute', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - specificRoleIsRequired: getAccessPath, - }, - }, - }); -} - -module.exports = { - shouldBehaveLikeDelayedAdminOperation, - shouldBehaveLikeNotDelayedAdminOperation, - shouldBehaveLikeRoleAdminOperation, - shouldBehaveLikeAManagedRestrictedOperation, -}; diff --git a/lib_openzeppelin_contracts/test/access/manager/AccessManager.predicate.js b/lib_openzeppelin_contracts/test/access/manager/AccessManager.predicate.js deleted file mode 100644 index 8b4c5f4..0000000 --- a/lib_openzeppelin_contracts/test/access/manager/AccessManager.predicate.js +++ /dev/null @@ -1,456 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { setStorageAt } = require('@nomicfoundation/hardhat-network-helpers'); - -const { EXECUTION_ID_STORAGE_SLOT, EXPIRATION, prepareOperation } = require('../../helpers/access-manager'); -const { impersonate } = require('../../helpers/account'); -const time = require('../../helpers/time'); - -// ============ COMMON PREDICATES ============ - -const LIKE_COMMON_IS_EXECUTING = { - executing() { - it('succeeds', async function () { - await this.caller.sendTransaction({ to: this.target, data: this.calldata }); - }); - }, - notExecuting() { - it('reverts as AccessManagerUnauthorizedAccount', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount') - .withArgs(this.caller, this.role.id); - }); - }, -}; - -const LIKE_COMMON_GET_ACCESS = { - requiredRoleIsGranted: { - roleGrantingIsDelayed: { - callerHasAnExecutionDelay: { - beforeGrantDelay() { - it('reverts as AccessManagerUnauthorizedAccount', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount') - .withArgs(this.caller, this.role.id); - }); - }, - afterGrantDelay: undefined, // Diverges if there's an operation delay or not - }, - callerHasNoExecutionDelay: { - beforeGrantDelay() { - it('reverts as AccessManagerUnauthorizedAccount', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount') - .withArgs(this.caller, this.role.id); - }); - }, - afterGrantDelay() { - it('succeeds called directly', async function () { - await this.caller.sendTransaction({ to: this.target, data: this.calldata }); - }); - - it('succeeds via execute', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - }, - }, - roleGrantingIsNotDelayed: { - callerHasAnExecutionDelay: undefined, // Diverges if there's an operation to schedule or not - callerHasNoExecutionDelay() { - it('succeeds called directly', async function () { - await this.caller.sendTransaction({ to: this.target, data: this.calldata }); - }); - - it('succeeds via execute', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - }, - }, - requiredRoleIsNotGranted() { - it('reverts as AccessManagerUnauthorizedAccount', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedAccount') - .withArgs(this.caller, this.role.id); - }); - }, -}; - -const LIKE_COMMON_SCHEDULABLE = { - scheduled: { - before() { - it('reverts as AccessManagerNotReady', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotReady') - .withArgs(this.operationId); - }); - }, - after() { - it('succeeds called directly', async function () { - await this.caller.sendTransaction({ to: this.target, data: this.calldata }); - }); - - it('succeeds via execute', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - expired() { - it('reverts as AccessManagerExpired', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerExpired') - .withArgs(this.operationId); - }); - }, - }, - notScheduled() { - it('reverts as AccessManagerNotScheduled', async function () { - await expect(this.caller.sendTransaction({ to: this.target, data: this.calldata })) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotScheduled') - .withArgs(this.operationId); - }); - }, -}; - -// ============ MODE ============ - -/** - * @requires this.{manager,target} - */ -function testAsClosable({ closed, open }) { - describe('when the manager is closed', function () { - beforeEach('close', async function () { - await this.manager.$_setTargetClosed(this.target, true); - }); - - closed(); - }); - - describe('when the manager is open', function () { - beforeEach('open', async function () { - await this.manager.$_setTargetClosed(this.target, false); - }); - - open(); - }); -} - -// ============ DELAY ============ - -/** - * @requires this.{delay} - */ -function testAsDelay(type, { before, after }) { - beforeEach('define timestamp when delay takes effect', async function () { - const timestamp = await time.clock.timestamp(); - this.delayEffect = timestamp + this.delay; - }); - - describe(`when ${type} delay has not taken effect yet`, function () { - beforeEach(`set next block timestamp before ${type} takes effect`, async function () { - await time.increaseTo.timestamp(this.delayEffect - 1n, !!before.mineDelay); - }); - - before(); - }); - - describe(`when ${type} delay has taken effect`, function () { - beforeEach(`set next block timestamp when ${type} takes effect`, async function () { - await time.increaseTo.timestamp(this.delayEffect, !!after.mineDelay); - }); - - after(); - }); -} - -// ============ OPERATION ============ - -/** - * @requires this.{manager,scheduleIn,caller,target,calldata} - */ -function testAsSchedulableOperation({ scheduled: { before, after, expired }, notScheduled }) { - describe('when operation is scheduled', function () { - beforeEach('schedule operation', async function () { - if (this.caller.target) { - await impersonate(this.caller.target); - this.caller = await ethers.getSigner(this.caller.target); - } - const { operationId, schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.scheduleIn, - }); - await schedule(); - this.operationId = operationId; - }); - - describe('when operation is not ready for execution', function () { - beforeEach('set next block time before operation is ready', async function () { - this.scheduledAt = await time.clock.timestamp(); - const schedule = await this.manager.getSchedule(this.operationId); - await time.increaseTo.timestamp(schedule - 1n, !!before.mineDelay); - }); - - before(); - }); - - describe('when operation is ready for execution', function () { - beforeEach('set next block time when operation is ready for execution', async function () { - this.scheduledAt = await time.clock.timestamp(); - const schedule = await this.manager.getSchedule(this.operationId); - await time.increaseTo.timestamp(schedule, !!after.mineDelay); - }); - - after(); - }); - - describe('when operation has expired', function () { - beforeEach('set next block time when operation expired', async function () { - this.scheduledAt = await time.clock.timestamp(); - const schedule = await this.manager.getSchedule(this.operationId); - await time.increaseTo.timestamp(schedule + EXPIRATION, !!expired.mineDelay); - }); - - expired(); - }); - }); - - describe('when operation is not scheduled', function () { - beforeEach('set expected operationId', async function () { - this.operationId = await this.manager.hashOperation(this.caller, this.target, this.calldata); - - // Assert operation is not scheduled - expect(await this.manager.getSchedule(this.operationId)).to.equal(0n); - }); - - notScheduled(); - }); -} - -/** - * @requires this.{manager,roles,target,calldata} - */ -function testAsRestrictedOperation({ callerIsTheManager: { executing, notExecuting }, callerIsNotTheManager }) { - describe('when the call comes from the manager (msg.sender == manager)', function () { - beforeEach('define caller as manager', async function () { - this.caller = this.manager; - if (this.caller.target) { - await impersonate(this.caller.target); - this.caller = await ethers.getSigner(this.caller.target); - } - }); - - describe('when _executionId is in storage for target and selector', function () { - beforeEach('set _executionId flag from calldata and target', async function () { - const executionId = ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode( - ['address', 'bytes4'], - [this.target.target, this.calldata.substring(0, 10)], - ), - ); - await setStorageAt(this.manager.target, EXECUTION_ID_STORAGE_SLOT, executionId); - }); - - executing(); - }); - - describe('when _executionId does not match target and selector', notExecuting); - }); - - describe('when the call does not come from the manager (msg.sender != manager)', function () { - beforeEach('define non manager caller', function () { - this.caller = this.roles.SOME.members[0]; - }); - - callerIsNotTheManager(); - }); -} - -/** - * @requires this.{manager,scheduleIn,caller,target,calldata,executionDelay} - */ -function testAsDelayedOperation() { - describe('with operation delay', function () { - describe('when operation delay is greater than execution delay', function () { - beforeEach('set operation delay', async function () { - this.operationDelay = this.executionDelay + time.duration.hours(1); - await this.manager.$_setTargetAdminDelay(this.target, this.operationDelay); - this.scheduleIn = this.operationDelay; // For testAsSchedulableOperation - }); - - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }); - - describe('when operation delay is shorter than execution delay', function () { - beforeEach('set operation delay', async function () { - this.operationDelay = this.executionDelay - time.duration.hours(1); - await this.manager.$_setTargetAdminDelay(this.target, this.operationDelay); - this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation - }); - - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }); - }); - - describe('without operation delay', function () { - beforeEach('set operation delay', async function () { - this.operationDelay = 0n; - await this.manager.$_setTargetAdminDelay(this.target, this.operationDelay); - this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation - }); - - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }); -} - -// ============ METHOD ============ - -/** - * @requires this.{manager,roles,role,target,calldata} - */ -function testAsCanCall({ - closed, - open: { - callerIsTheManager, - callerIsNotTheManager: { publicRoleIsRequired, specificRoleIsRequired }, - }, -}) { - testAsClosable({ - closed, - open() { - testAsRestrictedOperation({ - callerIsTheManager, - callerIsNotTheManager() { - testAsHasRole({ - publicRoleIsRequired, - specificRoleIsRequired, - }); - }, - }); - }, - }); -} - -/** - * @requires this.{target,calldata,roles,role} - */ -function testAsHasRole({ publicRoleIsRequired, specificRoleIsRequired }) { - describe('when the function requires the caller to be granted with the PUBLIC_ROLE', function () { - beforeEach('set target function role as PUBLIC_ROLE', async function () { - this.role = this.roles.PUBLIC; - await this.manager - .connect(this.roles.ADMIN.members[0]) - .$_setTargetFunctionRole(this.target, this.calldata.substring(0, 10), this.role.id); - }); - - publicRoleIsRequired(); - }); - - describe('when the function requires the caller to be granted with a role other than PUBLIC_ROLE', function () { - beforeEach('set target function role as PUBLIC_ROLE', async function () { - await this.manager - .connect(this.roles.ADMIN.members[0]) - .$_setTargetFunctionRole(this.target, this.calldata.substring(0, 10), this.role.id); - }); - - testAsGetAccess(specificRoleIsRequired); - }); -} - -/** - * @requires this.{manager,role,caller} - */ -function testAsGetAccess({ - requiredRoleIsGranted: { - roleGrantingIsDelayed: { - // Because both grant and execution delay are set within the same $_grantRole call - // it's not possible to create a set of tests that diverge between grant and execution delay. - // Therefore, the testAsDelay arguments are renamed for clarity: - // before => beforeGrantDelay - // after => afterGrantDelay - callerHasAnExecutionDelay: { beforeGrantDelay: case1, afterGrantDelay: case2 }, - callerHasNoExecutionDelay: { beforeGrantDelay: case3, afterGrantDelay: case4 }, - }, - roleGrantingIsNotDelayed: { callerHasAnExecutionDelay: case5, callerHasNoExecutionDelay: case6 }, - }, - requiredRoleIsNotGranted, -}) { - describe('when the required role is granted to the caller', function () { - describe('when role granting is delayed', function () { - beforeEach('define delay', function () { - this.grantDelay = time.duration.minutes(3); - this.delay = this.grantDelay; // For testAsDelay - }); - - describe('when caller has an execution delay', function () { - beforeEach('set role and delay', async function () { - this.executionDelay = time.duration.hours(10); - this.delay = this.grantDelay; - await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay); - }); - - testAsDelay('grant', { before: case1, after: case2 }); - }); - - describe('when caller has no execution delay', function () { - beforeEach('set role and delay', async function () { - this.executionDelay = 0n; - await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay); - }); - - testAsDelay('grant', { before: case3, after: case4 }); - }); - }); - - describe('when role granting is not delayed', function () { - beforeEach('define delay', function () { - this.grantDelay = 0n; - }); - - describe('when caller has an execution delay', function () { - beforeEach('set role and delay', async function () { - this.executionDelay = time.duration.hours(10); - await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay); - }); - - case5(); - }); - - describe('when caller has no execution delay', function () { - beforeEach('set role and delay', async function () { - this.executionDelay = 0n; - await this.manager.$_grantRole(this.role.id, this.caller, this.grantDelay, this.executionDelay); - }); - - case6(); - }); - }); - }); - - describe('when role is not granted', function () { - // Because this helper can be composed with other helpers, it's possible - // that role has been set already by another helper. - // Although this is highly unlikely, we check for it here to avoid false positives. - beforeEach('assert role is unset', async function () { - const { since } = await this.manager.getAccess(this.role.id, this.caller); - expect(since).to.equal(0n); - }); - - requiredRoleIsNotGranted(); - }); -} - -module.exports = { - LIKE_COMMON_IS_EXECUTING, - LIKE_COMMON_GET_ACCESS, - LIKE_COMMON_SCHEDULABLE, - testAsClosable, - testAsDelay, - testAsSchedulableOperation, - testAsRestrictedOperation, - testAsDelayedOperation, - testAsCanCall, - testAsHasRole, - testAsGetAccess, -}; diff --git a/lib_openzeppelin_contracts/test/access/manager/AccessManager.test.js b/lib_openzeppelin_contracts/test/access/manager/AccessManager.test.js deleted file mode 100644 index 07d7b5a..0000000 --- a/lib_openzeppelin_contracts/test/access/manager/AccessManager.test.js +++ /dev/null @@ -1,2432 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { impersonate } = require('../../helpers/account'); -const { MAX_UINT48 } = require('../../helpers/constants'); -const { selector } = require('../../helpers/methods'); -const time = require('../../helpers/time'); - -const { - buildBaseRoles, - formatAccess, - EXPIRATION, - MINSETBACK, - EXECUTION_ID_STORAGE_SLOT, - CONSUMING_SCHEDULE_STORAGE_SLOT, - prepareOperation, - hashOperation, -} = require('../../helpers/access-manager'); - -const { - shouldBehaveLikeDelayedAdminOperation, - shouldBehaveLikeNotDelayedAdminOperation, - shouldBehaveLikeRoleAdminOperation, - shouldBehaveLikeAManagedRestrictedOperation, -} = require('./AccessManager.behavior'); - -const { - LIKE_COMMON_SCHEDULABLE, - testAsClosable, - testAsDelay, - testAsSchedulableOperation, - testAsCanCall, - testAsHasRole, - testAsGetAccess, -} = require('./AccessManager.predicate'); - -async function fixture() { - const [admin, roleAdmin, roleGuardian, member, user, other] = await ethers.getSigners(); - - // Build roles - const roles = buildBaseRoles(); - - // Add members - roles.ADMIN.members = [admin]; - roles.SOME_ADMIN.members = [roleAdmin]; - roles.SOME_GUARDIAN.members = [roleGuardian]; - roles.SOME.members = [member]; - roles.PUBLIC.members = [admin, roleAdmin, roleGuardian, member, user, other]; - - const manager = await ethers.deployContract('$AccessManager', [admin]); - const target = await ethers.deployContract('$AccessManagedTarget', [manager]); - - for (const { id: roleId, admin, guardian, members } of Object.values(roles)) { - if (roleId === roles.PUBLIC.id) continue; // Every address belong to public and is locked - if (roleId === roles.ADMIN.id) continue; // Admin set during construction and is locked - - // Set admin role avoiding default - if (admin.id !== roles.ADMIN.id) { - await manager.$_setRoleAdmin(roleId, admin.id); - } - - // Set guardian role avoiding default - if (guardian.id !== roles.ADMIN.id) { - await manager.$_setRoleGuardian(roleId, guardian.id); - } - - // Grant role to members - for (const member of members) { - await manager.$_grantRole(roleId, member, 0, 0); - } - } - - return { - admin, - roleAdmin, - user, - other, - roles, - manager, - target, - }; -} - -// This test suite is made using the following tools: -// -// * Predicates: Functions with common conditional setups without assertions. -// * Behaviors: Functions with common assertions. -// -// The behavioral tests are built by composing predicates and are used as templates -// for testing access to restricted functions. -// -// Similarly, unit tests in this suite will use predicates to test subsets of these -// behaviors and are helped by common assertions provided for some of the predicates. -// -// The predicates can be identified by the `testAs*` prefix while the behaviors -// are prefixed with `shouldBehave*`. The common assertions for predicates are -// defined as constants. -describe('AccessManager', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('during construction', function () { - it('grants admin role to initialAdmin', async function () { - const manager = await ethers.deployContract('$AccessManager', [this.other]); - expect(await manager.hasRole(this.roles.ADMIN.id, this.other).then(formatAccess)).to.be.deep.equal([true, '0']); - }); - - it('rejects zero address for initialAdmin', async function () { - await expect(ethers.deployContract('$AccessManager', [ethers.ZeroAddress])) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerInvalidInitialAdmin') - .withArgs(ethers.ZeroAddress); - }); - - it('initializes setup roles correctly', async function () { - for (const { id: roleId, admin, guardian, members } of Object.values(this.roles)) { - expect(await this.manager.getRoleAdmin(roleId)).to.equal(admin.id); - expect(await this.manager.getRoleGuardian(roleId)).to.equal(guardian.id); - - for (const user of this.roles.PUBLIC.members) { - expect(await this.manager.hasRole(roleId, user).then(formatAccess)).to.be.deep.equal([ - members.includes(user), - '0', - ]); - } - } - }); - }); - - describe('getters', function () { - describe('#canCall', function () { - beforeEach('set calldata', function () { - this.calldata = '0x12345678'; - this.role = { id: 379204n }; - }); - - testAsCanCall({ - closed() { - it('should return false and no delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.other, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(0n); - }); - }, - open: { - callerIsTheManager: { - executing() { - it('should return true and no delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.true; - expect(delay).to.equal(0n); - }); - }, - notExecuting() { - it('should return false and no delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(0n); - }); - }, - }, - callerIsNotTheManager: { - publicRoleIsRequired() { - it('should return true and no delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.true; - expect(delay).to.equal(0n); - }); - }, - specificRoleIsRequired: { - requiredRoleIsGranted: { - roleGrantingIsDelayed: { - callerHasAnExecutionDelay: { - beforeGrantDelay: function self() { - self.mineDelay = true; - - it('should return false and no execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(0n); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - beforeEach('sets execution delay', function () { - this.scheduleIn = this.executionDelay; // For testAsSchedulableOperation - }); - - testAsSchedulableOperation({ - scheduled: { - before: function self() { - self.mineDelay = true; - - it('should return false and execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(this.executionDelay); - }); - }, - after: function self() { - self.mineDelay = true; - - it('should return false and execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(this.executionDelay); - }); - }, - expired: function self() { - self.mineDelay = true; - - it('should return false and execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(this.executionDelay); - }); - }, - }, - notScheduled() { - it('should return false and execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(this.executionDelay); - }); - }, - }); - }, - }, - callerHasNoExecutionDelay: { - beforeGrantDelay: function self() { - self.mineDelay = true; - - it('should return false and no execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(0n); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - it('should return true and no execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.true; - expect(delay).to.equal(0n); - }); - }, - }, - }, - roleGrantingIsNotDelayed: { - callerHasAnExecutionDelay() { - it('should return false and execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(this.executionDelay); - }); - }, - callerHasNoExecutionDelay() { - it('should return true and no execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.true; - expect(delay).to.equal(0n); - }); - }, - }, - }, - requiredRoleIsNotGranted() { - it('should return false and no execution delay', async function () { - const { immediate, delay } = await this.manager.canCall( - this.caller, - this.target, - this.calldata.substring(0, 10), - ); - expect(immediate).to.be.false; - expect(delay).to.equal(0n); - }); - }, - }, - }, - }, - }); - }); - - describe('#expiration', function () { - it('has a 7 days default expiration', async function () { - expect(await this.manager.expiration()).to.equal(EXPIRATION); - }); - }); - - describe('#minSetback', function () { - it('has a 5 days default minimum setback', async function () { - expect(await this.manager.minSetback()).to.equal(MINSETBACK); - }); - }); - - describe('#isTargetClosed', function () { - testAsClosable({ - closed() { - it('returns true', async function () { - expect(await this.manager.isTargetClosed(this.target)).to.be.true; - }); - }, - open() { - it('returns false', async function () { - expect(await this.manager.isTargetClosed(this.target)).to.be.false; - }); - }, - }); - }); - - describe('#getTargetFunctionRole', function () { - const methodSelector = selector('something(address,bytes)'); - - it('returns the target function role', async function () { - const roleId = 21498n; - await this.manager.$_setTargetFunctionRole(this.target, methodSelector, roleId); - - expect(await this.manager.getTargetFunctionRole(this.target, methodSelector)).to.equal(roleId); - }); - - it('returns the ADMIN role if not set', async function () { - expect(await this.manager.getTargetFunctionRole(this.target, methodSelector)).to.equal(this.roles.ADMIN.id); - }); - }); - - describe('#getTargetAdminDelay', function () { - describe('when the target admin delay is setup', function () { - beforeEach('set target admin delay', async function () { - this.oldDelay = await this.manager.getTargetAdminDelay(this.target); - this.newDelay = time.duration.days(10); - - await this.manager.$_setTargetAdminDelay(this.target, this.newDelay); - this.delay = MINSETBACK; // For testAsDelay - }); - - testAsDelay('effect', { - before: function self() { - self.mineDelay = true; - - it('returns the old target admin delay', async function () { - expect(await this.manager.getTargetAdminDelay(this.target)).to.equal(this.oldDelay); - }); - }, - after: function self() { - self.mineDelay = true; - - it('returns the new target admin delay', async function () { - expect(await this.manager.getTargetAdminDelay(this.target)).to.equal(this.newDelay); - }); - }, - }); - }); - - it('returns the 0 if not set', async function () { - expect(await this.manager.getTargetAdminDelay(this.target)).to.equal(0n); - }); - }); - - describe('#getRoleAdmin', function () { - const roleId = 5234907n; - - it('returns the role admin', async function () { - const adminId = 789433n; - - await this.manager.$_setRoleAdmin(roleId, adminId); - - expect(await this.manager.getRoleAdmin(roleId)).to.equal(adminId); - }); - - it('returns the ADMIN role if not set', async function () { - expect(await this.manager.getRoleAdmin(roleId)).to.equal(this.roles.ADMIN.id); - }); - }); - - describe('#getRoleGuardian', function () { - const roleId = 5234907n; - - it('returns the role guardian', async function () { - const guardianId = 789433n; - - await this.manager.$_setRoleGuardian(roleId, guardianId); - - expect(await this.manager.getRoleGuardian(roleId)).to.equal(guardianId); - }); - - it('returns the ADMIN role if not set', async function () { - expect(await this.manager.getRoleGuardian(roleId)).to.equal(this.roles.ADMIN.id); - }); - }); - - describe('#getRoleGrantDelay', function () { - const roleId = 9248439n; - - describe('when the grant admin delay is setup', function () { - beforeEach('set grant admin delay', async function () { - this.oldDelay = await this.manager.getRoleGrantDelay(roleId); - this.newDelay = time.duration.days(11); - - await this.manager.$_setGrantDelay(roleId, this.newDelay); - this.delay = MINSETBACK; // For testAsDelay - }); - - testAsDelay('grant', { - before: function self() { - self.mineDelay = true; - - it('returns the old role grant delay', async function () { - expect(await this.manager.getRoleGrantDelay(roleId)).to.equal(this.oldDelay); - }); - }, - after: function self() { - self.mineDelay = true; - - it('returns the new role grant delay', async function () { - expect(await this.manager.getRoleGrantDelay(roleId)).to.equal(this.newDelay); - }); - }, - }); - }); - - it('returns 0 if delay is not set', async function () { - expect(await this.manager.getTargetAdminDelay(this.target)).to.equal(0n); - }); - }); - - describe('#getAccess', function () { - beforeEach('set role', function () { - this.role = { id: 9452n }; - this.caller = this.user; - }); - - testAsGetAccess({ - requiredRoleIsGranted: { - roleGrantingIsDelayed: { - callerHasAnExecutionDelay: { - beforeGrantDelay: function self() { - self.mineDelay = true; - - it('role is not in effect and execution delay is set', async function () { - const access = await this.manager.getAccess(this.role.id, this.caller); - expect(access[0]).to.equal(this.delayEffect); // inEffectSince - expect(access[1]).to.equal(this.executionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Not in effect yet - expect(await time.clock.timestamp()).to.lt(access[0]); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - it('access has role in effect and execution delay is set', async function () { - const access = await this.manager.getAccess(this.role.id, this.caller); - - expect(access[0]).to.equal(this.delayEffect); // inEffectSince - expect(access[1]).to.equal(this.executionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await time.clock.timestamp()).to.equal(access[0]); - }); - }, - }, - callerHasNoExecutionDelay: { - beforeGrantDelay: function self() { - self.mineDelay = true; - - it('access has role not in effect without execution delay', async function () { - const access = await this.manager.getAccess(this.role.id, this.caller); - expect(access[0]).to.equal(this.delayEffect); // inEffectSince - expect(access[1]).to.equal(0n); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Not in effect yet - expect(await time.clock.timestamp()).to.lt(access[0]); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - it('role is in effect without execution delay', async function () { - const access = await this.manager.getAccess(this.role.id, this.caller); - expect(access[0]).to.equal(this.delayEffect); // inEffectSince - expect(access[1]).to.equal(0n); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await time.clock.timestamp()).to.equal(access[0]); - }); - }, - }, - }, - roleGrantingIsNotDelayed: { - callerHasAnExecutionDelay() { - it('access has role in effect and execution delay is set', async function () { - const access = await this.manager.getAccess(this.role.id, this.caller); - expect(access[0]).to.equal(await time.clock.timestamp()); // inEffectSince - expect(access[1]).to.equal(this.executionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await time.clock.timestamp()).to.equal(access[0]); - }); - }, - callerHasNoExecutionDelay() { - it('access has role in effect without execution delay', async function () { - const access = await this.manager.getAccess(this.role.id, this.caller); - expect(access[0]).to.equal(await time.clock.timestamp()); // inEffectSince - expect(access[1]).to.equal(0n); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await time.clock.timestamp()).to.equal(access[0]); - }); - }, - }, - }, - requiredRoleIsNotGranted() { - it('has empty access', async function () { - const access = await this.manager.getAccess(this.role.id, this.caller); - expect(access[0]).to.equal(0n); // inEffectSince - expect(access[1]).to.equal(0n); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - }); - }, - }); - }); - - describe('#hasRole', function () { - beforeEach('setup testAsHasRole', function () { - this.role = { id: 49832n }; - this.calldata = '0x12345678'; - this.caller = this.user; - }); - - testAsHasRole({ - publicRoleIsRequired() { - it('has PUBLIC role', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.true; - expect(executionDelay).to.equal('0'); - }); - }, - specificRoleIsRequired: { - requiredRoleIsGranted: { - roleGrantingIsDelayed: { - callerHasAnExecutionDelay: { - beforeGrantDelay: function self() { - self.mineDelay = true; - - it('does not have role but execution delay', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.false; - expect(executionDelay).to.equal(this.executionDelay); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - it('has role and execution delay', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.true; - expect(executionDelay).to.equal(this.executionDelay); - }); - }, - }, - callerHasNoExecutionDelay: { - beforeGrantDelay: function self() { - self.mineDelay = true; - - it('does not have role nor execution delay', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.false; - expect(executionDelay).to.equal('0'); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - it('has role and no execution delay', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.true; - expect(executionDelay).to.equal('0'); - }); - }, - }, - }, - roleGrantingIsNotDelayed: { - callerHasAnExecutionDelay() { - it('has role and execution delay', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.true; - expect(executionDelay).to.equal(this.executionDelay); - }); - }, - callerHasNoExecutionDelay() { - it('has role and no execution delay', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.true; - expect(executionDelay).to.equal('0'); - }); - }, - }, - }, - requiredRoleIsNotGranted() { - it('has no role and no execution delay', async function () { - const { isMember, executionDelay } = await this.manager.hasRole(this.role.id, this.caller); - expect(isMember).to.be.false; - expect(executionDelay).to.equal('0'); - }); - }, - }, - }); - }); - - describe('#getSchedule', function () { - beforeEach('set role and calldata', async function () { - const fnRestricted = this.target.fnRestricted.getFragment().selector; - this.caller = this.user; - this.role = { id: 493590n }; - await this.manager.$_setTargetFunctionRole(this.target, fnRestricted, this.role.id); - await this.manager.$_grantRole(this.role.id, this.caller, 0, 1); // nonzero execution delay - - this.calldata = this.target.interface.encodeFunctionData(fnRestricted, []); - this.scheduleIn = time.duration.days(10); // For testAsSchedulableOperation - }); - - testAsSchedulableOperation({ - scheduled: { - before: function self() { - self.mineDelay = true; - - it('returns schedule in the future', async function () { - const schedule = await this.manager.getSchedule(this.operationId); - expect(schedule).to.equal(this.scheduledAt + this.scheduleIn); - expect(schedule).to.gt(await time.clock.timestamp()); - }); - }, - after: function self() { - self.mineDelay = true; - - it('returns schedule', async function () { - const schedule = await this.manager.getSchedule(this.operationId); - expect(schedule).to.equal(this.scheduledAt + this.scheduleIn); - expect(schedule).to.equal(await time.clock.timestamp()); - }); - }, - expired: function self() { - self.mineDelay = true; - - it('returns 0', async function () { - expect(await this.manager.getSchedule(this.operationId)).to.equal(0n); - }); - }, - }, - notScheduled() { - it('defaults to 0', async function () { - expect(await this.manager.getSchedule(this.operationId)).to.equal(0n); - }); - }, - }); - }); - - describe('#getNonce', function () { - describe('when operation is scheduled', function () { - beforeEach('schedule operation', async function () { - const fnRestricted = this.target.fnRestricted.getFragment().selector; - this.caller = this.user; - this.role = { id: 4209043n }; - await this.manager.$_setTargetFunctionRole(this.target, fnRestricted, this.role.id); - await this.manager.$_grantRole(this.role.id, this.caller, 0, 1); // nonzero execution delay - - this.calldata = this.target.interface.encodeFunctionData(fnRestricted, []); - this.delay = time.duration.days(10); - - const { operationId, schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - await schedule(); - this.operationId = operationId; - }); - - it('returns nonce', async function () { - expect(await this.manager.getNonce(this.operationId)).to.equal(1n); - }); - }); - - describe('when is not scheduled', function () { - it('returns default 0', async function () { - expect(await this.manager.getNonce(ethers.id('operation'))).to.equal(0n); - }); - }); - }); - - describe('#hashOperation', function () { - it('returns an operationId', async function () { - const args = [this.user, this.other, '0x123543']; - expect(await this.manager.hashOperation(...args)).to.equal(hashOperation(...args)); - }); - }); - }); - - describe('admin operations', function () { - beforeEach('set required role', function () { - this.role = this.roles.ADMIN; - }); - - describe('subject to a delay', function () { - describe('#labelRole', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [123443, 'TEST']; - const method = this.manager.interface.getFunction('labelRole(uint64,string)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeDelayedAdminOperation(); - }); - - it('emits an event with the label', async function () { - await expect(this.manager.connect(this.admin).labelRole(this.roles.SOME.id, 'Some label')) - .to.emit(this.manager, 'RoleLabel') - .withArgs(this.roles.SOME.id, 'Some label'); - }); - - it('updates label on a second call', async function () { - await this.manager.connect(this.admin).labelRole(this.roles.SOME.id, 'Some label'); - - await expect(this.manager.connect(this.admin).labelRole(this.roles.SOME.id, 'Updated label')) - .to.emit(this.manager, 'RoleLabel') - .withArgs(this.roles.SOME.id, 'Updated label'); - }); - - it('reverts labeling PUBLIC_ROLE', async function () { - await expect(this.manager.connect(this.admin).labelRole(this.roles.PUBLIC.id, 'Some label')) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.PUBLIC.id); - }); - - it('reverts labeling ADMIN_ROLE', async function () { - await expect(this.manager.connect(this.admin).labelRole(this.roles.ADMIN.id, 'Some label')) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.ADMIN.id); - }); - }); - - describe('#setRoleAdmin', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [93445, 84532]; - const method = this.manager.interface.getFunction('setRoleAdmin(uint64,uint64)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeDelayedAdminOperation(); - }); - - it("sets any role's admin if called by an admin", async function () { - expect(await this.manager.getRoleAdmin(this.roles.SOME.id)).to.equal(this.roles.SOME_ADMIN.id); - - await expect(this.manager.connect(this.admin).setRoleAdmin(this.roles.SOME.id, this.roles.ADMIN.id)) - .to.emit(this.manager, 'RoleAdminChanged') - .withArgs(this.roles.SOME.id, this.roles.ADMIN.id); - - expect(await this.manager.getRoleAdmin(this.roles.SOME.id)).to.equal(this.roles.ADMIN.id); - }); - - it('reverts setting PUBLIC_ROLE admin', async function () { - await expect(this.manager.connect(this.admin).setRoleAdmin(this.roles.PUBLIC.id, this.roles.ADMIN.id)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.PUBLIC.id); - }); - - it('reverts setting ADMIN_ROLE admin', async function () { - await expect(this.manager.connect(this.admin).setRoleAdmin(this.roles.ADMIN.id, this.roles.ADMIN.id)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.ADMIN.id); - }); - }); - - describe('#setRoleGuardian', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [93445, 84532]; - const method = this.manager.interface.getFunction('setRoleGuardian(uint64,uint64)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeDelayedAdminOperation(); - }); - - it("sets any role's guardian if called by an admin", async function () { - expect(await this.manager.getRoleGuardian(this.roles.SOME.id)).to.equal(this.roles.SOME_GUARDIAN.id); - - await expect(this.manager.connect(this.admin).setRoleGuardian(this.roles.SOME.id, this.roles.ADMIN.id)) - .to.emit(this.manager, 'RoleGuardianChanged') - .withArgs(this.roles.SOME.id, this.roles.ADMIN.id); - - expect(await this.manager.getRoleGuardian(this.roles.SOME.id)).to.equal(this.roles.ADMIN.id); - }); - - it('reverts setting PUBLIC_ROLE admin', async function () { - await expect(this.manager.connect(this.admin).setRoleGuardian(this.roles.PUBLIC.id, this.roles.ADMIN.id)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.PUBLIC.id); - }); - - it('reverts setting ADMIN_ROLE admin', async function () { - await expect(this.manager.connect(this.admin).setRoleGuardian(this.roles.ADMIN.id, this.roles.ADMIN.id)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.ADMIN.id); - }); - }); - - describe('#setGrantDelay', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [984910, time.duration.days(2)]; - const method = this.manager.interface.getFunction('setGrantDelay(uint64,uint32)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeDelayedAdminOperation(); - }); - - it('reverts setting grant delay for the PUBLIC_ROLE', async function () { - await expect(this.manager.connect(this.admin).setGrantDelay(this.roles.PUBLIC.id, 69n)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.PUBLIC.id); - }); - - describe('when increasing the delay', function () { - const oldDelay = 10n; - const newDelay = 100n; - - beforeEach('sets old delay', async function () { - this.role = this.roles.SOME; - await this.manager.$_setGrantDelay(this.role.id, oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(oldDelay); - }); - - it('increases the delay after minsetback', async function () { - const txResponse = await this.manager.connect(this.admin).setGrantDelay(this.role.id, newDelay); - const setGrantDelayAt = await time.clockFromReceipt.timestamp(txResponse); - await expect(txResponse) - .to.emit(this.manager, 'RoleGrantDelayChanged') - .withArgs(this.role.id, newDelay, setGrantDelayAt + MINSETBACK); - - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(newDelay); - }); - }); - - describe('when reducing the delay', function () { - const oldDelay = time.duration.days(10); - - beforeEach('sets old delay', async function () { - this.role = this.roles.SOME; - await this.manager.$_setGrantDelay(this.role.id, oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(oldDelay); - }); - - describe('when the delay difference is shorter than minimum setback', function () { - const newDelay = oldDelay - 1n; - - it('increases the delay after minsetback', async function () { - const txResponse = await this.manager.connect(this.admin).setGrantDelay(this.role.id, newDelay); - const setGrantDelayAt = await time.clockFromReceipt.timestamp(txResponse); - await expect(txResponse) - .to.emit(this.manager, 'RoleGrantDelayChanged') - .withArgs(this.role.id, newDelay, setGrantDelayAt + MINSETBACK); - - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(newDelay); - }); - }); - - describe('when the delay difference is longer than minimum setback', function () { - const newDelay = 1n; - - beforeEach('assert delay difference is higher than minsetback', function () { - expect(oldDelay - newDelay).to.gt(MINSETBACK); - }); - - it('increases the delay after delay difference', async function () { - const setback = oldDelay - newDelay; - - const txResponse = await this.manager.connect(this.admin).setGrantDelay(this.role.id, newDelay); - const setGrantDelayAt = await time.clockFromReceipt.timestamp(txResponse); - - await expect(txResponse) - .to.emit(this.manager, 'RoleGrantDelayChanged') - .withArgs(this.role.id, newDelay, setGrantDelayAt + setback); - - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(oldDelay); - await time.increaseBy.timestamp(setback); - expect(await this.manager.getRoleGrantDelay(this.role.id)).to.equal(newDelay); - }); - }); - }); - }); - - describe('#setTargetAdminDelay', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [this.other.address, time.duration.days(3)]; - const method = this.manager.interface.getFunction('setTargetAdminDelay(address,uint32)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeDelayedAdminOperation(); - }); - - describe('when increasing the delay', function () { - const oldDelay = time.duration.days(10); - const newDelay = time.duration.days(11); - - beforeEach('sets old delay', async function () { - await this.manager.$_setTargetAdminDelay(this.other, oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(oldDelay); - }); - - it('increases the delay after minsetback', async function () { - const txResponse = await this.manager.connect(this.admin).setTargetAdminDelay(this.other, newDelay); - const setTargetAdminDelayAt = await time.clockFromReceipt.timestamp(txResponse); - await expect(txResponse) - .to.emit(this.manager, 'TargetAdminDelayUpdated') - .withArgs(this.other, newDelay, setTargetAdminDelayAt + MINSETBACK); - - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(newDelay); - }); - }); - - describe('when reducing the delay', function () { - const oldDelay = time.duration.days(10); - - beforeEach('sets old delay', async function () { - await this.manager.$_setTargetAdminDelay(this.other, oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(oldDelay); - }); - - describe('when the delay difference is shorter than minimum setback', function () { - const newDelay = oldDelay - 1n; - - it('increases the delay after minsetback', async function () { - const txResponse = await this.manager.connect(this.admin).setTargetAdminDelay(this.other, newDelay); - const setTargetAdminDelayAt = await time.clockFromReceipt.timestamp(txResponse); - await expect(txResponse) - .to.emit(this.manager, 'TargetAdminDelayUpdated') - .withArgs(this.other, newDelay, setTargetAdminDelayAt + MINSETBACK); - - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(oldDelay); - await time.increaseBy.timestamp(MINSETBACK); - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(newDelay); - }); - }); - - describe('when the delay difference is longer than minimum setback', function () { - const newDelay = 1n; - - beforeEach('assert delay difference is higher than minsetback', function () { - expect(oldDelay - newDelay).to.gt(MINSETBACK); - }); - - it('increases the delay after delay difference', async function () { - const setback = oldDelay - newDelay; - - const txResponse = await this.manager.connect(this.admin).setTargetAdminDelay(this.other, newDelay); - const setTargetAdminDelayAt = await time.clockFromReceipt.timestamp(txResponse); - - await expect(txResponse) - .to.emit(this.manager, 'TargetAdminDelayUpdated') - .withArgs(this.other, newDelay, setTargetAdminDelayAt + setback); - - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(oldDelay); - await time.increaseBy.timestamp(setback); - expect(await this.manager.getTargetAdminDelay(this.other)).to.equal(newDelay); - }); - }); - }); - }); - }); - - describe('not subject to a delay', function () { - describe('#updateAuthority', function () { - beforeEach('create a target and a new authority', async function () { - this.newAuthority = await ethers.deployContract('$AccessManager', [this.admin]); - this.newManagedTarget = await ethers.deployContract('$AccessManagedTarget', [this.manager]); - }); - - describe('restrictions', function () { - beforeEach('set method and args', function () { - this.calldata = this.manager.interface.encodeFunctionData('updateAuthority(address,address)', [ - this.newManagedTarget.target, - this.newAuthority.target, - ]); - }); - - shouldBehaveLikeNotDelayedAdminOperation(); - }); - - it('changes the authority', async function () { - expect(await this.newManagedTarget.authority()).to.equal(this.manager); - - await expect(this.manager.connect(this.admin).updateAuthority(this.newManagedTarget, this.newAuthority)) - .to.emit(this.newManagedTarget, 'AuthorityUpdated') // Managed contract is responsible of notifying the change through an event - .withArgs(this.newAuthority); - - expect(await this.newManagedTarget.authority()).to.equal(this.newAuthority); - }); - }); - - describe('#setTargetClosed', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [this.other.address, true]; - const method = this.manager.interface.getFunction('setTargetClosed(address,bool)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeNotDelayedAdminOperation(); - }); - - it('closes and opens a target', async function () { - await expect(this.manager.connect(this.admin).setTargetClosed(this.target, true)) - .to.emit(this.manager, 'TargetClosed') - .withArgs(this.target, true); - expect(await this.manager.isTargetClosed(this.target)).to.be.true; - - await expect(this.manager.connect(this.admin).setTargetClosed(this.target, false)) - .to.emit(this.manager, 'TargetClosed') - .withArgs(this.target, false); - expect(await this.manager.isTargetClosed(this.target)).to.be.false; - }); - - it('reverts if closing the manager', async function () { - await expect(this.manager.connect(this.admin).setTargetClosed(this.manager, true)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedAccount') - .withArgs(this.manager); - }); - }); - - describe('#setTargetFunctionRole', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [this.other.address, ['0x12345678'], 443342]; - const method = this.manager.interface.getFunction('setTargetFunctionRole(address,bytes4[],uint64)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeNotDelayedAdminOperation(); - }); - - const sigs = ['someFunction()', 'someOtherFunction(uint256)', 'oneMoreFunction(address,uint8)'].map(selector); - - it('sets function roles', async function () { - for (const sig of sigs) { - expect(await this.manager.getTargetFunctionRole(this.target, sig)).to.equal(this.roles.ADMIN.id); - } - - const allowRole = await this.manager - .connect(this.admin) - .setTargetFunctionRole(this.target, sigs, this.roles.SOME.id); - - for (const sig of sigs) { - await expect(allowRole) - .to.emit(this.manager, 'TargetFunctionRoleUpdated') - .withArgs(this.target, sig, this.roles.SOME.id); - expect(await this.manager.getTargetFunctionRole(this.target, sig)).to.equal(this.roles.SOME.id); - } - - await expect( - this.manager.connect(this.admin).setTargetFunctionRole(this.target, [sigs[1]], this.roles.SOME_ADMIN.id), - ) - .to.emit(this.manager, 'TargetFunctionRoleUpdated') - .withArgs(this.target, sigs[1], this.roles.SOME_ADMIN.id); - - for (const sig of sigs) { - expect(await this.manager.getTargetFunctionRole(this.target, sig)).to.equal( - sig == sigs[1] ? this.roles.SOME_ADMIN.id : this.roles.SOME.id, - ); - } - }); - }); - - describe('role admin operations', function () { - const ANOTHER_ADMIN = 0xdeadc0de1n; - const ANOTHER_ROLE = 0xdeadc0de2n; - - beforeEach('set required role', async function () { - // Make admin a member of ANOTHER_ADMIN - await this.manager.$_grantRole(ANOTHER_ADMIN, this.admin, 0, 0); - await this.manager.$_setRoleAdmin(ANOTHER_ROLE, ANOTHER_ADMIN); - - this.role = { id: ANOTHER_ADMIN }; - await this.manager.$_grantRole(this.role.id, this.user, 0, 0); - }); - - describe('#grantRole', function () { - describe('restrictions', function () { - beforeEach('set method and args', function () { - const args = [ANOTHER_ROLE, this.other.address, 0]; - const method = this.manager.interface.getFunction('grantRole(uint64,address,uint32)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - }); - - shouldBehaveLikeRoleAdminOperation(ANOTHER_ADMIN); - }); - - it('reverts when granting PUBLIC_ROLE', async function () { - await expect(this.manager.connect(this.admin).grantRole(this.roles.PUBLIC.id, this.user, 0)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.PUBLIC.id); - }); - - describe('when the user is not a role member', function () { - describe('with grant delay', function () { - beforeEach('set grant delay and grant role', async function () { - // Delay granting - this.grantDelay = time.duration.weeks(2); - await this.manager.$_setGrantDelay(ANOTHER_ROLE, this.grantDelay); - await time.increaseBy.timestamp(MINSETBACK); - - // Grant role - this.executionDelay = time.duration.days(3); - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - - this.txResponse = await this.manager - .connect(this.admin) - .grantRole(ANOTHER_ROLE, this.user, this.executionDelay); - this.delay = this.grantDelay; // For testAsDelay - }); - - testAsDelay('grant', { - before: function self() { - self.mineDelay = true; - - it('does not grant role to the user yet', async function () { - const timestamp = await time.clockFromReceipt.timestamp(this.txResponse); - await expect(this.txResponse) - .to.emit(this.manager, 'RoleGranted') - .withArgs(ANOTHER_ROLE, this.user, this.executionDelay, timestamp + this.grantDelay, true); - - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(timestamp + this.grantDelay); // inEffectSince - expect(access[1]).to.equal(this.executionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Not in effect yet - const currentTimestamp = await time.clock.timestamp(); - expect(currentTimestamp).to.be.lt(access[0]); - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - false, - this.executionDelay.toString(), - ]); - }); - }, - after: function self() { - self.mineDelay = true; - - it('grants role to the user', async function () { - const timestamp = await time.clockFromReceipt.timestamp(this.txResponse); - await expect(this.txResponse) - .to.emit(this.manager, 'RoleGranted') - .withArgs(ANOTHER_ROLE, this.user, this.executionDelay, timestamp + this.grantDelay, true); - - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(timestamp + this.grantDelay); // inEffectSince - expect(access[1]).to.equal(this.executionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - const currentTimestamp = await time.clock.timestamp(); - expect(currentTimestamp).to.equal(access[0]); - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.executionDelay.toString(), - ]); - }); - }, - }); - }); - - describe('without grant delay', function () { - beforeEach('set granting delay', async function () { - // Delay granting - this.grantDelay = 0; - await this.manager.$_setGrantDelay(ANOTHER_ROLE, this.grantDelay); - await time.increaseBy.timestamp(MINSETBACK); - }); - - it('immediately grants the role to the user', async function () { - const executionDelay = time.duration.days(6); - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - const txResponse = await this.manager - .connect(this.admin) - .grantRole(ANOTHER_ROLE, this.user, executionDelay); - const grantedAt = await time.clockFromReceipt.timestamp(txResponse); - await expect(txResponse) - .to.emit(this.manager, 'RoleGranted') - .withArgs(ANOTHER_ROLE, this.user, executionDelay, grantedAt, true); - - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(grantedAt); // inEffectSince - expect(access[1]).to.equal(executionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - const currentTimestamp = await time.clock.timestamp(); - expect(currentTimestamp).to.equal(access[0]); - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - executionDelay.toString(), - ]); - }); - }); - }); - - describe('when the user is already a role member', function () { - beforeEach('make user role member', async function () { - this.previousExecutionDelay = time.duration.days(6); - await this.manager.$_grantRole(ANOTHER_ROLE, this.user, 0, this.previousExecutionDelay); - this.oldAccess = await this.manager.getAccess(ANOTHER_ROLE, this.user); - }); - - describe('with grant delay', function () { - beforeEach('set granting delay', async function () { - // Delay granting - const grantDelay = time.duration.weeks(2); - await this.manager.$_setGrantDelay(ANOTHER_ROLE, grantDelay); - await time.increaseBy.timestamp(MINSETBACK); - }); - - describe('when increasing the execution delay', function () { - beforeEach('set increased new execution delay', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - - this.newExecutionDelay = this.previousExecutionDelay + time.duration.days(4); - }); - - it('emits event and immediately changes the execution delay', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - const txResponse = await this.manager - .connect(this.admin) - .grantRole(ANOTHER_ROLE, this.user, this.newExecutionDelay); - const timestamp = await time.clockFromReceipt.timestamp(txResponse); - - await expect(txResponse) - .to.emit(this.manager, 'RoleGranted') - .withArgs(ANOTHER_ROLE, this.user, this.newExecutionDelay, timestamp, false); - - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(this.oldAccess[0]); // inEffectSince - expect(access[1]).to.equal(this.newExecutionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.newExecutionDelay.toString(), - ]); - }); - }); - - describe('when decreasing the execution delay', function () { - beforeEach('decrease execution delay', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - - this.newExecutionDelay = this.previousExecutionDelay - time.duration.days(4); - this.txResponse = await this.manager - .connect(this.admin) - .grantRole(ANOTHER_ROLE, this.user, this.newExecutionDelay); - this.grantTimestamp = await time.clockFromReceipt.timestamp(this.txResponse); - - this.delay = this.previousExecutionDelay - this.newExecutionDelay; // For testAsDelay - }); - - it('emits event', async function () { - await expect(this.txResponse) - .to.emit(this.manager, 'RoleGranted') - .withArgs(ANOTHER_ROLE, this.user, this.newExecutionDelay, this.grantTimestamp + this.delay, false); - }); - - testAsDelay('execution delay effect', { - before: function self() { - self.mineDelay = true; - - it('does not change the execution delay yet', async function () { - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(this.oldAccess[0]); // inEffectSince - expect(access[1]).to.equal(this.previousExecutionDelay); // currentDelay - expect(access[2]).to.equal(this.newExecutionDelay); // pendingDelay - expect(access[3]).to.equal(this.grantTimestamp + this.delay); // pendingDelayEffect - - // Not in effect yet - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - }); - }, - after: function self() { - self.mineDelay = true; - - it('changes the execution delay', async function () { - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - - expect(access[0]).to.equal(this.oldAccess[0]); // inEffectSince - expect(access[1]).to.equal(this.newExecutionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.newExecutionDelay.toString(), - ]); - }); - }, - }); - }); - }); - - describe('without grant delay', function () { - beforeEach('set granting delay', async function () { - // Delay granting - const grantDelay = 0; - await this.manager.$_setGrantDelay(ANOTHER_ROLE, grantDelay); - await time.increaseBy.timestamp(MINSETBACK); - }); - - describe('when increasing the execution delay', function () { - beforeEach('set increased new execution delay', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - - this.newExecutionDelay = this.previousExecutionDelay + time.duration.days(4); - }); - - it('emits event and immediately changes the execution delay', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - const txResponse = await this.manager - .connect(this.admin) - .grantRole(ANOTHER_ROLE, this.user, this.newExecutionDelay); - const timestamp = await time.clockFromReceipt.timestamp(txResponse); - - await expect(txResponse) - .to.emit(this.manager, 'RoleGranted') - .withArgs(ANOTHER_ROLE, this.user, this.newExecutionDelay, timestamp, false); - - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(this.oldAccess[0]); // inEffectSince - expect(access[1]).to.equal(this.newExecutionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.newExecutionDelay.toString(), - ]); - }); - }); - - describe('when decreasing the execution delay', function () { - beforeEach('decrease execution delay', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - - this.newExecutionDelay = this.previousExecutionDelay - time.duration.days(4); - this.txResponse = await this.manager - .connect(this.admin) - .grantRole(ANOTHER_ROLE, this.user, this.newExecutionDelay); - this.grantTimestamp = await time.clockFromReceipt.timestamp(this.txResponse); - - this.delay = this.previousExecutionDelay - this.newExecutionDelay; // For testAsDelay - }); - - it('emits event', async function () { - await expect(this.txResponse) - .to.emit(this.manager, 'RoleGranted') - .withArgs(ANOTHER_ROLE, this.user, this.newExecutionDelay, this.grantTimestamp + this.delay, false); - }); - - testAsDelay('execution delay effect', { - before: function self() { - self.mineDelay = true; - - it('does not change the execution delay yet', async function () { - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(this.oldAccess[0]); // inEffectSince - expect(access[1]).to.equal(this.previousExecutionDelay); // currentDelay - expect(access[2]).to.equal(this.newExecutionDelay); // pendingDelay - expect(access[3]).to.equal(this.grantTimestamp + this.delay); // pendingDelayEffect - - // Not in effect yet - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.previousExecutionDelay.toString(), - ]); - }); - }, - after: function self() { - self.mineDelay = true; - - it('changes the execution delay', async function () { - // Access is correctly stored - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - - expect(access[0]).to.equal(this.oldAccess[0]); // inEffectSince - expect(access[1]).to.equal(this.newExecutionDelay); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // pendingDelayEffect - - // Already in effect - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - this.newExecutionDelay.toString(), - ]); - }); - }, - }); - }); - }); - }); - }); - - describe('#revokeRole', function () { - describe('restrictions', function () { - beforeEach('set method and args', async function () { - const args = [ANOTHER_ROLE, this.other.address]; - const method = this.manager.interface.getFunction('revokeRole(uint64,address)'); - this.calldata = this.manager.interface.encodeFunctionData(method, args); - - // Need to be set before revoking - await this.manager.$_grantRole(...args, 0, 0); - }); - - shouldBehaveLikeRoleAdminOperation(ANOTHER_ADMIN); - }); - - describe('when role has been granted', function () { - beforeEach('grant role with grant delay', async function () { - this.grantDelay = time.duration.weeks(1); - await this.manager.$_grantRole(ANOTHER_ROLE, this.user, this.grantDelay, 0); - - this.delay = this.grantDelay; // For testAsDelay - }); - - testAsDelay('grant', { - before: function self() { - self.mineDelay = true; - - it('revokes a granted role that will take effect in the future', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - - await expect(this.manager.connect(this.admin).revokeRole(ANOTHER_ROLE, this.user)) - .to.emit(this.manager, 'RoleRevoked') - .withArgs(ANOTHER_ROLE, this.user); - - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(0n); // inRoleSince - expect(access[1]).to.equal(0n); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // effect - }); - }, - after: function self() { - self.mineDelay = true; - - it('revokes a granted role that already took effect', async function () { - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - true, - '0', - ]); - - await expect(this.manager.connect(this.admin).revokeRole(ANOTHER_ROLE, this.user)) - .to.emit(this.manager, 'RoleRevoked') - .withArgs(ANOTHER_ROLE, this.user); - - expect(await this.manager.hasRole(ANOTHER_ROLE, this.user).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - - const access = await this.manager.getAccess(ANOTHER_ROLE, this.user); - expect(access[0]).to.equal(0n); // inRoleSince - expect(access[1]).to.equal(0n); // currentDelay - expect(access[2]).to.equal(0n); // pendingDelay - expect(access[3]).to.equal(0n); // effect - }); - }, - }); - }); - - describe('when role has not been granted', function () { - it('has no effect', async function () { - expect(await this.manager.hasRole(this.roles.SOME.id, this.user).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - await expect(this.manager.connect(this.roleAdmin).revokeRole(this.roles.SOME.id, this.user)).to.not.emit( - this.manager, - 'RoleRevoked', - ); - expect(await this.manager.hasRole(this.roles.SOME.id, this.user).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - }); - }); - - it('reverts revoking PUBLIC_ROLE', async function () { - await expect(this.manager.connect(this.admin).revokeRole(this.roles.PUBLIC.id, this.user)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.PUBLIC.id); - }); - }); - }); - - describe('self role operations', function () { - describe('#renounceRole', function () { - beforeEach('grant role', async function () { - this.role = { id: 783164n }; - this.caller = this.user; - await this.manager.$_grantRole(this.role.id, this.caller, 0, 0); - }); - - it('renounces a role', async function () { - expect(await this.manager.hasRole(this.role.id, this.caller).then(formatAccess)).to.be.deep.equal([ - true, - '0', - ]); - await expect(this.manager.connect(this.caller).renounceRole(this.role.id, this.caller)) - .to.emit(this.manager, 'RoleRevoked') - .withArgs(this.role.id, this.caller); - expect(await this.manager.hasRole(this.role.id, this.caller).then(formatAccess)).to.be.deep.equal([ - false, - '0', - ]); - }); - - it('reverts if renouncing the PUBLIC_ROLE', async function () { - await expect(this.manager.connect(this.caller).renounceRole(this.roles.PUBLIC.id, this.caller)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerLockedRole') - .withArgs(this.roles.PUBLIC.id); - }); - - it('reverts if renouncing with bad caller confirmation', async function () { - await expect( - this.manager.connect(this.caller).renounceRole(this.role.id, this.other), - ).to.be.revertedWithCustomError(this.manager, 'AccessManagerBadConfirmation'); - }); - }); - }); - }); - }); - - describe('access managed target operations', function () { - describe('when calling a restricted target function', function () { - beforeEach('set required role', function () { - this.method = this.target.fnRestricted.getFragment(); - this.role = { id: 3597243n }; - this.manager.$_setTargetFunctionRole(this.target, this.method.selector, this.role.id); - }); - - describe('restrictions', function () { - beforeEach('set method and args', function () { - this.calldata = this.target.interface.encodeFunctionData(this.method, []); - this.caller = this.user; - }); - - shouldBehaveLikeAManagedRestrictedOperation(); - }); - - it('succeeds called by a role member', async function () { - await this.manager.$_grantRole(this.role.id, this.user, 0, 0); - - await expect( - this.target.connect(this.user)[this.method.selector]({ - data: this.calldata, - }), - ) - .to.emit(this.target, 'CalledRestricted') - .withArgs(this.user); - }); - }); - - describe('when calling a non-restricted target function', function () { - const method = 'fnUnrestricted()'; - - beforeEach('set required role', async function () { - this.role = { id: 879435n }; - await this.manager.$_setTargetFunctionRole( - this.target, - this.target[method].getFragment().selector, - this.role.id, - ); - }); - - it('succeeds called by anyone', async function () { - await expect( - this.target.connect(this.user)[method]({ - data: this.calldata, - }), - ) - .to.emit(this.target, 'CalledUnrestricted') - .withArgs(this.user); - }); - }); - }); - - describe('#schedule', function () { - beforeEach('set target function role', async function () { - this.method = this.target.fnRestricted.getFragment(); - this.role = { id: 498305n }; - this.caller = this.user; - - await this.manager.$_setTargetFunctionRole(this.target, this.method.selector, this.role.id); - await this.manager.$_grantRole(this.role.id, this.caller, 0, 1); // nonzero execution delay - - this.calldata = this.target.interface.encodeFunctionData(this.method, []); - this.delay = time.duration.weeks(2); - }); - - describe('restrictions', function () { - testAsCanCall({ - closed() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - const { schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - await expect(schedule()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - open: { - callerIsTheManager: { - executing() { - it.skip('is not reachable because schedule is not restrictable'); - }, - notExecuting() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - const { schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - await expect(schedule()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - }, - callerIsNotTheManager: { - publicRoleIsRequired() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - // prepareOperation is not used here because it alters the next block timestamp - await expect(this.manager.connect(this.caller).schedule(this.target, this.calldata, MAX_UINT48)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - specificRoleIsRequired: { - requiredRoleIsGranted: { - roleGrantingIsDelayed: { - callerHasAnExecutionDelay: { - beforeGrantDelay() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - // prepareOperation is not used here because it alters the next block timestamp - await expect(this.manager.connect(this.caller).schedule(this.target, this.calldata, MAX_UINT48)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - afterGrantDelay() { - it('succeeds', async function () { - // prepareOperation is not used here because it alters the next block timestamp - await this.manager.connect(this.caller).schedule(this.target, this.calldata, MAX_UINT48); - }); - }, - }, - callerHasNoExecutionDelay: { - beforeGrantDelay() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - // prepareOperation is not used here because it alters the next block timestamp - await expect(this.manager.connect(this.caller).schedule(this.target, this.calldata, MAX_UINT48)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - afterGrantDelay() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - // prepareOperation is not used here because it alters the next block timestamp - await expect(this.manager.connect(this.caller).schedule(this.target, this.calldata, MAX_UINT48)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - }, - }, - roleGrantingIsNotDelayed: { - callerHasAnExecutionDelay() { - it('succeeds', async function () { - const { schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - - await schedule(); - }); - }, - callerHasNoExecutionDelay() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - // prepareOperation is not used here because it alters the next block timestamp - await expect(this.manager.connect(this.caller).schedule(this.target, this.calldata, MAX_UINT48)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - }, - }, - requiredRoleIsNotGranted() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - const { schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - await expect(schedule()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - }, - }, - }, - }); - }); - - it('schedules an operation at the specified execution date if it is larger than caller execution delay', async function () { - const { operationId, scheduledAt, schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - - const txResponse = await schedule(); - - expect(await this.manager.getSchedule(operationId)).to.equal(scheduledAt + this.delay); - await expect(txResponse) - .to.emit(this.manager, 'OperationScheduled') - .withArgs(operationId, '1', scheduledAt + this.delay, this.caller, this.target, this.calldata); - }); - - it('schedules an operation at the minimum execution date if no specified execution date (when == 0)', async function () { - const executionDelay = await time.duration.hours(72); - await this.manager.$_grantRole(this.role.id, this.caller, 0, executionDelay); - - const txResponse = await this.manager.connect(this.caller).schedule(this.target, this.calldata, 0); - const scheduledAt = await time.clockFromReceipt.timestamp(txResponse); - - const operationId = await this.manager.hashOperation(this.caller, this.target, this.calldata); - - expect(await this.manager.getSchedule(operationId)).to.equal(scheduledAt + executionDelay); - await expect(txResponse) - .to.emit(this.manager, 'OperationScheduled') - .withArgs(operationId, '1', scheduledAt + executionDelay, this.caller, this.target, this.calldata); - }); - - it('increases the nonce of an operation scheduled more than once', async function () { - // Setup and check initial nonce - const expectedOperationId = hashOperation(this.caller, this.target, this.calldata); - expect(await this.manager.getNonce(expectedOperationId)).to.equal('0'); - - // Schedule - const op1 = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - await expect(op1.schedule()) - .to.emit(this.manager, 'OperationScheduled') - .withArgs(op1.operationId, 1n, op1.scheduledAt + this.delay, this.caller, this.target, this.calldata); - expect(expectedOperationId).to.equal(op1.operationId); - - // Consume - await time.increaseBy.timestamp(this.delay); - await this.manager.$_consumeScheduledOp(expectedOperationId); - - // Check nonce - expect(await this.manager.getNonce(expectedOperationId)).to.equal('1'); - - // Schedule again - const op2 = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - await expect(op2.schedule()) - .to.emit(this.manager, 'OperationScheduled') - .withArgs(op2.operationId, 2n, op2.scheduledAt + this.delay, this.caller, this.target, this.calldata); - expect(expectedOperationId).to.equal(op2.operationId); - - // Check final nonce - expect(await this.manager.getNonce(expectedOperationId)).to.equal('2'); - }); - - it('reverts if the specified execution date is before the current timestamp + caller execution delay', async function () { - const executionDelay = time.duration.weeks(1) + this.delay; - await this.manager.$_grantRole(this.role.id, this.caller, 0, executionDelay); - - const { schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - - await expect(schedule()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - - it('reverts if an operation is already schedule', async function () { - const op1 = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - - await op1.schedule(); - - const op2 = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.delay, - }); - - await expect(op2.schedule()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerAlreadyScheduled') - .withArgs(op1.operationId); - }); - - it('panics scheduling calldata with less than 4 bytes', async function () { - const calldata = '0x1234'; // 2 bytes - - // Managed contract - const op1 = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: calldata, - delay: this.delay, - }); - await expect(op1.schedule()).to.be.revertedWithoutReason(); - - // Manager contract - const op2 = await prepareOperation(this.manager, { - caller: this.caller, - target: this.manager, - calldata: calldata, - delay: this.delay, - }); - await expect(op2.schedule()).to.be.revertedWithoutReason(); - }); - - it('reverts scheduling an unknown operation to the manager', async function () { - const calldata = '0x12345678'; - - const { schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.manager, - calldata, - delay: this.delay, - }); - - await expect(schedule()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.manager, calldata); - }); - }); - - describe('#execute', function () { - beforeEach('set target function role', async function () { - this.method = this.target.fnRestricted.getFragment(); - this.role = { id: 9825430n }; - this.caller = this.user; - - await this.manager.$_setTargetFunctionRole(this.target, this.method.selector, this.role.id); - await this.manager.$_grantRole(this.role.id, this.caller, 0, 0); - - this.calldata = this.target.interface.encodeFunctionData(this.method, []); - }); - - describe('restrictions', function () { - testAsCanCall({ - closed() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - open: { - callerIsTheManager: { - executing() { - it('succeeds', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - notExecuting() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - }, - callerIsNotTheManager: { - publicRoleIsRequired() { - it('succeeds', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - specificRoleIsRequired: { - requiredRoleIsGranted: { - roleGrantingIsDelayed: { - callerHasAnExecutionDelay: { - beforeGrantDelay() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - beforeEach('define schedule delay', function () { - this.scheduleIn = time.duration.days(21); // For testAsSchedulableOperation - }); - - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }, - }, - callerHasNoExecutionDelay: { - beforeGrantDelay() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - afterGrantDelay: function self() { - self.mineDelay = true; - - it('succeeds', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - }, - }, - roleGrantingIsNotDelayed: { - callerHasAnExecutionDelay() { - beforeEach('define schedule delay', function () { - this.scheduleIn = time.duration.days(15); // For testAsSchedulableOperation - }); - - testAsSchedulableOperation(LIKE_COMMON_SCHEDULABLE); - }, - callerHasNoExecutionDelay() { - it('succeeds', async function () { - await this.manager.connect(this.caller).execute(this.target, this.calldata); - }); - }, - }, - }, - requiredRoleIsNotGranted() { - it('reverts as AccessManagerUnauthorizedCall', async function () { - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.caller, this.target, this.calldata.substring(0, 10)); - }); - }, - }, - }, - }, - }); - }); - - it('executes with a delay consuming the scheduled operation', async function () { - const delay = time.duration.hours(4); - await this.manager.$_grantRole(this.role.id, this.caller, 0, 1); // Execution delay is needed so the operation is consumed - - const { operationId, schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay, - }); - await schedule(); - await time.increaseBy.timestamp(delay); - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.emit(this.manager, 'OperationExecuted') - .withArgs(operationId, 1n); - - expect(await this.manager.getSchedule(operationId)).to.equal(0n); - }); - - it('executes with no delay consuming a scheduled operation', async function () { - const delay = time.duration.hours(4); - - // give caller an execution delay - await this.manager.$_grantRole(this.role.id, this.caller, 0, 1); - - const { operationId, schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay, - }); - await schedule(); - - // remove the execution delay - await this.manager.$_grantRole(this.role.id, this.caller, 0, 0); - - await time.increaseBy.timestamp(delay); - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.emit(this.manager, 'OperationExecuted') - .withArgs(operationId, 1n); - - expect(await this.manager.getSchedule(operationId)).to.equal(0n); - }); - - it('keeps the original _executionId after finishing the call', async function () { - const executionIdBefore = await ethers.provider.getStorage(this.manager, EXECUTION_ID_STORAGE_SLOT); - await this.manager.connect(this.caller).execute(this.target, this.calldata); - const executionIdAfter = await ethers.provider.getStorage(this.manager, EXECUTION_ID_STORAGE_SLOT); - expect(executionIdBefore).to.equal(executionIdAfter); - }); - - it('reverts executing twice', async function () { - const delay = time.duration.hours(2); - await this.manager.$_grantRole(this.role.id, this.caller, 0, 1); // Execution delay is needed so the operation is consumed - - const { operationId, schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay, - }); - await schedule(); - await time.increaseBy.timestamp(delay); - await this.manager.connect(this.caller).execute(this.target, this.calldata); - await expect(this.manager.connect(this.caller).execute(this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotScheduled') - .withArgs(operationId); - }); - }); - - describe('#consumeScheduledOp', function () { - beforeEach('define scheduling parameters', async function () { - const method = this.target.fnRestricted.getFragment(); - this.caller = await ethers.getSigner(this.target.target); - await impersonate(this.caller.address); - this.calldata = this.target.interface.encodeFunctionData(method, []); - this.role = { id: 9834983n }; - - await this.manager.$_setTargetFunctionRole(this.target, method.selector, this.role.id); - await this.manager.$_grantRole(this.role.id, this.caller, 0, 1); // nonzero execution delay - - this.scheduleIn = time.duration.hours(10); // For testAsSchedulableOperation - }); - - describe('when caller is not consuming scheduled operation', function () { - beforeEach('set consuming false', async function () { - await this.target.setIsConsumingScheduledOp(false, ethers.toBeHex(CONSUMING_SCHEDULE_STORAGE_SLOT, 32)); - }); - - it('reverts as AccessManagerUnauthorizedConsume', async function () { - await expect(this.manager.connect(this.caller).consumeScheduledOp(this.caller, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedConsume') - .withArgs(this.caller); - }); - }); - - describe('when caller is consuming scheduled operation', function () { - beforeEach('set consuming true', async function () { - await this.target.setIsConsumingScheduledOp(true, ethers.toBeHex(CONSUMING_SCHEDULE_STORAGE_SLOT, 32)); - }); - - testAsSchedulableOperation({ - scheduled: { - before() { - it('reverts as AccessManagerNotReady', async function () { - await expect(this.manager.connect(this.caller).consumeScheduledOp(this.caller, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotReady') - .withArgs(this.operationId); - }); - }, - after() { - it('consumes the scheduled operation and resets timepoint', async function () { - expect(await this.manager.getSchedule(this.operationId)).to.equal(this.scheduledAt + this.scheduleIn); - - await expect(this.manager.connect(this.caller).consumeScheduledOp(this.caller, this.calldata)) - .to.emit(this.manager, 'OperationExecuted') - .withArgs(this.operationId, 1n); - expect(await this.manager.getSchedule(this.operationId)).to.equal(0n); - }); - }, - expired() { - it('reverts as AccessManagerExpired', async function () { - await expect(this.manager.connect(this.caller).consumeScheduledOp(this.caller, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerExpired') - .withArgs(this.operationId); - }); - }, - }, - notScheduled() { - it('reverts as AccessManagerNotScheduled', async function () { - await expect(this.manager.connect(this.caller).consumeScheduledOp(this.caller, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotScheduled') - .withArgs(this.operationId); - }); - }, - }); - }); - }); - - describe('#cancelScheduledOp', function () { - beforeEach('setup scheduling', async function () { - this.method = this.target.fnRestricted.getFragment(); - this.caller = this.roles.SOME.members[0]; - await this.manager.$_setTargetFunctionRole(this.target, this.method.selector, this.roles.SOME.id); - await this.manager.$_grantRole(this.roles.SOME.id, this.caller, 0, 1); // nonzero execution delay - - this.calldata = this.target.interface.encodeFunctionData(this.method, []); - this.scheduleIn = time.duration.days(10); // For testAsSchedulableOperation - }); - - testAsSchedulableOperation({ - scheduled: { - before() { - describe('when caller is the scheduler', function () { - it('succeeds', async function () { - await this.manager.connect(this.caller).cancel(this.caller, this.target, this.calldata); - }); - }); - - describe('when caller is an admin', function () { - it('succeeds', async function () { - await this.manager.connect(this.roles.ADMIN.members[0]).cancel(this.caller, this.target, this.calldata); - }); - }); - - describe('when caller is the role guardian', function () { - it('succeeds', async function () { - await this.manager - .connect(this.roles.SOME_GUARDIAN.members[0]) - .cancel(this.caller, this.target, this.calldata); - }); - }); - - describe('when caller is any other account', function () { - it('reverts as AccessManagerUnauthorizedCancel', async function () { - await expect(this.manager.connect(this.other).cancel(this.caller, this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCancel') - .withArgs(this.other, this.caller, this.target, this.method.selector); - }); - }); - }, - after() { - it('succeeds', async function () { - await this.manager.connect(this.caller).cancel(this.caller, this.target, this.calldata); - }); - }, - expired() { - it('succeeds', async function () { - await this.manager.connect(this.caller).cancel(this.caller, this.target, this.calldata); - }); - }, - }, - notScheduled() { - it('reverts as AccessManagerNotScheduled', async function () { - await expect(this.manager.cancel(this.caller, this.target, this.calldata)) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotScheduled') - .withArgs(this.operationId); - }); - }, - }); - - it('cancels an operation and resets schedule', async function () { - const { operationId, schedule } = await prepareOperation(this.manager, { - caller: this.caller, - target: this.target, - calldata: this.calldata, - delay: this.scheduleIn, - }); - await schedule(); - await expect(this.manager.connect(this.caller).cancel(this.caller, this.target, this.calldata)) - .to.emit(this.manager, 'OperationCanceled') - .withArgs(operationId, 1n); - expect(await this.manager.getSchedule(operationId)).to.equal('0'); - }); - }); - - describe('with Ownable target contract', function () { - const roleId = 1n; - - beforeEach(async function () { - this.ownable = await ethers.deployContract('$Ownable', [this.manager]); - - // add user to role - await this.manager.$_grantRole(roleId, this.user, 0, 0); - }); - - it('initial state', async function () { - expect(await this.ownable.owner()).to.equal(this.manager); - }); - - describe('Contract is closed', function () { - beforeEach(async function () { - await this.manager.$_setTargetClosed(this.ownable, true); - }); - - it('directly call: reverts', async function () { - await expect(this.ownable.connect(this.user).$_checkOwner()) - .to.be.revertedWithCustomError(this.ownable, 'OwnableUnauthorizedAccount') - .withArgs(this.user); - }); - - it('relayed call (with role): reverts', async function () { - await expect( - this.manager.connect(this.user).execute(this.ownable, this.ownable.$_checkOwner.getFragment().selector), - ) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.user, this.ownable, this.ownable.$_checkOwner.getFragment().selector); - }); - - it('relayed call (without role): reverts', async function () { - await expect( - this.manager.connect(this.other).execute(this.ownable, this.ownable.$_checkOwner.getFragment().selector), - ) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.other, this.ownable, this.ownable.$_checkOwner.getFragment().selector); - }); - }); - - describe('Contract is managed', function () { - describe('function is open to specific role', function () { - beforeEach(async function () { - await this.manager.$_setTargetFunctionRole( - this.ownable, - this.ownable.$_checkOwner.getFragment().selector, - roleId, - ); - }); - - it('directly call: reverts', async function () { - await expect(this.ownable.connect(this.user).$_checkOwner()) - .to.be.revertedWithCustomError(this.ownable, 'OwnableUnauthorizedAccount') - .withArgs(this.user); - }); - - it('relayed call (with role): success', async function () { - await this.manager.connect(this.user).execute(this.ownable, this.ownable.$_checkOwner.getFragment().selector); - }); - - it('relayed call (without role): reverts', async function () { - await expect( - this.manager.connect(this.other).execute(this.ownable, this.ownable.$_checkOwner.getFragment().selector), - ) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerUnauthorizedCall') - .withArgs(this.other, this.ownable, this.ownable.$_checkOwner.getFragment().selector); - }); - }); - - describe('function is open to public role', function () { - beforeEach(async function () { - await this.manager.$_setTargetFunctionRole( - this.ownable, - this.ownable.$_checkOwner.getFragment().selector, - this.roles.PUBLIC.id, - ); - }); - - it('directly call: reverts', async function () { - await expect(this.ownable.connect(this.user).$_checkOwner()) - .to.be.revertedWithCustomError(this.ownable, 'OwnableUnauthorizedAccount') - .withArgs(this.user); - }); - - it('relayed call (with role): success', async function () { - await this.manager.connect(this.user).execute(this.ownable, this.ownable.$_checkOwner.getFragment().selector); - }); - - it('relayed call (without role): success', async function () { - await this.manager - .connect(this.other) - .execute(this.ownable, this.ownable.$_checkOwner.getFragment().selector); - }); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/access/manager/AuthorityUtils.test.js b/lib_openzeppelin_contracts/test/access/manager/AuthorityUtils.test.js deleted file mode 100644 index 905913f..0000000 --- a/lib_openzeppelin_contracts/test/access/manager/AuthorityUtils.test.js +++ /dev/null @@ -1,102 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [user, other] = await ethers.getSigners(); - - const mock = await ethers.deployContract('$AuthorityUtils'); - const notAuthorityMock = await ethers.deployContract('NotAuthorityMock'); - const authorityNoDelayMock = await ethers.deployContract('AuthorityNoDelayMock'); - const authorityDelayMock = await ethers.deployContract('AuthorityDelayMock'); - const authorityNoResponse = await ethers.deployContract('AuthorityNoResponse'); - - return { - user, - other, - mock, - notAuthorityMock, - authorityNoDelayMock, - authorityDelayMock, - authorityNoResponse, - }; -} - -describe('AuthorityUtils', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('canCallWithDelay', function () { - describe('when authority does not have a canCall function', function () { - beforeEach(async function () { - this.authority = this.notAuthorityMock; - }); - - it('returns (immediate = 0, delay = 0)', async function () { - const { immediate, delay } = await this.mock.$canCallWithDelay( - this.authority, - this.user, - this.other, - '0x12345678', - ); - expect(immediate).to.be.false; - expect(delay).to.equal(0n); - }); - }); - - describe('when authority has no delay', function () { - beforeEach(async function () { - this.authority = this.authorityNoDelayMock; - this.immediate = true; - await this.authority._setImmediate(this.immediate); - }); - - it('returns (immediate, delay = 0)', async function () { - const { immediate, delay } = await this.mock.$canCallWithDelay( - this.authority, - this.user, - this.other, - '0x12345678', - ); - expect(immediate).to.equal(this.immediate); - expect(delay).to.equal(0n); - }); - }); - - describe('when authority replies with a delay', function () { - beforeEach(async function () { - this.authority = this.authorityDelayMock; - }); - - for (const immediate of [true, false]) { - for (const delay of [0n, 42n]) { - it(`returns (immediate=${immediate}, delay=${delay})`, async function () { - await this.authority._setImmediate(immediate); - await this.authority._setDelay(delay); - const result = await this.mock.$canCallWithDelay(this.authority, this.user, this.other, '0x12345678'); - expect(result.immediate).to.equal(immediate); - expect(result.delay).to.equal(delay); - }); - } - } - }); - - describe('when authority replies with empty data', function () { - beforeEach(async function () { - this.authority = this.authorityNoResponse; - }); - - it('returns (immediate = 0, delay = 0)', async function () { - const { immediate, delay } = await this.mock.$canCallWithDelay( - this.authority, - this.user, - this.other, - '0x12345678', - ); - expect(immediate).to.be.false; - expect(delay).to.equal(0n); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/finance/VestingWallet.behavior.js b/lib_openzeppelin_contracts/test/finance/VestingWallet.behavior.js deleted file mode 100644 index b45ffee..0000000 --- a/lib_openzeppelin_contracts/test/finance/VestingWallet.behavior.js +++ /dev/null @@ -1,87 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const time = require('../helpers/time'); - -async function envSetup(mock, beneficiary, token) { - return { - eth: { - checkRelease: async (tx, amount) => { - await expect(tx).to.changeEtherBalances([mock, beneficiary], [-amount, amount]); - }, - setupFailure: async () => { - const beneficiaryMock = await ethers.deployContract('EtherReceiverMock'); - await beneficiaryMock.setAcceptEther(false); - await mock.connect(beneficiary).transferOwnership(beneficiaryMock); - return { args: [], error: [mock, 'FailedCall'] }; - }, - releasedEvent: 'EtherReleased', - args: [], - }, - token: { - checkRelease: async (tx, amount) => { - await expect(tx).to.emit(token, 'Transfer').withArgs(mock, beneficiary, amount); - await expect(tx).to.changeTokenBalances(token, [mock, beneficiary], [-amount, amount]); - }, - setupFailure: async () => { - const pausableToken = await ethers.deployContract('$ERC20Pausable', ['Name', 'Symbol']); - await pausableToken.$_pause(); - return { - args: [ethers.Typed.address(pausableToken)], - error: [pausableToken, 'EnforcedPause'], - }; - }, - releasedEvent: 'ERC20Released', - args: [ethers.Typed.address(token)], - }, - }; -} - -function shouldBehaveLikeVesting() { - it('check vesting schedule', async function () { - for (const timestamp of this.schedule) { - await time.increaseTo.timestamp(timestamp); - const vesting = this.vestingFn(timestamp); - - expect(await this.mock.vestedAmount(...this.args, timestamp)).to.equal(vesting); - expect(await this.mock.releasable(...this.args)).to.equal(vesting); - } - }); - - it('execute vesting schedule', async function () { - let released = 0n; - { - const tx = await this.mock.release(...this.args); - await expect(tx) - .to.emit(this.mock, this.releasedEvent) - .withArgs(...this.args, 0); - - await this.checkRelease(tx, 0n); - } - - for (const timestamp of this.schedule) { - await time.increaseTo.timestamp(timestamp, false); - const vested = this.vestingFn(timestamp); - - const tx = await this.mock.release(...this.args); - await expect(tx).to.emit(this.mock, this.releasedEvent); - - await this.checkRelease(tx, vested - released); - released = vested; - } - }); - - it('should revert on transaction failure', async function () { - const { args, error } = await this.setupFailure(); - - for (const timestamp of this.schedule) { - await time.increaseTo.timestamp(timestamp); - - await expect(this.mock.release(...args)).to.be.revertedWithCustomError(...error); - } - }); -} - -module.exports = { - envSetup, - shouldBehaveLikeVesting, -}; diff --git a/lib_openzeppelin_contracts/test/finance/VestingWallet.test.js b/lib_openzeppelin_contracts/test/finance/VestingWallet.test.js deleted file mode 100644 index b89258d..0000000 --- a/lib_openzeppelin_contracts/test/finance/VestingWallet.test.js +++ /dev/null @@ -1,65 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { min } = require('../helpers/math'); -const time = require('../helpers/time'); - -const { envSetup, shouldBehaveLikeVesting } = require('./VestingWallet.behavior'); - -async function fixture() { - const amount = ethers.parseEther('100'); - const duration = time.duration.years(4); - const start = (await time.clock.timestamp()) + time.duration.hours(1); - - const [sender, beneficiary] = await ethers.getSigners(); - const mock = await ethers.deployContract('VestingWallet', [beneficiary, start, duration]); - - const token = await ethers.deployContract('$ERC20', ['Name', 'Symbol']); - await token.$_mint(mock, amount); - await sender.sendTransaction({ to: mock, value: amount }); - - const env = await envSetup(mock, beneficiary, token); - - const schedule = Array.from({ length: 64 }, (_, i) => (BigInt(i) * duration) / 60n + start); - const vestingFn = timestamp => min(amount, (amount * (timestamp - start)) / duration); - - return { mock, duration, start, beneficiary, schedule, vestingFn, env }; -} - -describe('VestingWallet', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('rejects zero address for beneficiary', async function () { - await expect(ethers.deployContract('VestingWallet', [ethers.ZeroAddress, this.start, this.duration])) - .revertedWithCustomError(this.mock, 'OwnableInvalidOwner') - .withArgs(ethers.ZeroAddress); - }); - - it('check vesting contract', async function () { - expect(await this.mock.owner()).to.equal(this.beneficiary); - expect(await this.mock.start()).to.equal(this.start); - expect(await this.mock.duration()).to.equal(this.duration); - expect(await this.mock.end()).to.equal(this.start + this.duration); - }); - - describe('vesting schedule', function () { - describe('Eth vesting', function () { - beforeEach(async function () { - Object.assign(this, this.env.eth); - }); - - shouldBehaveLikeVesting(); - }); - - describe('ERC20 vesting', function () { - beforeEach(async function () { - Object.assign(this, this.env.token); - }); - - shouldBehaveLikeVesting(); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/finance/VestingWalletCliff.test.js b/lib_openzeppelin_contracts/test/finance/VestingWalletCliff.test.js deleted file mode 100644 index 799b24c..0000000 --- a/lib_openzeppelin_contracts/test/finance/VestingWalletCliff.test.js +++ /dev/null @@ -1,70 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { min } = require('../helpers/math'); -const time = require('../helpers/time'); - -const { envSetup, shouldBehaveLikeVesting } = require('./VestingWallet.behavior'); - -async function fixture() { - const amount = ethers.parseEther('100'); - const duration = time.duration.years(4); - const start = (await time.clock.timestamp()) + time.duration.hours(1); - const cliffDuration = time.duration.years(1); - const cliff = start + cliffDuration; - - const [sender, beneficiary] = await ethers.getSigners(); - const mock = await ethers.deployContract('$VestingWalletCliff', [beneficiary, start, duration, cliffDuration]); - - const token = await ethers.deployContract('$ERC20', ['Name', 'Symbol']); - await token.$_mint(mock, amount); - await sender.sendTransaction({ to: mock, value: amount }); - - const env = await envSetup(mock, beneficiary, token); - - const schedule = Array.from({ length: 64 }, (_, i) => (BigInt(i) * duration) / 60n + start); - const vestingFn = timestamp => min(amount, timestamp < cliff ? 0n : (amount * (timestamp - start)) / duration); - - return { mock, duration, start, beneficiary, cliff, schedule, vestingFn, env }; -} - -describe('VestingWalletCliff', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('rejects a larger cliff than vesting duration', async function () { - await expect( - ethers.deployContract('$VestingWalletCliff', [this.beneficiary, this.start, this.duration, this.duration + 1n]), - ) - .revertedWithCustomError(this.mock, 'InvalidCliffDuration') - .withArgs(this.duration + 1n, this.duration); - }); - - it('check vesting contract', async function () { - expect(await this.mock.owner()).to.equal(this.beneficiary); - expect(await this.mock.start()).to.equal(this.start); - expect(await this.mock.duration()).to.equal(this.duration); - expect(await this.mock.end()).to.equal(this.start + this.duration); - expect(await this.mock.cliff()).to.equal(this.cliff); - }); - - describe('vesting schedule', function () { - describe('Eth vesting', function () { - beforeEach(async function () { - Object.assign(this, this.env.eth); - }); - - shouldBehaveLikeVesting(); - }); - - describe('ERC20 vesting', function () { - beforeEach(async function () { - Object.assign(this, this.env.token); - }); - - shouldBehaveLikeVesting(); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/governance/Governor.t.sol b/lib_openzeppelin_contracts/test/governance/Governor.t.sol deleted file mode 100644 index e906f8e..0000000 --- a/lib_openzeppelin_contracts/test/governance/Governor.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; - -contract GovernorInternalTest is Test, Governor { - constructor() Governor("") {} - - function testValidDescriptionForProposer(string memory description, address proposer, bool includeProposer) public { - if (includeProposer) { - description = string.concat(description, "#proposer=", Strings.toHexString(proposer)); - } - assertTrue(_isValidDescriptionForProposer(proposer, description)); - } - - function testInvalidDescriptionForProposer( - string memory description, - address commitProposer, - address actualProposer - ) public { - vm.assume(commitProposer != actualProposer); - description = string.concat(description, "#proposer=", Strings.toHexString(commitProposer)); - assertFalse(_isValidDescriptionForProposer(actualProposer, description)); - } - - // We don't need to truly implement implement the missing functions because we are just testing - // internal helpers. - - function clock() public pure override returns (uint48) {} - - // solhint-disable-next-line func-name-mixedcase - function CLOCK_MODE() public pure override returns (string memory) {} - - // solhint-disable-next-line func-name-mixedcase - function COUNTING_MODE() public pure virtual override returns (string memory) {} - - function votingDelay() public pure virtual override returns (uint256) {} - - function votingPeriod() public pure virtual override returns (uint256) {} - - function quorum(uint256) public pure virtual override returns (uint256) {} - - function hasVoted(uint256, address) public pure virtual override returns (bool) {} - - function _quorumReached(uint256) internal pure virtual override returns (bool) {} - - function _voteSucceeded(uint256) internal pure virtual override returns (bool) {} - - function _getVotes(address, uint256, bytes memory) internal pure virtual override returns (uint256) {} - - function _countVote(uint256, address, uint8, uint256, bytes memory) internal virtual override {} -} diff --git a/lib_openzeppelin_contracts/test/governance/Governor.test.js b/lib_openzeppelin_contracts/test/governance/Governor.test.js deleted file mode 100644 index 3e48ccf..0000000 --- a/lib_openzeppelin_contracts/test/governance/Governor.test.js +++ /dev/null @@ -1,992 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { GovernorHelper } = require('../helpers/governance'); -const { getDomain, Ballot } = require('../helpers/eip712'); -const { ProposalState, VoteType } = require('../helpers/enums'); -const time = require('../helpers/time'); - -const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior'); -const { shouldBehaveLikeERC6372 } = require('./utils/ERC6372.behavior'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, - { Token: '$ERC20VotesLegacyMock', mode: 'blocknumber' }, -]; - -const name = 'OZ-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, { Ballot }, message)); - -async function deployToken(contractName) { - try { - return await ethers.deployContract(contractName, [tokenName, tokenSymbol, tokenName, version]); - } catch (error) { - if (error.message == 'incorrect number of arguments to constructor') { - // ERC20VotesLegacyMock has a different construction that uses version='1' by default. - return ethers.deployContract(contractName, [tokenName, tokenSymbol, tokenName]); - } - throw error; - } -} - -describe('Governor', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [owner, proposer, voter1, voter2, voter3, voter4, userEOA] = await ethers.getSigners(); - const receiver = await ethers.deployContract('CallReceiverMock'); - - const token = await deployToken(Token, [tokenName, tokenSymbol, version]); - const mock = await ethers.deployContract('$GovernorMock', [ - 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: token, to: voter1, value: ethers.parseEther('10') }); - await helper.connect(owner).delegate({ token: token, to: voter2, value: ethers.parseEther('7') }); - await helper.connect(owner).delegate({ token: token, to: voter3, value: ethers.parseEther('5') }); - await helper.connect(owner).delegate({ token: token, to: voter4, value: ethers.parseEther('2') }); - - return { - owner, - proposer, - voter1, - voter2, - voter3, - voter4, - userEOA, - receiver, - token, - mock, - helper, - }; - }; - - describe(`using ${Token}`, function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - // initiate fresh proposal - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunction'), - value, - }, - ], - '', - ); - }); - - shouldSupportInterfaces(['ERC1155Receiver', 'Governor']); - shouldBehaveLikeERC6372(mode); - - 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.quorum(0)).to.equal(0n); - expect(await this.mock.COUNTING_MODE()).to.equal('support=bravo&quorum=for,abstain'); - }); - - it('nominal workflow', async function () { - // Before - expect(await this.mock.proposalProposer(this.proposal.id)).to.equal(ethers.ZeroAddress); - 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.false; - expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.false; - expect(await ethers.provider.getBalance(this.mock)).to.equal(value); - expect(await ethers.provider.getBalance(this.receiver)).to.equal(0n); - - expect(await this.mock.proposalEta(this.proposal.id)).to.equal(0n); - expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.false; - - // Run proposal - const txPropose = await this.helper.connect(this.proposer).propose(); - const timepoint = await time.clockFromReceipt[mode](txPropose); - - await expect(txPropose) - .to.emit(this.mock, 'ProposalCreated') - .withArgs( - this.proposal.id, - this.proposer, - this.proposal.targets, - this.proposal.values, - this.proposal.signatures, - this.proposal.data, - timepoint + votingDelay, - timepoint + votingDelay + votingPeriod, - this.proposal.description, - ); - - await this.helper.waitForSnapshot(); - - await expect(this.helper.connect(this.voter1).vote({ support: VoteType.For, reason: 'This is nice' })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter1, this.proposal.id, VoteType.For, ethers.parseEther('10'), 'This is nice'); - - await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter2, this.proposal.id, VoteType.For, ethers.parseEther('7'), ''); - - await expect(this.helper.connect(this.voter3).vote({ support: VoteType.Against })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter3, this.proposal.id, VoteType.Against, ethers.parseEther('5'), ''); - - await expect(this.helper.connect(this.voter4).vote({ support: VoteType.Abstain })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter4, this.proposal.id, VoteType.Abstain, ethers.parseEther('2'), ''); - - await this.helper.waitForDeadline(); - - const txExecute = await this.helper.execute(); - - await expect(txExecute).to.emit(this.mock, 'ProposalExecuted').withArgs(this.proposal.id); - - await expect(txExecute).to.emit(this.receiver, 'MockFunctionCalled'); - - // After - expect(await this.mock.proposalProposer(this.proposal.id)).to.equal(this.proposer); - 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); - - expect(await this.mock.proposalEta(this.proposal.id)).to.equal(0n); - expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.false; - }); - - it('send ethers', async function () { - this.helper.setProposal( - [ - { - target: this.userEOA.address, - value, - }, - ], - '', - ); - - // Run proposal - await expect(async () => { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - return this.helper.execute(); - }).to.changeEtherBalances([this.mock, this.userEOA], [-value, value]); - }); - - describe('vote with signature', function () { - it('votes with an EOA signature', async function () { - await this.token.connect(this.voter1).delegate(this.userEOA); - - const nonce = await this.mock.nonces(this.userEOA); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await expect( - this.helper.vote({ - support: VoteType.For, - voter: this.userEOA.address, - nonce, - signature: signBallot(this.userEOA), - }), - ) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.userEOA, this.proposal.id, VoteType.For, ethers.parseEther('10'), ''); - - await this.helper.waitForDeadline(); - await this.helper.execute(); - - // After - expect(await this.mock.hasVoted(this.proposal.id, this.userEOA)).to.be.true; - expect(await this.mock.nonces(this.userEOA)).to.equal(nonce + 1n); - }); - - it('votes with a valid EIP-1271 signature', async function () { - const wallet = await ethers.deployContract('ERC1271WalletMock', [this.userEOA]); - - await this.token.connect(this.voter1).delegate(wallet); - - const nonce = await this.mock.nonces(this.userEOA); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await expect( - this.helper.vote({ - support: VoteType.For, - voter: wallet.target, - nonce, - signature: signBallot(this.userEOA), - }), - ) - .to.emit(this.mock, 'VoteCast') - .withArgs(wallet, this.proposal.id, VoteType.For, ethers.parseEther('10'), ''); - await this.helper.waitForDeadline(); - await this.helper.execute(); - - // After - expect(await this.mock.hasVoted(this.proposal.id, wallet)).to.be.true; - expect(await this.mock.nonces(wallet)).to.equal(nonce + 1n); - }); - - afterEach('no other votes are cast', async function () { - 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.false; - expect(await this.mock.hasVoted(this.proposal.id, this.voter2)).to.be.false; - }); - }); - - describe('should revert', function () { - describe('on propose', function () { - it('if proposal already exists', async function () { - await this.helper.propose(); - await expect(this.helper.propose()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs(this.proposal.id, ProposalState.Pending, ethers.ZeroHash); - }); - - it('if proposer has below threshold votes', async function () { - const votes = ethers.parseEther('10'); - const threshold = ethers.parseEther('1000'); - await this.mock.$_setProposalThreshold(threshold); - await expect(this.helper.connect(this.voter1).propose()) - .to.be.revertedWithCustomError(this.mock, 'GovernorInsufficientProposerVotes') - .withArgs(this.voter1, votes, threshold); - }); - }); - - describe('on vote', function () { - it('if proposal does not exist', async function () { - await expect(this.helper.connect(this.voter1).vote({ support: VoteType.For })) - .to.be.revertedWithCustomError(this.mock, 'GovernorNonexistentProposal') - .withArgs(this.proposal.id); - }); - - it('if voting has not started', async function () { - await this.helper.propose(); - await expect(this.helper.connect(this.voter1).vote({ support: VoteType.For })) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Pending, - GovernorHelper.proposalStatesToBitMap([ProposalState.Active]), - ); - }); - - it('if support value is invalid', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await expect(this.helper.vote({ support: 255 })).to.be.revertedWithCustomError( - this.mock, - 'GovernorInvalidVoteType', - ); - }); - - it('if vote was already casted', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await expect(this.helper.connect(this.voter1).vote({ support: VoteType.For })) - .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyCastVote') - .withArgs(this.voter1); - }); - - it('if voting is over', async function () { - await this.helper.propose(); - await this.helper.waitForDeadline(); - await expect(this.helper.connect(this.voter1).vote({ support: VoteType.For })) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Defeated, - GovernorHelper.proposalStatesToBitMap([ProposalState.Active]), - ); - }); - }); - - describe('on vote by signature', function () { - beforeEach(async function () { - await this.token.connect(this.voter1).delegate(this.userEOA); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - }); - - it('if signature does not match signer', async function () { - const nonce = await this.mock.nonces(this.userEOA); - - function tamper(str, index, mask) { - const arrayStr = ethers.getBytes(str); - arrayStr[index] ^= mask; - return ethers.hexlify(arrayStr); - } - - const voteParams = { - support: VoteType.For, - voter: this.userEOA.address, - nonce, - signature: (...args) => signBallot(this.userEOA)(...args).then(sig => tamper(sig, 42, 0xff)), - }; - - await expect(this.helper.vote(voteParams)) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') - .withArgs(voteParams.voter); - }); - - it('if vote nonce is incorrect', async function () { - const nonce = await this.mock.nonces(this.userEOA); - - const voteParams = { - support: VoteType.For, - voter: this.userEOA.address, - nonce: nonce + 1n, - signature: signBallot(this.userEOA), - }; - - await expect(this.helper.vote(voteParams)) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') - .withArgs(voteParams.voter); - }); - }); - - describe('on queue', function () { - it('always', async function () { - await this.helper.connect(this.proposer).propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await expect(this.helper.queue()).to.be.revertedWithCustomError(this.mock, 'GovernorQueueNotImplemented'); - }); - }); - - describe('on execute', function () { - it('if proposal does not exist', async function () { - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorNonexistentProposal') - .withArgs(this.proposal.id); - }); - - it('if quorum is not reached', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter3).vote({ support: VoteType.For }); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Active, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('if score not reached', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.Against }); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Active, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('if voting is not over', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Active, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('if receiver revert without reason', async function () { - this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunctionRevertsNoReason'), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await expect(this.helper.execute()).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - }); - - it('if receiver revert with reason', async function () { - this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunctionRevertsReason'), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await expect(this.helper.execute()).to.be.revertedWith('CallReceiverMock: reverting'); - }); - - it('if proposal was already executed', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.execute(); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Executed, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - }); - }); - - describe('state', function () { - it('Unset', async function () { - await expect(this.mock.state(this.proposal.id)) - .to.be.revertedWithCustomError(this.mock, 'GovernorNonexistentProposal') - .withArgs(this.proposal.id); - }); - - it('Pending & Active', async function () { - await this.helper.propose(); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Pending); - await this.helper.waitForSnapshot(); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Pending); - await this.helper.waitForSnapshot(1n); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Active); - }); - - it('Defeated', async function () { - await this.helper.propose(); - await this.helper.waitForDeadline(); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Active); - await this.helper.waitForDeadline(1n); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Defeated); - }); - - it('Succeeded', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Active); - await this.helper.waitForDeadline(1n); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Succeeded); - }); - - it('Executed', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.execute(); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Executed); - }); - }); - - describe('cancel', function () { - describe('internal', function () { - it('before proposal', async function () { - await expect(this.helper.cancel('internal')) - .to.be.revertedWithCustomError(this.mock, 'GovernorNonexistentProposal') - .withArgs(this.proposal.id); - }); - - it('after proposal', async function () { - await this.helper.propose(); - - await this.helper.cancel('internal'); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - - await this.helper.waitForSnapshot(); - await expect(this.helper.connect(this.voter1).vote({ support: VoteType.For })) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Active]), - ); - }); - - it('after vote', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - - await this.helper.cancel('internal'); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - - await this.helper.waitForDeadline(); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('after deadline', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await this.helper.cancel('internal'); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('after execution', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.execute(); - - await expect(this.helper.cancel('internal')) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Executed, - GovernorHelper.proposalStatesToBitMap( - [ProposalState.Canceled, ProposalState.Expired, ProposalState.Executed], - { inverted: true }, - ), - ); - }); - }); - - describe('public', function () { - it('before proposal', async function () { - await expect(this.helper.cancel('external')) - .to.be.revertedWithCustomError(this.mock, 'GovernorNonexistentProposal') - .withArgs(this.proposal.id); - }); - - it('after proposal', async function () { - await this.helper.propose(); - - await this.helper.cancel('external'); - }); - - it('after proposal - restricted to proposer', async function () { - await this.helper.connect(this.proposer).propose(); - - await expect(this.helper.connect(this.owner).cancel('external')) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyProposer') - .withArgs(this.owner); - }); - - it('after vote started', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(1n); // snapshot + 1 block - - await expect(this.helper.cancel('external')) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Active, - GovernorHelper.proposalStatesToBitMap([ProposalState.Pending]), - ); - }); - - it('after vote', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - - await expect(this.helper.cancel('external')) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Active, - GovernorHelper.proposalStatesToBitMap([ProposalState.Pending]), - ); - }); - - it('after deadline', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.cancel('external')) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Succeeded, - GovernorHelper.proposalStatesToBitMap([ProposalState.Pending]), - ); - }); - - it('after execution', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.execute(); - - await expect(this.helper.cancel('external')) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Executed, - GovernorHelper.proposalStatesToBitMap([ProposalState.Pending]), - ); - }); - }); - }); - - describe('proposal length', function () { - it('empty', async function () { - this.helper.setProposal([], ''); - - await expect(this.helper.propose()) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidProposalLength') - .withArgs(0, 0, 0); - }); - - it('mismatch #1', async function () { - this.helper.setProposal( - { - targets: [], - values: [0n], - data: [this.receiver.interface.encodeFunctionData('mockFunction')], - }, - '', - ); - await expect(this.helper.propose()) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidProposalLength') - .withArgs(0, 1, 1); - }); - - it('mismatch #2', async function () { - this.helper.setProposal( - { - targets: [this.receiver.target], - values: [], - data: [this.receiver.interface.encodeFunctionData('mockFunction')], - }, - '', - ); - await expect(this.helper.propose()) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidProposalLength') - .withArgs(1, 1, 0); - }); - - it('mismatch #3', async function () { - this.helper.setProposal( - { - targets: [this.receiver.target], - values: [0n], - data: [], - }, - '', - ); - await expect(this.helper.propose()) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidProposalLength') - .withArgs(1, 0, 1); - }); - }); - - describe('frontrun protection using description suffix', function () { - function shouldPropose() { - it('proposer can propose', async function () { - const txPropose = await this.helper.connect(this.proposer).propose(); - - await expect(txPropose) - .to.emit(this.mock, 'ProposalCreated') - .withArgs( - this.proposal.id, - this.proposer, - this.proposal.targets, - this.proposal.values, - this.proposal.signatures, - this.proposal.data, - (await time.clockFromReceipt[mode](txPropose)) + votingDelay, - (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod, - this.proposal.description, - ); - }); - - it('someone else can propose', async function () { - const txPropose = await this.helper.connect(this.voter1).propose(); - - await expect(txPropose) - .to.emit(this.mock, 'ProposalCreated') - .withArgs( - this.proposal.id, - this.voter1, - this.proposal.targets, - this.proposal.values, - this.proposal.signatures, - this.proposal.data, - (await time.clockFromReceipt[mode](txPropose)) + votingDelay, - (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod, - this.proposal.description, - ); - }); - } - - describe('without protection', function () { - describe('without suffix', function () { - shouldPropose(); - }); - - describe('with different suffix', function () { - beforeEach(function () { - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunction'), - value, - }, - ], - `#wrong-suffix=${this.proposer}`, - ); - }); - - shouldPropose(); - }); - - describe('with proposer suffix but bad address part', function () { - beforeEach(function () { - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunction'), - value, - }, - ], - `#proposer=0x3C44CdDdB6a900fa2b585dd299e03d12FA429XYZ`, // XYZ are not a valid hex char - ); - }); - - shouldPropose(); - }); - }); - - describe('with protection via proposer suffix', function () { - beforeEach(function () { - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunction'), - value, - }, - ], - `#proposer=${this.proposer}`, - ); - }); - - shouldPropose(); - }); - }); - - describe('onlyGovernance updates', function () { - it('setVotingDelay is protected', async function () { - await expect(this.mock.connect(this.owner).setVotingDelay(0n)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('setVotingPeriod is protected', async function () { - await expect(this.mock.connect(this.owner).setVotingPeriod(32n)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('setProposalThreshold is protected', async function () { - await expect(this.mock.connect(this.owner).setProposalThreshold(1_000_000_000_000_000_000n)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('can setVotingDelay through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setVotingDelay', [0n]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()).to.emit(this.mock, 'VotingDelaySet').withArgs(4n, 0n); - - expect(await this.mock.votingDelay()).to.equal(0n); - }); - - it('can setVotingPeriod through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setVotingPeriod', [32n]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()).to.emit(this.mock, 'VotingPeriodSet').withArgs(16n, 32n); - - expect(await this.mock.votingPeriod()).to.equal(32n); - }); - - it('cannot setVotingPeriod to 0 through governance', async function () { - const votingPeriod = 0n; - - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setVotingPeriod', [votingPeriod]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidVotingPeriod') - .withArgs(votingPeriod); - }); - - it('can setProposalThreshold to 0 through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setProposalThreshold', [1_000_000_000_000_000_000n]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()) - .to.emit(this.mock, 'ProposalThresholdSet') - .withArgs(0n, 1_000_000_000_000_000_000n); - - expect(await this.mock.proposalThreshold()).to.equal(1_000_000_000_000_000_000n); - }); - }); - - describe('safe receive', function () { - describe('ERC721', function () { - const tokenId = 1n; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC721', ['Non Fungible Token', 'NFT']); - await this.token.$_mint(this.owner, tokenId); - }); - - it('can receive an ERC721 safeTransfer', async function () { - await this.token.connect(this.owner).safeTransferFrom(this.owner, this.mock, tokenId); - }); - }); - - describe('ERC1155', function () { - const tokenIds = { - 1: 1000n, - 2: 2000n, - 3: 3000n, - }; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']); - await this.token.$_mintBatch(this.owner, Object.keys(tokenIds), Object.values(tokenIds), '0x'); - }); - - it('can receive ERC1155 safeTransfer', async function () { - await this.token.connect(this.owner).safeTransferFrom( - this.owner, - this.mock, - ...Object.entries(tokenIds)[0], // id + amount - '0x', - ); - }); - - it('can receive ERC1155 safeBatchTransfer', async function () { - await this.token - .connect(this.owner) - .safeBatchTransferFrom(this.owner, this.mock, Object.keys(tokenIds), Object.values(tokenIds), '0x'); - }); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/TimelockController.test.js b/lib_openzeppelin_contracts/test/governance/TimelockController.test.js deleted file mode 100644 index 08410d4..0000000 --- a/lib_openzeppelin_contracts/test/governance/TimelockController.test.js +++ /dev/null @@ -1,1279 +0,0 @@ -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 { GovernorHelper } = require('../helpers/governance'); -const { OperationState } = require('../helpers/enums'); -const time = require('../helpers/time'); - -const { shouldSupportInterfaces } = require('../utils/introspection/SupportsInterface.behavior'); - -const salt = '0x025e7b0be353a74631ad648c667493c0e1cd31caa4cc2d3520fdc171ea0cc726'; // a random value - -const MINDELAY = time.duration.days(1); -const DEFAULT_ADMIN_ROLE = ethers.ZeroHash; -const PROPOSER_ROLE = ethers.id('PROPOSER_ROLE'); -const EXECUTOR_ROLE = ethers.id('EXECUTOR_ROLE'); -const CANCELLER_ROLE = ethers.id('CANCELLER_ROLE'); - -const getAddress = obj => obj.address ?? obj.target ?? obj; - -function genOperation(target, value, data, predecessor, salt) { - const id = ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode( - ['address', 'uint256', 'bytes', 'uint256', 'bytes32'], - [getAddress(target), value, data, predecessor, salt], - ), - ); - return { id, target, value, data, predecessor, salt }; -} - -function genOperationBatch(targets, values, payloads, predecessor, salt) { - const id = ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode( - ['address[]', 'uint256[]', 'bytes[]', 'uint256', 'bytes32'], - [targets.map(getAddress), values, payloads, predecessor, salt], - ), - ); - return { id, targets, values, payloads, predecessor, salt }; -} - -async function fixture() { - const [admin, proposer, canceller, executor, other] = await ethers.getSigners(); - - const mock = await ethers.deployContract('TimelockController', [MINDELAY, [proposer], [executor], admin]); - const callreceivermock = await ethers.deployContract('CallReceiverMock'); - const implementation2 = await ethers.deployContract('Implementation2'); - - expect(await mock.hasRole(CANCELLER_ROLE, proposer)).to.be.true; - await mock.connect(admin).revokeRole(CANCELLER_ROLE, proposer); - await mock.connect(admin).grantRole(CANCELLER_ROLE, canceller); - - return { - admin, - proposer, - canceller, - executor, - other, - mock, - callreceivermock, - implementation2, - }; -} - -describe('TimelockController', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldSupportInterfaces(['ERC1155Receiver']); - - it('initial state', async function () { - expect(await this.mock.getMinDelay()).to.equal(MINDELAY); - - expect(await this.mock.DEFAULT_ADMIN_ROLE()).to.equal(DEFAULT_ADMIN_ROLE); - expect(await this.mock.PROPOSER_ROLE()).to.equal(PROPOSER_ROLE); - expect(await this.mock.EXECUTOR_ROLE()).to.equal(EXECUTOR_ROLE); - expect(await this.mock.CANCELLER_ROLE()).to.equal(CANCELLER_ROLE); - - expect( - await Promise.all( - [PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, this.proposer)), - ), - ).to.deep.equal([true, false, false]); - - expect( - await Promise.all( - [PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, this.canceller)), - ), - ).to.deep.equal([false, true, false]); - - expect( - await Promise.all( - [PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE].map(role => this.mock.hasRole(role, this.executor)), - ), - ).to.deep.equal([false, false, true]); - }); - - it('optional admin', async function () { - const mock = await ethers.deployContract('TimelockController', [ - MINDELAY, - [this.proposer], - [this.executor], - ethers.ZeroAddress, - ]); - expect(await mock.hasRole(DEFAULT_ADMIN_ROLE, this.admin)).to.be.false; - expect(await mock.hasRole(DEFAULT_ADMIN_ROLE, mock.target)).to.be.true; - }); - - describe('methods', function () { - describe('operation hashing', function () { - it('hashOperation', async function () { - this.operation = genOperation( - '0x29cebefe301c6ce1bb36b58654fea275e1cacc83', - '0xf94fdd6e21da21d2', - '0xa3bc5104', - '0xba41db3be0a9929145cfe480bd0f1f003689104d275ae912099f925df424ef94', - '0x60d9109846ab510ed75c15f979ae366a8a2ace11d34ba9788c13ac296db50e6e', - ); - expect( - await this.mock.hashOperation( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - ), - ).to.equal(this.operation.id); - }); - - it('hashOperationBatch', async function () { - this.operation = genOperationBatch( - Array(8).fill('0x2d5f21620e56531c1d59c2df9b8e95d129571f71'), - Array(8).fill('0x2b993cfce932ccee'), - Array(8).fill('0xcf51966b'), - '0xce8f45069cc71d25f71ba05062de1a3974f9849b004de64a70998bca9d29c2e7', - '0x8952d74c110f72bfe5accdf828c74d53a7dfb71235dfa8a1e8c75d8576b372ff', - ); - expect( - await this.mock.hashOperationBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ), - ).to.equal(this.operation.id); - }); - }); - describe('simple', function () { - describe('schedule', function () { - beforeEach(async function () { - this.operation = genOperation( - '0x31754f590B97fD975Eb86938f18Cc304E264D2F2', - 0n, - '0x3bf92ccc', - ethers.ZeroHash, - salt, - ); - }); - - it('proposer can schedule', async function () { - const tx = await this.mock - .connect(this.proposer) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ); - - await expect(tx) - .to.emit(this.mock, 'CallScheduled') - .withArgs( - this.operation.id, - 0n, - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - MINDELAY, - ) - .to.emit(this.mock, 'CallSalt') - .withArgs(this.operation.id, this.operation.salt); - - expect(await this.mock.getTimestamp(this.operation.id)).to.equal( - (await time.clockFromReceipt.timestamp(tx)) + MINDELAY, - ); - }); - - it('prevent overwriting active operation', async function () { - await this.mock - .connect(this.proposer) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ); - - await expect( - this.mock - .connect(this.proposer) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Unset)); - }); - - it('prevent non-proposer from committing', async function () { - await expect( - this.mock - .connect(this.other) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, PROPOSER_ROLE); - }); - - it('enforce minimum delay', async function () { - await expect( - this.mock - .connect(this.proposer) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - MINDELAY - 1n, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockInsufficientDelay') - .withArgs(MINDELAY - 1n, MINDELAY); - }); - - it('schedule operation with salt zero', async function () { - await expect( - this.mock - .connect(this.proposer) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - ethers.ZeroHash, - MINDELAY, - ), - ).to.not.emit(this.mock, 'CallSalt'); - }); - }); - - describe('execute', function () { - beforeEach(async function () { - this.operation = genOperation( - '0xAe22104DCD970750610E6FE15E623468A98b15f7', - 0n, - '0x13e414de', - ethers.ZeroHash, - '0xc1059ed2dc130227aa1d1d539ac94c641306905c020436c636e19e3fab56fc7f', - ); - }); - - it('revert if operation is not scheduled', async function () { - await expect( - this.mock - .connect(this.executor) - .execute( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - describe('with scheduled operation', function () { - beforeEach(async function () { - await this.mock - .connect(this.proposer) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ); - }); - - it('revert if execution comes too early 1/2', async function () { - await expect( - this.mock - .connect(this.executor) - .execute( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - it('revert if execution comes too early 2/2', async function () { - // -1 is too tight, test sometime fails - await this.mock.getTimestamp(this.operation.id).then(clock => time.increaseTo.timestamp(clock - 5n)); - - await expect( - this.mock - .connect(this.executor) - .execute( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - describe('on time', function () { - beforeEach(async function () { - await this.mock.getTimestamp(this.operation.id).then(time.increaseTo.timestamp); - }); - - it('executor can reveal', async function () { - await expect( - this.mock - .connect(this.executor) - .execute( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.emit(this.mock, 'CallExecuted') - .withArgs(this.operation.id, 0n, this.operation.target, this.operation.value, this.operation.data); - }); - - it('prevent non-executor from revealing', async function () { - await expect( - this.mock - .connect(this.other) - .execute( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, EXECUTOR_ROLE); - }); - - it('prevents reentrancy execution', async function () { - // Create operation - const reentrant = await ethers.deployContract('$TimelockReentrant'); - const reentrantOperation = genOperation( - reentrant, - 0n, - reentrant.interface.encodeFunctionData('reenter'), - ethers.ZeroHash, - salt, - ); - - // Schedule so it can be executed - await this.mock - .connect(this.proposer) - .schedule( - reentrantOperation.target, - reentrantOperation.value, - reentrantOperation.data, - reentrantOperation.predecessor, - reentrantOperation.salt, - MINDELAY, - ); - - // Advance on time to make the operation executable - await this.mock.getTimestamp(reentrantOperation.id).then(time.increaseTo.timestamp); - - // Grant executor role to the reentrant contract - await this.mock.connect(this.admin).grantRole(EXECUTOR_ROLE, reentrant); - - // Prepare reenter - const data = this.mock.interface.encodeFunctionData('execute', [ - getAddress(reentrantOperation.target), - reentrantOperation.value, - reentrantOperation.data, - reentrantOperation.predecessor, - reentrantOperation.salt, - ]); - await reentrant.enableRentrancy(this.mock, data); - - // Expect to fail - await expect( - this.mock - .connect(this.executor) - .execute( - reentrantOperation.target, - reentrantOperation.value, - reentrantOperation.data, - reentrantOperation.predecessor, - reentrantOperation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(reentrantOperation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - - // Disable reentrancy - await reentrant.disableReentrancy(); - const nonReentrantOperation = reentrantOperation; // Not anymore - - // Try again successfully - await expect( - this.mock - .connect(this.executor) - .execute( - nonReentrantOperation.target, - nonReentrantOperation.value, - nonReentrantOperation.data, - nonReentrantOperation.predecessor, - nonReentrantOperation.salt, - ), - ) - .to.emit(this.mock, 'CallExecuted') - .withArgs( - nonReentrantOperation.id, - 0n, - getAddress(nonReentrantOperation), - nonReentrantOperation.value, - nonReentrantOperation.data, - ); - }); - }); - }); - }); - }); - - describe('batch', function () { - describe('schedule', function () { - beforeEach(async function () { - this.operation = genOperationBatch( - Array(8).fill('0xEd912250835c812D4516BBD80BdaEA1bB63a293C'), - Array(8).fill(0n), - Array(8).fill('0x2fcb7a88'), - ethers.ZeroHash, - '0x6cf9d042ade5de78bed9ffd075eb4b2a4f6b1736932c2dc8af517d6e066f51f5', - ); - }); - - it('proposer can schedule', async function () { - const tx = this.mock - .connect(this.proposer) - .scheduleBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ); - for (const i in this.operation.targets) { - await expect(tx) - .to.emit(this.mock, 'CallScheduled') - .withArgs( - this.operation.id, - i, - getAddress(this.operation.targets[i]), - this.operation.values[i], - this.operation.payloads[i], - this.operation.predecessor, - MINDELAY, - ) - .to.emit(this.mock, 'CallSalt') - .withArgs(this.operation.id, this.operation.salt); - } - - expect(await this.mock.getTimestamp(this.operation.id)).to.equal( - (await time.clockFromReceipt.timestamp(tx)) + MINDELAY, - ); - }); - - it('prevent overwriting active operation', async function () { - await this.mock - .connect(this.proposer) - .scheduleBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ); - - await expect( - this.mock - .connect(this.proposer) - .scheduleBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Unset)); - }); - - it('length of batch parameter must match #1', async function () { - await expect( - this.mock - .connect(this.proposer) - .scheduleBatch( - this.operation.targets, - [], - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength') - .withArgs(this.operation.targets.length, this.operation.payloads.length, 0n); - }); - - it('length of batch parameter must match #1', async function () { - await expect( - this.mock - .connect(this.proposer) - .scheduleBatch( - this.operation.targets, - this.operation.values, - [], - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength') - .withArgs(this.operation.targets.length, 0n, this.operation.payloads.length); - }); - - it('prevent non-proposer from committing', async function () { - await expect( - this.mock - .connect(this.other) - .scheduleBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, PROPOSER_ROLE); - }); - - it('enforce minimum delay', async function () { - await expect( - this.mock - .connect(this.proposer) - .scheduleBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - MINDELAY - 1n, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockInsufficientDelay') - .withArgs(MINDELAY - 1n, MINDELAY); - }); - }); - - describe('execute', function () { - beforeEach(async function () { - this.operation = genOperationBatch( - Array(8).fill('0x76E53CcEb05131Ef5248553bEBDb8F70536830b1'), - Array(8).fill(0n), - Array(8).fill('0x58a60f63'), - ethers.ZeroHash, - '0x9545eeabc7a7586689191f78a5532443698538e54211b5bd4d7dc0fc0102b5c7', - ); - }); - - it('revert if operation is not scheduled', async function () { - await expect( - this.mock - .connect(this.executor) - .executeBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - describe('with scheduled operation', function () { - beforeEach(async function () { - await this.mock - .connect(this.proposer) - .scheduleBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ); - }); - - it('revert if execution comes too early 1/2', async function () { - await expect( - this.mock - .connect(this.executor) - .executeBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - it('revert if execution comes too early 2/2', async function () { - // -1 is to tight, test sometime fails - await this.mock.getTimestamp(this.operation.id).then(clock => time.increaseTo.timestamp(clock - 5n)); - - await expect( - this.mock - .connect(this.executor) - .executeBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(this.operation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - describe('on time', function () { - beforeEach(async function () { - await this.mock.getTimestamp(this.operation.id).then(time.increaseTo.timestamp); - }); - - it('executor can reveal', async function () { - const tx = this.mock - .connect(this.executor) - .executeBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ); - for (const i in this.operation.targets) { - await expect(tx) - .to.emit(this.mock, 'CallExecuted') - .withArgs( - this.operation.id, - i, - this.operation.targets[i], - this.operation.values[i], - this.operation.payloads[i], - ); - } - }); - - it('prevent non-executor from revealing', async function () { - await expect( - this.mock - .connect(this.other) - .executeBatch( - this.operation.targets, - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, EXECUTOR_ROLE); - }); - - it('length mismatch #1', async function () { - await expect( - this.mock - .connect(this.executor) - .executeBatch( - [], - this.operation.values, - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength') - .withArgs(0, this.operation.payloads.length, this.operation.values.length); - }); - - it('length mismatch #2', async function () { - await expect( - this.mock - .connect(this.executor) - .executeBatch( - this.operation.targets, - [], - this.operation.payloads, - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength') - .withArgs(this.operation.targets.length, this.operation.payloads.length, 0n); - }); - - it('length mismatch #3', async function () { - await expect( - this.mock - .connect(this.executor) - .executeBatch( - this.operation.targets, - this.operation.values, - [], - this.operation.predecessor, - this.operation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockInvalidOperationLength') - .withArgs(this.operation.targets.length, 0n, this.operation.values.length); - }); - - it('prevents reentrancy execution', async function () { - // Create operation - const reentrant = await ethers.deployContract('$TimelockReentrant'); - const reentrantBatchOperation = genOperationBatch( - [reentrant], - [0n], - [reentrant.interface.encodeFunctionData('reenter')], - ethers.ZeroHash, - salt, - ); - - // Schedule so it can be executed - await this.mock - .connect(this.proposer) - .scheduleBatch( - reentrantBatchOperation.targets, - reentrantBatchOperation.values, - reentrantBatchOperation.payloads, - reentrantBatchOperation.predecessor, - reentrantBatchOperation.salt, - MINDELAY, - ); - - // Advance on time to make the operation executable - await this.mock.getTimestamp(reentrantBatchOperation.id).then(time.increaseTo.timestamp); - - // Grant executor role to the reentrant contract - await this.mock.connect(this.admin).grantRole(EXECUTOR_ROLE, reentrant); - - // Prepare reenter - const data = this.mock.interface.encodeFunctionData('executeBatch', [ - reentrantBatchOperation.targets.map(getAddress), - reentrantBatchOperation.values, - reentrantBatchOperation.payloads, - reentrantBatchOperation.predecessor, - reentrantBatchOperation.salt, - ]); - await reentrant.enableRentrancy(this.mock, data); - - // Expect to fail - await expect( - this.mock - .connect(this.executor) - .executeBatch( - reentrantBatchOperation.targets, - reentrantBatchOperation.values, - reentrantBatchOperation.payloads, - reentrantBatchOperation.predecessor, - reentrantBatchOperation.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs(reentrantBatchOperation.id, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - - // Disable reentrancy - await reentrant.disableReentrancy(); - const nonReentrantBatchOperation = reentrantBatchOperation; // Not anymore - - // Try again successfully - const tx = this.mock - .connect(this.executor) - .executeBatch( - nonReentrantBatchOperation.targets, - nonReentrantBatchOperation.values, - nonReentrantBatchOperation.payloads, - nonReentrantBatchOperation.predecessor, - nonReentrantBatchOperation.salt, - ); - for (const i in nonReentrantBatchOperation.targets) { - await expect(tx) - .to.emit(this.mock, 'CallExecuted') - .withArgs( - nonReentrantBatchOperation.id, - i, - nonReentrantBatchOperation.targets[i], - nonReentrantBatchOperation.values[i], - nonReentrantBatchOperation.payloads[i], - ); - } - }); - }); - }); - - it('partial execution', async function () { - const operation = genOperationBatch( - [this.callreceivermock, this.callreceivermock, this.callreceivermock], - [0n, 0n, 0n], - [ - this.callreceivermock.interface.encodeFunctionData('mockFunction'), - this.callreceivermock.interface.encodeFunctionData('mockFunctionRevertsNoReason'), - this.callreceivermock.interface.encodeFunctionData('mockFunction'), - ], - ethers.ZeroHash, - '0x8ac04aa0d6d66b8812fb41d39638d37af0a9ab11da507afd65c509f8ed079d3e', - ); - - await this.mock - .connect(this.proposer) - .scheduleBatch( - operation.targets, - operation.values, - operation.payloads, - operation.predecessor, - operation.salt, - MINDELAY, - ); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - await expect( - this.mock - .connect(this.executor) - .executeBatch( - operation.targets, - operation.values, - operation.payloads, - operation.predecessor, - operation.salt, - ), - ).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - }); - }); - }); - - describe('cancel', function () { - beforeEach(async function () { - this.operation = genOperation( - '0xC6837c44AA376dbe1d2709F13879E040CAb653ca', - 0n, - '0x296e58dd', - ethers.ZeroHash, - '0xa2485763600634800df9fc9646fb2c112cf98649c55f63dd1d9c7d13a64399d9', - ); - await this.mock - .connect(this.proposer) - .schedule( - this.operation.target, - this.operation.value, - this.operation.data, - this.operation.predecessor, - this.operation.salt, - MINDELAY, - ); - }); - - it('canceller can cancel', async function () { - await expect(this.mock.connect(this.canceller).cancel(this.operation.id)) - .to.emit(this.mock, 'Cancelled') - .withArgs(this.operation.id); - }); - - it('cannot cancel invalid operation', async function () { - await expect(this.mock.connect(this.canceller).cancel(ethers.ZeroHash)) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexpectedOperationState') - .withArgs( - ethers.ZeroHash, - GovernorHelper.proposalStatesToBitMap([OperationState.Waiting, OperationState.Ready]), - ); - }); - - it('prevent non-canceller from canceling', async function () { - await expect(this.mock.connect(this.other).cancel(this.operation.id)) - .to.be.revertedWithCustomError(this.mock, 'AccessControlUnauthorizedAccount') - .withArgs(this.other, CANCELLER_ROLE); - }); - }); - }); - - describe('maintenance', function () { - it('prevent unauthorized maintenance', async function () { - await expect(this.mock.connect(this.other).updateDelay(0n)) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnauthorizedCaller') - .withArgs(this.other); - }); - - it('timelock scheduled maintenance', async function () { - const newDelay = time.duration.hours(6); - const operation = genOperation( - this.mock, - 0n, - this.mock.interface.encodeFunctionData('updateDelay', [newDelay]), - ethers.ZeroHash, - '0xf8e775b2c5f4d66fb5c7fa800f35ef518c262b6014b3c0aee6ea21bff157f108', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - await expect( - this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt), - ) - .to.emit(this.mock, 'MinDelayChange') - .withArgs(MINDELAY, newDelay); - - expect(await this.mock.getMinDelay()).to.equal(newDelay); - }); - }); - - describe('dependency', function () { - beforeEach(async function () { - this.operation1 = genOperation( - '0xdE66bD4c97304200A95aE0AadA32d6d01A867E39', - 0n, - '0x01dc731a', - ethers.ZeroHash, - '0x64e932133c7677402ead2926f86205e2ca4686aebecf5a8077627092b9bb2feb', - ); - this.operation2 = genOperation( - '0x3c7944a3F1ee7fc8c5A5134ba7c79D11c3A1FCa3', - 0n, - '0x8f531849', - this.operation1.id, - '0x036e1311cac523f9548e6461e29fb1f8f9196b91910a41711ea22f5de48df07d', - ); - await this.mock - .connect(this.proposer) - .schedule( - this.operation1.target, - this.operation1.value, - this.operation1.data, - this.operation1.predecessor, - this.operation1.salt, - MINDELAY, - ); - await this.mock - .connect(this.proposer) - .schedule( - this.operation2.target, - this.operation2.value, - this.operation2.data, - this.operation2.predecessor, - this.operation2.salt, - MINDELAY, - ); - - await this.mock.getTimestamp(this.operation2.id).then(time.increaseTo.timestamp); - }); - - it('cannot execute before dependency', async function () { - await expect( - this.mock - .connect(this.executor) - .execute( - this.operation2.target, - this.operation2.value, - this.operation2.data, - this.operation2.predecessor, - this.operation2.salt, - ), - ) - .to.be.revertedWithCustomError(this.mock, 'TimelockUnexecutedPredecessor') - .withArgs(this.operation1.id); - }); - - it('can execute after dependency', async function () { - await this.mock - .connect(this.executor) - .execute( - this.operation1.target, - this.operation1.value, - this.operation1.data, - this.operation1.predecessor, - this.operation1.salt, - ); - await this.mock - .connect(this.executor) - .execute( - this.operation2.target, - this.operation2.value, - this.operation2.data, - this.operation2.predecessor, - this.operation2.salt, - ); - }); - }); - - describe('usage scenario', function () { - this.timeout(10000); - - it('call', async function () { - const operation = genOperation( - this.implementation2, - 0n, - this.implementation2.interface.encodeFunctionData('setValue', [42n]), - ethers.ZeroHash, - '0x8043596363daefc89977b25f9d9b4d06c3910959ef0c4d213557a903e1b555e2', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - await this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt); - - expect(await this.implementation2.getValue()).to.equal(42n); - }); - - it('call reverting', async function () { - const operation = genOperation( - this.callreceivermock, - 0n, - this.callreceivermock.interface.encodeFunctionData('mockFunctionRevertsNoReason'), - ethers.ZeroHash, - '0xb1b1b276fdf1a28d1e00537ea73b04d56639128b08063c1a2f70a52e38cba693', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - await expect( - this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt), - ).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - }); - - it('call throw', async function () { - const operation = genOperation( - this.callreceivermock, - 0n, - this.callreceivermock.interface.encodeFunctionData('mockFunctionThrows'), - ethers.ZeroHash, - '0xe5ca79f295fc8327ee8a765fe19afb58f4a0cbc5053642bfdd7e73bc68e0fc67', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - // Targeted function reverts with a panic code (0x1) + the timelock bubble the panic code - await expect( - this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt), - ).to.be.revertedWithPanic(PANIC_CODES.ASSERTION_ERROR); - }); - - it('call out of gas', async function () { - const operation = genOperation( - this.callreceivermock, - 0n, - this.callreceivermock.interface.encodeFunctionData('mockFunctionOutOfGas'), - ethers.ZeroHash, - '0xf3274ce7c394c5b629d5215723563a744b817e1730cca5587c567099a14578fd', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - await expect( - this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { - gasLimit: '100000', - }), - ).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - }); - - it('call payable with eth', async function () { - const operation = genOperation( - this.callreceivermock, - 1, - this.callreceivermock.interface.encodeFunctionData('mockFunction'), - ethers.ZeroHash, - '0x5ab73cd33477dcd36c1e05e28362719d0ed59a7b9ff14939de63a43073dc1f44', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n); - - await this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, { - value: 1, - }); - - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(1n); - }); - - it('call nonpayable with eth', async function () { - const operation = genOperation( - this.callreceivermock, - 1, - this.callreceivermock.interface.encodeFunctionData('mockFunctionNonPayable'), - ethers.ZeroHash, - '0xb78edbd920c7867f187e5aa6294ae5a656cfbf0dea1ccdca3751b740d0f2bdf8', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n); - - await expect( - this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt), - ).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n); - }); - - it('call reverting with eth', async function () { - const operation = genOperation( - this.callreceivermock, - 1, - this.callreceivermock.interface.encodeFunctionData('mockFunctionRevertsNoReason'), - ethers.ZeroHash, - '0xdedb4563ef0095db01d81d3f2decf57cf83e4a72aa792af14c43a792b56f4de6', - ); - - await this.mock - .connect(this.proposer) - .schedule(operation.target, operation.value, operation.data, operation.predecessor, operation.salt, MINDELAY); - - await this.mock.getTimestamp(operation.id).then(time.increaseTo.timestamp); - - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n); - - await expect( - this.mock - .connect(this.executor) - .execute(operation.target, operation.value, operation.data, operation.predecessor, operation.salt), - ).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - expect(await ethers.provider.getBalance(this.callreceivermock)).to.equal(0n); - }); - }); - - describe('safe receive', function () { - describe('ERC721', function () { - const tokenId = 1n; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC721', ['Non Fungible Token', 'NFT']); - await this.token.$_mint(this.other, tokenId); - }); - - it('can receive an ERC721 safeTransfer', async function () { - await this.token.connect(this.other).safeTransferFrom(this.other, this.mock, tokenId); - }); - }); - - describe('ERC1155', function () { - const tokenIds = { - 1: 1000n, - 2: 2000n, - 3: 3000n, - }; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']); - await this.token.$_mintBatch(this.other, Object.keys(tokenIds), Object.values(tokenIds), '0x'); - }); - - it('can receive ERC1155 safeTransfer', async function () { - await this.token.connect(this.other).safeTransferFrom( - this.other, - this.mock, - ...Object.entries(tokenIds)[0n], // id + amount - '0x', - ); - }); - - it('can receive ERC1155 safeBatchTransfer', async function () { - await this.token - .connect(this.other) - .safeBatchTransferFrom(this.other, this.mock, Object.keys(tokenIds), Object.values(tokenIds), '0x'); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorERC721.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorERC721.test.js deleted file mode 100644 index 1ae5508..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorERC721.test.js +++ /dev/null @@ -1,131 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { GovernorHelper } = require('../../helpers/governance'); -const { VoteType } = require('../../helpers/enums'); - -const TOKENS = [ - { Token: '$ERC721Votes', mode: 'blocknumber' }, - { Token: '$ERC721VotesTimestampMock', mode: 'timestamp' }, -]; - -const name = 'OZ-Governor'; -const version = '1'; -const tokenName = 'MockNFToken'; -const tokenSymbol = 'MTKN'; -const NFT0 = 0n; -const NFT1 = 1n; -const NFT2 = 2n; -const NFT3 = 3n; -const NFT4 = 4n; -const votingDelay = 4n; -const votingPeriod = 16n; -const value = ethers.parseEther('1'); - -describe('GovernorERC721', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [owner, voter1, voter2, voter3, voter4] = await ethers.getSigners(); - const receiver = await ethers.deployContract('CallReceiverMock'); - - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); - const mock = await ethers.deployContract('$GovernorMock', [ - name, // name - votingDelay, // initialVotingDelay - votingPeriod, // initialVotingPeriod - 0n, // initialProposalThreshold - token, // tokenAddress - 10n, // quorumNumeratorValue - ]); - - await owner.sendTransaction({ to: mock, value }); - await Promise.all([NFT0, NFT1, NFT2, NFT3, NFT4].map(tokenId => token.$_mint(owner, tokenId))); - - const helper = new GovernorHelper(mock, mode); - await helper.connect(owner).delegate({ token, to: voter1, tokenId: NFT0 }); - await helper.connect(owner).delegate({ token, to: voter2, tokenId: NFT1 }); - await helper.connect(owner).delegate({ token, to: voter2, tokenId: NFT2 }); - await helper.connect(owner).delegate({ token, to: voter3, tokenId: NFT3 }); - await helper.connect(owner).delegate({ token, to: voter4, tokenId: NFT4 }); - - return { - owner, - voter1, - voter2, - voter3, - voter4, - receiver, - token, - mock, - helper, - }; - }; - - describe(`using ${Token}`, function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - // initiate fresh proposal - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunction'), - value, - }, - ], - '', - ); - }); - - 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.quorum(0n)).to.equal(0n); - - expect(await this.token.getVotes(this.voter1)).to.equal(1n); // NFT0 - expect(await this.token.getVotes(this.voter2)).to.equal(2n); // NFT1 & NFT2 - expect(await this.token.getVotes(this.voter3)).to.equal(1n); // NFT3 - expect(await this.token.getVotes(this.voter4)).to.equal(1n); // NFT4 - }); - - it('voting with ERC721 token', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - - await expect(this.helper.connect(this.voter1).vote({ support: VoteType.For })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter1, this.proposal.id, VoteType.For, 1n, ''); - - await expect(this.helper.connect(this.voter2).vote({ support: VoteType.For })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter2, this.proposal.id, VoteType.For, 2n, ''); - - await expect(this.helper.connect(this.voter3).vote({ support: VoteType.Against })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter3, this.proposal.id, VoteType.Against, 1n, ''); - - await expect(this.helper.connect(this.voter4).vote({ support: VoteType.Abstain })) - .to.emit(this.mock, 'VoteCast') - .withArgs(this.voter4, this.proposal.id, VoteType.Abstain, 1n, ''); - - 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 this.mock.hasVoted(this.proposal.id, this.voter3)).to.be.true; - expect(await this.mock.hasVoted(this.proposal.id, this.voter4)).to.be.true; - - expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([ - 1n, // againstVotes - 3n, // forVotes - 1n, // abstainVotes - ]); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorPreventLateQuorum.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorPreventLateQuorum.test.js deleted file mode 100644 index aac0e68..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorPreventLateQuorum.test.js +++ /dev/null @@ -1,185 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { GovernorHelper } = require('../../helpers/governance'); -const { ProposalState, VoteType } = require('../../helpers/enums'); -const time = require('../../helpers/time'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const name = 'OZ-Governor'; -const version = '1'; -const tokenName = 'MockToken'; -const tokenSymbol = 'MTKN'; -const tokenSupply = ethers.parseEther('100'); -const votingDelay = 4n; -const votingPeriod = 16n; -const lateQuorumVoteExtension = 8n; -const quorum = ethers.parseEther('1'); -const value = ethers.parseEther('1'); - -describe('GovernorPreventLateQuorum', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [owner, proposer, voter1, voter2, voter3, voter4] = await ethers.getSigners(); - const receiver = await ethers.deployContract('CallReceiverMock'); - - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); - const mock = await ethers.deployContract('$GovernorPreventLateQuorumMock', [ - name, // name - votingDelay, // initialVotingDelay - votingPeriod, // initialVotingPeriod - 0n, // initialProposalThreshold - token, // tokenAddress - lateQuorumVoteExtension, - quorum, - ]); - - 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, receiver, token, mock, helper }; - }; - - describe(`using ${Token}`, function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - // initiate fresh proposal - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunction'), - value, - }, - ], - '', - ); - }); - - 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.quorum(0)).to.equal(quorum); - expect(await this.mock.lateQuorumVoteExtension()).to.equal(lateQuorumVoteExtension); - }); - - it('nominal workflow unaffected', async function () { - const txPropose = await this.helper.connect(this.proposer).propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - 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 this.mock.hasVoted(this.proposal.id, this.voter3)).to.be.true; - expect(await this.mock.hasVoted(this.proposal.id, this.voter4)).to.be.true; - - expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([ - ethers.parseEther('5'), // againstVotes - ethers.parseEther('17'), // forVotes - ethers.parseEther('2'), // abstainVotes - ]); - - const voteStart = (await time.clockFromReceipt[mode](txPropose)) + votingDelay; - const voteEnd = (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod; - expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(voteStart); - expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(voteEnd); - - await expect(txPropose) - .to.emit(this.mock, 'ProposalCreated') - .withArgs( - this.proposal.id, - this.proposer, - this.proposal.targets, - this.proposal.values, - this.proposal.signatures, - this.proposal.data, - voteStart, - voteEnd, - this.proposal.description, - ); - }); - - it('Delay is extended to prevent last minute take-over', async function () { - const txPropose = await this.helper.connect(this.proposer).propose(); - - // compute original schedule - const snapshotTimepoint = (await time.clockFromReceipt[mode](txPropose)) + votingDelay; - const deadlineTimepoint = (await time.clockFromReceipt[mode](txPropose)) + votingDelay + votingPeriod; - expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(snapshotTimepoint); - expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(deadlineTimepoint); - // wait for the last minute to vote - await this.helper.waitForDeadline(-1n); - const txVote = await this.helper.connect(this.voter2).vote({ support: VoteType.For }); - - // cannot execute yet - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Active); - - // compute new extended schedule - const extendedDeadline = (await time.clockFromReceipt[mode](txVote)) + lateQuorumVoteExtension; - expect(await this.mock.proposalSnapshot(this.proposal.id)).to.equal(snapshotTimepoint); - expect(await this.mock.proposalDeadline(this.proposal.id)).to.equal(extendedDeadline); - - // still possible to vote - await this.helper.connect(this.voter1).vote({ support: VoteType.Against }); - - await this.helper.waitForDeadline(); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Active); - await this.helper.waitForDeadline(1n); - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Defeated); - - // check extension event - await expect(txVote).to.emit(this.mock, 'ProposalExtended').withArgs(this.proposal.id, extendedDeadline); - }); - - describe('onlyGovernance updates', function () { - it('setLateQuorumVoteExtension is protected', async function () { - await expect(this.mock.connect(this.owner).setLateQuorumVoteExtension(0n)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('can setLateQuorumVoteExtension through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setLateQuorumVoteExtension', [0n]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()) - .to.emit(this.mock, 'LateQuorumVoteExtensionSet') - .withArgs(lateQuorumVoteExtension, 0n); - - expect(await this.mock.lateQuorumVoteExtension()).to.equal(0n); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorStorage.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorStorage.test.js deleted file mode 100644 index ef56fa5..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorStorage.test.js +++ /dev/null @@ -1,155 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); -const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); - -const { GovernorHelper, timelockSalt } = require('../../helpers/governance'); -const { VoteType } = require('../../helpers/enums'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const DEFAULT_ADMIN_ROLE = ethers.ZeroHash; -const PROPOSER_ROLE = ethers.id('PROPOSER_ROLE'); -const EXECUTOR_ROLE = ethers.id('EXECUTOR_ROLE'); -const CANCELLER_ROLE = ethers.id('CANCELLER_ROLE'); - -const name = 'OZ-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 delay = 3600n; - -describe('GovernorStorage', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [deployer, owner, proposer, voter1, voter2, voter3, voter4] = await ethers.getSigners(); - const receiver = await ethers.deployContract('CallReceiverMock'); - - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); - const timelock = await ethers.deployContract('TimelockController', [delay, [], [], deployer]); - const mock = await ethers.deployContract('$GovernorStorageMock', [ - name, - votingDelay, - votingPeriod, - 0n, - timelock, - token, - 0n, - ]); - - await owner.sendTransaction({ to: timelock, value }); - await token.$_mint(owner, tokenSupply); - await timelock.grantRole(PROPOSER_ROLE, mock); - await timelock.grantRole(PROPOSER_ROLE, owner); - await timelock.grantRole(CANCELLER_ROLE, mock); - await timelock.grantRole(CANCELLER_ROLE, owner); - await timelock.grantRole(EXECUTOR_ROLE, ethers.ZeroAddress); - await timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer); - - 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 { deployer, owner, proposer, voter1, voter2, voter3, voter4, receiver, token, timelock, mock, helper }; - }; - - describe(`using ${Token}`, function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - // initiate fresh proposal - this.proposal = this.helper.setProposal( - [ - { - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('mockFunction'), - value, - }, - ], - '', - ); - this.proposal.timelockid = await this.timelock.hashOperationBatch( - ...this.proposal.shortProposal.slice(0, 3), - ethers.ZeroHash, - timelockSalt(this.mock.target, this.proposal.shortProposal[3]), - ); - }); - - describe('proposal indexing', function () { - it('before propose', async function () { - expect(await this.mock.proposalCount()).to.equal(0n); - - await expect(this.mock.proposalDetailsAt(0n)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - - await expect(this.mock.proposalDetails(this.proposal.id)) - .to.be.revertedWithCustomError(this.mock, 'GovernorNonexistentProposal') - .withArgs(this.proposal.id); - }); - - it('after propose', async function () { - await this.helper.propose(); - - expect(await this.mock.proposalCount()).to.equal(1n); - - expect(await this.mock.proposalDetailsAt(0n)).to.deep.equal([ - this.proposal.id, - this.proposal.targets, - this.proposal.values, - this.proposal.data, - this.proposal.descriptionHash, - ]); - - expect(await this.mock.proposalDetails(this.proposal.id)).to.deep.equal([ - this.proposal.targets, - this.proposal.values, - this.proposal.data, - this.proposal.descriptionHash, - ]); - }); - }); - - it('queue and execute by id', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - 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 expect(this.mock.queue(this.proposal.id)) - .to.emit(this.mock, 'ProposalQueued') - .withArgs(this.proposal.id, anyValue) - .to.emit(this.timelock, 'CallScheduled') - .withArgs(this.proposal.timelockid, ...Array(6).fill(anyValue)) - .to.emit(this.timelock, 'CallSalt') - .withArgs(this.proposal.timelockid, anyValue); - - await this.helper.waitForEta(); - - await expect(this.mock.execute(this.proposal.id)) - .to.emit(this.mock, 'ProposalExecuted') - .withArgs(this.proposal.id) - .to.emit(this.timelock, 'CallExecuted') - .withArgs(this.proposal.timelockid, ...Array(4).fill(anyValue)) - .to.emit(this.receiver, 'MockFunctionCalled'); - }); - - it('cancel by id', async function () { - await this.helper.connect(this.proposer).propose(); - await expect(this.mock.connect(this.proposer).cancel(this.proposal.id)) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockAccess.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockAccess.test.js deleted file mode 100644 index c3d3b32..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockAccess.test.js +++ /dev/null @@ -1,864 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); - -const { GovernorHelper } = require('../../helpers/governance'); -const { hashOperation } = require('../../helpers/access-manager'); -const { max } = require('../../helpers/math'); -const { selector } = require('../../helpers/methods'); -const { ProposalState, VoteType } = require('../../helpers/enums'); -const time = require('../../helpers/time'); - -function prepareOperation({ sender, target, value = 0n, data = '0x' }) { - return { - id: hashOperation(sender, target, data), - operation: { target, value, data }, - selector: data.slice(0, 10).padEnd(10, '0'), - }; -} - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const name = 'OZ-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'); - -describe('GovernorTimelockAccess', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [admin, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); - - const manager = await ethers.deployContract('$AccessManager', [admin]); - const receiver = await ethers.deployContract('$AccessManagedTarget', [manager]); - - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); - const mock = await ethers.deployContract('$GovernorTimelockAccessMock', [ - name, - votingDelay, - votingPeriod, - 0n, - manager, - 0n, - token, - 0n, - ]); - - await admin.sendTransaction({ to: mock, value }); - await token.$_mint(admin, tokenSupply); - - const helper = new GovernorHelper(mock, mode); - await helper.connect(admin).delegate({ token, to: voter1, value: ethers.parseEther('10') }); - await helper.connect(admin).delegate({ token, to: voter2, value: ethers.parseEther('7') }); - await helper.connect(admin).delegate({ token, to: voter3, value: ethers.parseEther('5') }); - await helper.connect(admin).delegate({ token, to: voter4, value: ethers.parseEther('2') }); - - return { admin, voter1, voter2, voter3, voter4, other, manager, receiver, token, mock, helper }; - }; - - describe(`using ${Token}`, function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - - // restricted proposal - this.restricted = prepareOperation({ - sender: this.mock.target, - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('fnRestricted'), - }); - - this.unrestricted = prepareOperation({ - sender: this.mock.target, - target: this.receiver.target, - data: this.receiver.interface.encodeFunctionData('fnUnrestricted'), - }); - - this.fallback = prepareOperation({ - sender: this.mock.target, - target: this.receiver.target, - data: '0x1234', - }); - }); - - it('accepts ether transfers', async function () { - await this.admin.sendTransaction({ to: this.mock, value: 1n }); - }); - - it('post 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.quorum(0n)).to.equal(0n); - - expect(await this.mock.accessManager()).to.equal(this.manager); - }); - - it('sets base delay (seconds)', async function () { - const baseDelay = time.duration.hours(10n); - - // Only through governance - await expect(this.mock.connect(this.voter1).setBaseDelaySeconds(baseDelay)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.voter1); - - this.proposal = await this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setBaseDelaySeconds', [baseDelay]), - }, - ], - 'descr', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()).to.emit(this.mock, 'BaseDelaySet').withArgs(0n, baseDelay); - - expect(await this.mock.baseDelaySeconds()).to.equal(baseDelay); - }); - - it('sets access manager ignored', async function () { - const selectors = ['0x12345678', '0x87654321', '0xabcdef01']; - - // Only through governance - await expect(this.mock.connect(this.voter1).setAccessManagerIgnored(this.other, selectors, true)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.voter1); - - // Ignore - await this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setAccessManagerIgnored', [ - this.other.address, - selectors, - true, - ]), - }, - ], - 'descr', - ); - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - const ignoreReceipt = this.helper.execute(); - for (const selector of selectors) { - await expect(ignoreReceipt) - .to.emit(this.mock, 'AccessManagerIgnoredSet') - .withArgs(this.other, selector, true); - expect(await this.mock.isAccessManagerIgnored(this.other, selector)).to.be.true; - } - - // Unignore - await this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setAccessManagerIgnored', [ - this.other.address, - selectors, - false, - ]), - }, - ], - 'descr', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - const unignoreReceipt = this.helper.execute(); - for (const selector of selectors) { - await expect(unignoreReceipt) - .to.emit(this.mock, 'AccessManagerIgnoredSet') - .withArgs(this.other, selector, false); - expect(await this.mock.isAccessManagerIgnored(this.other, selector)).to.be.false; - } - }); - - it('sets access manager ignored when target is the governor', async function () { - const selectors = ['0x12345678', '0x87654321', '0xabcdef01']; - - await this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('setAccessManagerIgnored', [ - this.mock.target, - selectors, - true, - ]), - }, - ], - 'descr', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - const tx = this.helper.execute(); - for (const selector of selectors) { - await expect(tx).to.emit(this.mock, 'AccessManagerIgnoredSet').withArgs(this.mock, selector, true); - expect(await this.mock.isAccessManagerIgnored(this.mock, selector)).to.be.true; - } - }); - - it('does not need to queue proposals with no delay', async function () { - const roleId = 1n; - const executionDelay = 0n; - const baseDelay = 0n; - - // Set execution delay - await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay); - - // Set base delay - await this.mock.$_setBaseDelaySeconds(baseDelay); - - await this.helper.setProposal([this.restricted.operation], 'descr'); - await this.helper.propose(); - expect(await this.mock.proposalNeedsQueuing(this.helper.currentProposal.id)).to.be.false; - }); - - it('needs to queue proposals with any delay', async function () { - const roleId = 1n; - const delays = [ - [time.duration.hours(1n), time.duration.hours(2n)], - [time.duration.hours(2n), time.duration.hours(1n)], - ]; - - for (const [executionDelay, baseDelay] of delays) { - // Set execution delay - await this.manager - .connect(this.admin) - .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay); - - // Set base delay - await this.mock.$_setBaseDelaySeconds(baseDelay); - - await this.helper.setProposal( - [this.restricted.operation], - `executionDelay=${executionDelay.toString()}}baseDelay=${baseDelay.toString()}}`, - ); - await this.helper.propose(); - expect(await this.mock.proposalNeedsQueuing(this.helper.currentProposal.id)).to.be.true; - } - }); - - describe('execution plan', function () { - it('returns plan for delayed operations', async function () { - const roleId = 1n; - const delays = [ - [time.duration.hours(1n), time.duration.hours(2n)], - [time.duration.hours(2n), time.duration.hours(1n)], - ]; - - for (const [executionDelay, baseDelay] of delays) { - // Set execution delay - await this.manager - .connect(this.admin) - .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay); - - // Set base delay - await this.mock.$_setBaseDelaySeconds(baseDelay); - - this.proposal = await this.helper.setProposal( - [this.restricted.operation], - `executionDelay=${executionDelay.toString()}}baseDelay=${baseDelay.toString()}}`, - ); - await this.helper.propose(); - - expect(await this.mock.proposalExecutionPlan(this.proposal.id)).to.deep.equal([ - max(baseDelay, executionDelay), - [true], - [true], - ]); - } - }); - - it('returns plan for not delayed operations', async function () { - const roleId = 1n; - const executionDelay = 0n; - const baseDelay = 0n; - - // Set execution delay - await this.manager - .connect(this.admin) - .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay); - - // Set base delay - await this.mock.$_setBaseDelaySeconds(baseDelay); - - this.proposal = await this.helper.setProposal([this.restricted.operation], `descr`); - await this.helper.propose(); - - expect(await this.mock.proposalExecutionPlan(this.proposal.id)).to.deep.equal([0n, [true], [false]]); - }); - - it('returns plan for an operation ignoring the manager', async function () { - await this.mock.$_setAccessManagerIgnored(this.receiver, this.restricted.selector, true); - - const roleId = 1n; - const delays = [ - [time.duration.hours(1n), time.duration.hours(2n)], - [time.duration.hours(2n), time.duration.hours(1n)], - ]; - - for (const [executionDelay, baseDelay] of delays) { - // Set execution delay - await this.manager - .connect(this.admin) - .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay); - - // Set base delay - await this.mock.$_setBaseDelaySeconds(baseDelay); - - this.proposal = await this.helper.setProposal( - [this.restricted.operation], - `executionDelay=${executionDelay.toString()}}baseDelay=${baseDelay.toString()}}`, - ); - await this.helper.propose(); - - expect(await this.mock.proposalExecutionPlan(this.proposal.id)).to.deep.equal([ - baseDelay, - [false], - [false], - ]); - } - }); - }); - - describe('base delay only', function () { - for (const [delay, queue] of [ - [0, true], - [0, false], - [1000, true], - ]) { - it(`delay ${delay}, ${queue ? 'with' : 'without'} queuing`, async function () { - await this.mock.$_setBaseDelaySeconds(delay); - - this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr'); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - if (await this.mock.proposalNeedsQueuing(this.proposal.id)) { - expect(await this.helper.queue()) - .to.emit(this.mock, 'ProposalQueued') - .withArgs(this.proposal.id, anyValue); - } - if (delay > 0) { - await this.helper.waitForEta(); - } - await expect(this.helper.execute()) - .to.emit(this.mock, 'ProposalExecuted') - .withArgs(this.proposal.id) - .to.emit(this.receiver, 'CalledUnrestricted'); - }); - } - }); - - it('reverts when an operation is executed before eta', async function () { - const delay = time.duration.hours(2n); - await this.mock.$_setBaseDelaySeconds(delay); - - this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr'); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnmetDelay') - .withArgs(this.proposal.id, await this.mock.proposalEta(this.proposal.id)); - }); - - it('reverts with a proposal including multiple operations but one of those was cancelled in the manager', async function () { - const delay = time.duration.hours(2n); - const roleId = 1n; - - await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, delay); - - // Set proposals - const original = new GovernorHelper(this.mock, mode); - await original.setProposal([this.restricted.operation, this.unrestricted.operation], 'descr'); - - // Go through all the governance process - await original.propose(); - await original.waitForSnapshot(); - await original.connect(this.voter1).vote({ support: VoteType.For }); - await original.waitForDeadline(); - await original.queue(); - await original.waitForEta(); - - // Suddenly cancel one of the proposed operations in the manager - await this.manager - .connect(this.admin) - .cancel(this.mock, this.restricted.operation.target, this.restricted.operation.data); - - // Reschedule the same operation in a different proposal to avoid "AccessManagerNotScheduled" error - const rescheduled = new GovernorHelper(this.mock, mode); - await rescheduled.setProposal([this.restricted.operation], 'descr'); - await rescheduled.propose(); - await rescheduled.waitForSnapshot(); - await rescheduled.connect(this.voter1).vote({ support: VoteType.For }); - await rescheduled.waitForDeadline(); - await rescheduled.queue(); // This will schedule it again in the manager - await rescheduled.waitForEta(); - - // Attempt to execute - await expect(original.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorMismatchedNonce') - .withArgs(original.currentProposal.id, 1, 2); - }); - - it('single operation with access manager delay', async function () { - const delay = 1000n; - const roleId = 1n; - - await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, delay); - - this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr'); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - const txQueue = await this.helper.queue(); - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); - - await expect(txQueue) - .to.emit(this.mock, 'ProposalQueued') - .withArgs(this.proposal.id, anyValue) - .to.emit(this.manager, 'OperationScheduled') - .withArgs( - this.restricted.id, - 1n, - (await time.clockFromReceipt.timestamp(txQueue)) + delay, - this.mock.target, - this.restricted.operation.target, - this.restricted.operation.data, - ); - - await expect(txExecute) - .to.emit(this.mock, 'ProposalExecuted') - .withArgs(this.proposal.id) - .to.emit(this.manager, 'OperationExecuted') - .withArgs(this.restricted.id, 1n) - .to.emit(this.receiver, 'CalledRestricted'); - }); - - it('bundle of varied operations', async function () { - const managerDelay = 1000n; - const roleId = 1n; - const baseDelay = managerDelay * 2n; - - await this.mock.$_setBaseDelaySeconds(baseDelay); - - await this.manager.connect(this.admin).setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, managerDelay); - - this.proposal = await this.helper.setProposal( - [this.restricted.operation, this.unrestricted.operation, this.fallback.operation], - 'descr', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - const txQueue = await this.helper.queue(); - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); - - await expect(txQueue) - .to.emit(this.mock, 'ProposalQueued') - .withArgs(this.proposal.id, anyValue) - .to.emit(this.manager, 'OperationScheduled') - .withArgs( - this.restricted.id, - 1n, - (await time.clockFromReceipt.timestamp(txQueue)) + baseDelay, - this.mock.target, - this.restricted.operation.target, - this.restricted.operation.data, - ); - - await expect(txExecute) - .to.emit(this.mock, 'ProposalExecuted') - .withArgs(this.proposal.id) - .to.emit(this.manager, 'OperationExecuted') - .withArgs(this.restricted.id, 1n) - .to.emit(this.receiver, 'CalledRestricted') - .to.emit(this.receiver, 'CalledUnrestricted') - .to.emit(this.receiver, 'CalledFallback'); - }); - - describe('cancel', function () { - const delay = 1000n; - const roleId = 1n; - - beforeEach(async function () { - await this.manager - .connect(this.admin) - .setTargetFunctionRole(this.receiver, [this.restricted.selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, delay); - }); - - it('cancels restricted with delay after queue (internal)', async function () { - this.proposal = await this.helper.setProposal([this.restricted.operation], 'descr'); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - await expect(this.helper.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id) - .to.emit(this.manager, 'OperationCanceled') - .withArgs(this.restricted.id, 1n); - - await this.helper.waitForEta(); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('cancels restricted with queueing if the same operation is part of a more recent proposal (internal)', async function () { - // Set proposals - const original = new GovernorHelper(this.mock, mode); - await original.setProposal([this.restricted.operation], 'descr'); - - // Go through all the governance process - await original.propose(); - await original.waitForSnapshot(); - await original.connect(this.voter1).vote({ support: VoteType.For }); - await original.waitForDeadline(); - await original.queue(); - - // Cancel the operation in the manager - await this.manager - .connect(this.admin) - .cancel(this.mock, this.restricted.operation.target, this.restricted.operation.data); - - // Another proposal is added with the same operation - const rescheduled = new GovernorHelper(this.mock, mode); - await rescheduled.setProposal([this.restricted.operation], 'another descr'); - - // Queue the new proposal - await rescheduled.propose(); - await rescheduled.waitForSnapshot(); - await rescheduled.connect(this.voter1).vote({ support: VoteType.For }); - await rescheduled.waitForDeadline(); - await rescheduled.queue(); // This will schedule it again in the manager - - // Cancel - const eta = await this.mock.proposalEta(rescheduled.currentProposal.id); - - await expect(original.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(original.currentProposal.id); - - await time.clock.timestamp().then(clock => time.increaseTo.timestamp(max(clock + 1n, eta))); - - await expect(original.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - original.currentProposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('cancels unrestricted with queueing (internal)', async function () { - this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr'); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - const eta = await this.mock.proposalEta(this.proposal.id); - - await expect(this.helper.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id); - - await time.clock.timestamp().then(clock => time.increaseTo.timestamp(max(clock + 1n, eta))); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('cancels unrestricted without queueing (internal)', async function () { - this.proposal = await this.helper.setProposal([this.unrestricted.operation], 'descr'); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('cancels calls already canceled by guardian', async function () { - const operationA = { target: this.receiver.target, data: this.restricted.selector + '00' }; - const operationB = { target: this.receiver.target, data: this.restricted.selector + '01' }; - const operationC = { target: this.receiver.target, data: this.restricted.selector + '02' }; - const operationAId = hashOperation(this.mock.target, operationA.target, operationA.data); - const operationBId = hashOperation(this.mock.target, operationB.target, operationB.data); - - const proposal1 = new GovernorHelper(this.mock, mode); - const proposal2 = new GovernorHelper(this.mock, mode); - proposal1.setProposal([operationA, operationB], 'proposal A+B'); - proposal2.setProposal([operationA, operationC], 'proposal A+C'); - - for (const p of [proposal1, proposal2]) { - await p.propose(); - await p.waitForSnapshot(); - await p.connect(this.voter1).vote({ support: VoteType.For }); - await p.waitForDeadline(); - } - - // Can queue the first proposal - await proposal1.queue(); - - // Cannot queue the second proposal: operation A already scheduled with delay - await expect(proposal2.queue()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerAlreadyScheduled') - .withArgs(operationAId); - - // Admin cancels operation B on the manager - await this.manager.connect(this.admin).cancel(this.mock, operationB.target, operationB.data); - - // Still cannot queue the second proposal: operation A already scheduled with delay - await expect(proposal2.queue()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerAlreadyScheduled') - .withArgs(operationAId); - - await proposal1.waitForEta(); - - // Cannot execute first proposal: operation B has been canceled - await expect(proposal1.execute()) - .to.be.revertedWithCustomError(this.manager, 'AccessManagerNotScheduled') - .withArgs(operationBId); - - // Cancel the first proposal to release operation A - await proposal1.cancel('internal'); - - // can finally queue the second proposal - await proposal2.queue(); - - await proposal2.waitForEta(); - - // Can execute second proposal - await proposal2.execute(); - }); - }); - - describe('ignore AccessManager', function () { - it('defaults', async function () { - expect(await this.mock.isAccessManagerIgnored(this.receiver, this.restricted.selector)).to.be.false; - expect(await this.mock.isAccessManagerIgnored(this.mock, '0x12341234')).to.be.true; - }); - - it('internal setter', async function () { - await expect(this.mock.$_setAccessManagerIgnored(this.receiver, this.restricted.selector, true)) - .to.emit(this.mock, 'AccessManagerIgnoredSet') - .withArgs(this.receiver, this.restricted.selector, true); - - expect(await this.mock.isAccessManagerIgnored(this.receiver, this.restricted.selector)).to.be.true; - - await expect(this.mock.$_setAccessManagerIgnored(this.mock, '0x12341234', false)) - .to.emit(this.mock, 'AccessManagerIgnoredSet') - .withArgs(this.mock, '0x12341234', false); - - expect(await this.mock.isAccessManagerIgnored(this.mock, '0x12341234')).to.be.false; - }); - - it('external setter', async function () { - const setAccessManagerIgnored = (...args) => - this.mock.interface.encodeFunctionData('setAccessManagerIgnored', args); - - await this.helper.setProposal( - [ - { - target: this.mock.target, - data: setAccessManagerIgnored( - this.receiver.target, - [this.restricted.selector, this.unrestricted.selector], - true, - ), - }, - { - target: this.mock.target, - data: setAccessManagerIgnored(this.mock.target, ['0x12341234', '0x67896789'], false), - }, - ], - 'descr', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()).to.emit(this.mock, 'AccessManagerIgnoredSet'); - - expect(await this.mock.isAccessManagerIgnored(this.receiver, this.restricted.selector)).to.be.true; - expect(await this.mock.isAccessManagerIgnored(this.receiver, this.unrestricted.selector)).to.be.true; - expect(await this.mock.isAccessManagerIgnored(this.mock, '0x12341234')).to.be.false; - expect(await this.mock.isAccessManagerIgnored(this.mock, '0x67896789')).to.be.false; - }); - - it('locked function', async function () { - const setAccessManagerIgnored = selector('setAccessManagerIgnored(address,bytes4[],bool)'); - - await expect( - this.mock.$_setAccessManagerIgnored(this.mock, setAccessManagerIgnored, true), - ).to.be.revertedWithCustomError(this.mock, 'GovernorLockedIgnore'); - - await this.mock.$_setAccessManagerIgnored(this.receiver, setAccessManagerIgnored, true); - }); - - it('ignores access manager', async function () { - const amount = 100n; - const target = this.token.target; - const data = this.token.interface.encodeFunctionData('transfer', [this.voter4.address, amount]); - const selector = data.slice(0, 10); - await this.token.$_mint(this.mock, amount); - - const roleId = 1n; - await this.manager.connect(this.admin).setTargetFunctionRole(target, [selector], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, 0); - - await this.helper.setProposal([{ target, data }], 'descr #1'); - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.manager, 0n, amount); - - await this.mock.$_setAccessManagerIgnored(target, selector, true); - - await this.helper.setProposal([{ target, data }], 'descr #2'); - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()).to.emit(this.token, 'Transfer').withArgs(this.mock, this.voter4, amount); - }); - }); - - describe('operating on an Ownable contract', function () { - const method = selector('$_checkOwner()'); - - beforeEach(async function () { - this.ownable = await ethers.deployContract('$Ownable', [this.manager]); - this.operation = { - target: this.ownable.target, - data: this.ownable.interface.encodeFunctionData('$_checkOwner'), - }; - }); - - it('succeeds with delay', async function () { - const roleId = 1n; - const executionDelay = time.duration.hours(2n); - const baseDelay = time.duration.hours(1n); - - // Set execution delay - await this.manager.connect(this.admin).setTargetFunctionRole(this.ownable, [method], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay); - - // Set base delay - await this.mock.$_setBaseDelaySeconds(baseDelay); - - await this.helper.setProposal([this.operation], `descr`); - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - await this.helper.execute(); // Don't revert - }); - - it('succeeds without delay', async function () { - const roleId = 1n; - const executionDelay = 0n; - const baseDelay = 0n; - - // Set execution delay - await this.manager.connect(this.admin).setTargetFunctionRole(this.ownable, [method], roleId); - await this.manager.connect(this.admin).grantRole(roleId, this.mock, executionDelay); - - // Set base delay - await this.mock.$_setBaseDelaySeconds(baseDelay); - - await this.helper.setProposal([this.operation], `descr`); - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.execute(); // Don't revert - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockCompound.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockCompound.test.js deleted file mode 100644 index 545bf35..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockCompound.test.js +++ /dev/null @@ -1,448 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); - -const { GovernorHelper } = require('../../helpers/governance'); -const { ProposalState, VoteType } = require('../../helpers/enums'); -const time = require('../../helpers/time'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const name = 'OZ-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 defaultDelay = time.duration.days(2n); - -describe('GovernorTimelockCompound', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [deployer, owner, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); - const receiver = await ethers.deployContract('CallReceiverMock'); - - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); - const predictGovernor = await deployer - .getNonce() - .then(nonce => ethers.getCreateAddress({ from: deployer.address, nonce: nonce + 1 })); - const timelock = await ethers.deployContract('CompTimelock', [predictGovernor, defaultDelay]); - const mock = await ethers.deployContract('$GovernorTimelockCompoundMock', [ - name, - votingDelay, - votingPeriod, - 0n, - timelock, - token, - 0n, - ]); - - await owner.sendTransaction({ to: timelock, 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 { deployer, owner, voter1, voter2, voter3, voter4, other, receiver, token, mock, timelock, 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("doesn't accept ether transfers", async function () { - await expect(this.owner.sendTransaction({ to: this.mock, value: 1n })).to.be.revertedWithCustomError( - this.mock, - 'GovernorDisabledDeposit', - ); - }); - - it('post 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.quorum(0n)).to.equal(0n); - - expect(await this.mock.timelock()).to.equal(this.timelock); - expect(await this.timelock.admin()).to.equal(this.mock); - }); - - it('nominal', async function () { - expect(await this.mock.proposalEta(this.proposal.id)).to.equal(0n); - expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true; - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - 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(); - const txQueue = await this.helper.queue(); - - const eta = (await time.clockFromReceipt.timestamp(txQueue)) + defaultDelay; - expect(await this.mock.proposalEta(this.proposal.id)).to.equal(eta); - expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true; - - await this.helper.waitForEta(); - const txExecute = await this.helper.execute(); - - await expect(txQueue) - .to.emit(this.mock, 'ProposalQueued') - .withArgs(this.proposal.id, eta) - .to.emit(this.timelock, 'QueueTransaction') - .withArgs(...Array(5).fill(anyValue), eta); - - await expect(txExecute) - .to.emit(this.mock, 'ProposalExecuted') - .withArgs(this.proposal.id) - .to.emit(this.timelock, 'ExecuteTransaction') - .withArgs(...Array(5).fill(anyValue), eta) - .to.emit(this.receiver, 'MockFunctionCalled'); - }); - - describe('should revert', function () { - describe('on queue', function () { - it('if already queued', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await expect(this.helper.queue()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Queued, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]), - ); - }); - - it('if proposal contains duplicate calls', async function () { - const action = { - target: this.token.target, - data: this.token.interface.encodeFunctionData('approve', [this.receiver.target, ethers.MaxUint256]), - }; - const { id } = this.helper.setProposal([action, action], ''); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await expect(this.helper.queue()) - .to.be.revertedWithCustomError(this.mock, 'GovernorAlreadyQueuedProposal') - .withArgs(id); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorNotQueuedProposal') - .withArgs(id); - }); - }); - - describe('on execute', function () { - it('if not queued', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(1n); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Succeeded); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorNotQueuedProposal') - .withArgs(this.proposal.id); - }); - - it('if too early', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Queued); - - await expect(this.helper.execute()).to.be.rejectedWith( - "Timelock::executeTransaction: Transaction hasn't surpassed time lock", - ); - }); - - it('if too late', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(time.duration.days(30)); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Expired); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Expired, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('if already executed', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - await this.helper.execute(); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Executed, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - }); - - describe('on safe receive', function () { - describe('ERC721', function () { - const tokenId = 1n; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC721', ['Non Fungible Token', 'NFT']); - await this.token.$_mint(this.owner, tokenId); - }); - - it("can't receive an ERC721 safeTransfer", async function () { - await expect( - this.token.connect(this.owner).safeTransferFrom(this.owner, this.mock, tokenId), - ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit'); - }); - }); - - describe('ERC1155', function () { - const tokenIds = { - 1: 1000n, - 2: 2000n, - 3: 3000n, - }; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']); - await this.token.$_mintBatch(this.owner, Object.keys(tokenIds), Object.values(tokenIds), '0x'); - }); - - it("can't receive ERC1155 safeTransfer", async function () { - await expect( - this.token.connect(this.owner).safeTransferFrom( - this.owner, - this.mock, - ...Object.entries(tokenIds)[0], // id + amount - '0x', - ), - ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit'); - }); - - it("can't receive ERC1155 safeBatchTransfer", async function () { - await expect( - this.token - .connect(this.owner) - .safeBatchTransferFrom(this.owner, this.mock, Object.keys(tokenIds), Object.values(tokenIds), '0x'), - ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit'); - }); - }); - }); - }); - - describe('cancel', function () { - it('cancel before queue prevents scheduling', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - - await expect(this.helper.queue()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]), - ); - }); - - it('cancel after queue prevents executing', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - await expect(this.helper.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - }); - - describe('onlyGovernance', function () { - describe('relay', function () { - beforeEach(async function () { - await this.token.$_mint(this.mock, 1); - }); - - it('is protected', async function () { - await expect( - this.mock - .connect(this.owner) - .relay(this.token, 0, this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n])), - ) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('can be executed through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('relay', [ - this.token.target, - 0n, - this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n]), - ]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - const txExecute = this.helper.execute(); - - await expect(txExecute).to.changeTokenBalances(this.token, [this.mock, this.other], [-1n, 1n]); - - await expect(txExecute).to.emit(this.token, 'Transfer').withArgs(this.mock, this.other, 1n); - }); - }); - - describe('updateTimelock', function () { - beforeEach(async function () { - this.newTimelock = await ethers.deployContract('CompTimelock', [this.mock, time.duration.days(7n)]); - }); - - it('is protected', async function () { - await expect(this.mock.connect(this.owner).updateTimelock(this.newTimelock)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('can be executed through governance to', async function () { - this.helper.setProposal( - [ - { - target: this.timelock.target, - data: this.timelock.interface.encodeFunctionData('setPendingAdmin', [this.owner.address]), - }, - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('updateTimelock', [this.newTimelock.target]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - await expect(this.helper.execute()) - .to.emit(this.mock, 'TimelockChange') - .withArgs(this.timelock, this.newTimelock); - - expect(await this.mock.timelock()).to.equal(this.newTimelock); - }); - }); - - it('can transfer timelock to new governor', async function () { - const newGovernor = await ethers.deployContract('$GovernorTimelockCompoundMock', [ - name, - 8n, - 32n, - 0n, - this.timelock, - this.token, - 0n, - ]); - - this.helper.setProposal( - [ - { - target: this.timelock.target, - data: this.timelock.interface.encodeFunctionData('setPendingAdmin', [newGovernor.target]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - await expect(this.helper.execute()).to.emit(this.timelock, 'NewPendingAdmin').withArgs(newGovernor); - - await newGovernor.__acceptAdmin(); - expect(await this.timelock.admin()).to.equal(newGovernor); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockControl.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockControl.test.js deleted file mode 100644 index c1156a5..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorTimelockControl.test.js +++ /dev/null @@ -1,504 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); -const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); - -const { GovernorHelper, timelockSalt } = require('../../helpers/governance'); -const { OperationState, ProposalState, VoteType } = require('../../helpers/enums'); -const time = require('../../helpers/time'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const DEFAULT_ADMIN_ROLE = ethers.ZeroHash; -const PROPOSER_ROLE = ethers.id('PROPOSER_ROLE'); -const EXECUTOR_ROLE = ethers.id('EXECUTOR_ROLE'); -const CANCELLER_ROLE = ethers.id('CANCELLER_ROLE'); - -const name = 'OZ-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 delay = time.duration.hours(1n); - -describe('GovernorTimelockControl', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [deployer, owner, voter1, voter2, voter3, voter4, other] = await ethers.getSigners(); - const receiver = await ethers.deployContract('CallReceiverMock'); - - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); - const timelock = await ethers.deployContract('TimelockController', [delay, [], [], deployer]); - const mock = await ethers.deployContract('$GovernorTimelockControlMock', [ - name, - votingDelay, - votingPeriod, - 0n, - timelock, - token, - 0n, - ]); - - await owner.sendTransaction({ to: timelock, value }); - await token.$_mint(owner, tokenSupply); - await timelock.grantRole(PROPOSER_ROLE, mock); - await timelock.grantRole(PROPOSER_ROLE, owner); - await timelock.grantRole(CANCELLER_ROLE, mock); - await timelock.grantRole(CANCELLER_ROLE, owner); - await timelock.grantRole(EXECUTOR_ROLE, ethers.ZeroAddress); - await timelock.revokeRole(DEFAULT_ADMIN_ROLE, deployer); - - 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 { deployer, owner, voter1, voter2, voter3, voter4, other, receiver, token, mock, timelock, 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'), - }, - ], - '', - ); - - this.proposal.timelockid = await this.timelock.hashOperationBatch( - ...this.proposal.shortProposal.slice(0, 3), - ethers.ZeroHash, - timelockSalt(this.mock.target, this.proposal.shortProposal[3]), - ); - }); - - it("doesn't accept ether transfers", async function () { - await expect(this.owner.sendTransaction({ to: this.mock, value: 1n })).to.be.revertedWithCustomError( - this.mock, - 'GovernorDisabledDeposit', - ); - }); - - it('post 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.quorum(0n)).to.equal(0n); - - expect(await this.mock.timelock()).to.equal(this.timelock); - }); - - it('nominal', async function () { - expect(await this.mock.proposalEta(this.proposal.id)).to.equal(0n); - expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true; - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - 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(); - - expect(await this.mock.proposalNeedsQueuing(this.proposal.id)).to.be.true; - const txQueue = await this.helper.queue(); - - const eta = (await time.clockFromReceipt.timestamp(txQueue)) + delay; - expect(await this.mock.proposalEta(this.proposal.id)).to.equal(eta); - await this.helper.waitForEta(); - - const txExecute = this.helper.execute(); - - await expect(txQueue) - .to.emit(this.mock, 'ProposalQueued') - .withArgs(this.proposal.id, anyValue) - .to.emit(this.timelock, 'CallScheduled') - .withArgs(this.proposal.timelockid, ...Array(6).fill(anyValue)) - .to.emit(this.timelock, 'CallSalt') - .withArgs(this.proposal.timelockid, anyValue); - - await expect(txExecute) - .to.emit(this.mock, 'ProposalExecuted') - .withArgs(this.proposal.id) - .to.emit(this.timelock, 'CallExecuted') - .withArgs(this.proposal.timelockid, ...Array(4).fill(anyValue)) - .to.emit(this.receiver, 'MockFunctionCalled'); - }); - - describe('should revert', function () { - describe('on queue', function () { - it('if already queued', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await expect(this.helper.queue()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Queued, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]), - ); - }); - }); - - describe('on execute', function () { - it('if not queued', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(1n); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Succeeded); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.timelock, 'TimelockUnexpectedOperationState') - .withArgs(this.proposal.timelockid, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - it('if too early', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Queued); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.timelock, 'TimelockUnexpectedOperationState') - .withArgs(this.proposal.timelockid, GovernorHelper.proposalStatesToBitMap(OperationState.Ready)); - }); - - it('if already executed', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - await this.helper.execute(); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Executed, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('if already executed by another proposer', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - await this.timelock.executeBatch( - ...this.proposal.shortProposal.slice(0, 3), - ethers.ZeroHash, - timelockSalt(this.mock.target, this.proposal.shortProposal[3]), - ); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Executed, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - }); - }); - - describe('cancel', function () { - it('cancel before queue prevents scheduling', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - - await expect(this.helper.queue()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded]), - ); - }); - - it('cancel after queue prevents executing', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - await expect(this.helper.cancel('internal')) - .to.emit(this.mock, 'ProposalCanceled') - .withArgs(this.proposal.id); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Canceled, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - it('cancel on timelock is reflected on governor', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Queued); - - await expect(this.timelock.connect(this.owner).cancel(this.proposal.timelockid)) - .to.emit(this.timelock, 'Cancelled') - .withArgs(this.proposal.timelockid); - - expect(await this.mock.state(this.proposal.id)).to.equal(ProposalState.Canceled); - }); - }); - - describe('onlyGovernance', function () { - describe('relay', function () { - beforeEach(async function () { - await this.token.$_mint(this.mock, 1); - }); - - it('is protected', async function () { - await expect( - this.mock - .connect(this.owner) - .relay(this.token, 0n, this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n])), - ) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('can be executed through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('relay', [ - this.token.target, - 0n, - this.token.interface.encodeFunctionData('transfer', [this.other.address, 1n]), - ]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - const txExecute = await this.helper.execute(); - - await expect(txExecute).to.changeTokenBalances(this.token, [this.mock, this.other], [-1n, 1n]); - - await expect(txExecute).to.emit(this.token, 'Transfer').withArgs(this.mock, this.other, 1n); - }); - - it('is payable and can transfer eth to EOA', async function () { - const t2g = 128n; // timelock to governor - const g2o = 100n; // governor to eoa (other) - - this.helper.setProposal( - [ - { - target: this.mock.target, - value: t2g, - data: this.mock.interface.encodeFunctionData('relay', [this.other.address, g2o, '0x']), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - await expect(this.helper.execute()).to.changeEtherBalances( - [this.timelock, this.mock, this.other], - [-t2g, t2g - g2o, g2o], - ); - }); - - it('protected against other proposers', async function () { - const call = [ - this.mock, - 0n, - this.mock.interface.encodeFunctionData('relay', [ethers.ZeroAddress, 0n, '0x']), - ethers.ZeroHash, - ethers.ZeroHash, - ]; - - await this.timelock.connect(this.owner).schedule(...call, delay); - - await time.increaseBy.timestamp(delay); - - // Error bubbled up from Governor - await expect(this.timelock.connect(this.owner).execute(...call)).to.be.revertedWithPanic( - PANIC_CODES.POP_ON_EMPTY_ARRAY, - ); - }); - }); - - describe('updateTimelock', function () { - beforeEach(async function () { - this.newTimelock = await ethers.deployContract('TimelockController', [ - delay, - [this.mock], - [this.mock], - ethers.ZeroAddress, - ]); - }); - - it('is protected', async function () { - await expect(this.mock.connect(this.owner).updateTimelock(this.newTimelock)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('can be executed through governance to', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('updateTimelock', [this.newTimelock.target]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - - await expect(this.helper.execute()) - .to.emit(this.mock, 'TimelockChange') - .withArgs(this.timelock, this.newTimelock); - - expect(await this.mock.timelock()).to.equal(this.newTimelock); - }); - }); - - describe('on safe receive', function () { - describe('ERC721', function () { - const tokenId = 1n; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC721', ['Non Fungible Token', 'NFT']); - await this.token.$_mint(this.owner, tokenId); - }); - - it("can't receive an ERC721 safeTransfer", async function () { - await expect( - this.token.connect(this.owner).safeTransferFrom(this.owner, this.mock, tokenId), - ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit'); - }); - }); - - describe('ERC1155', function () { - const tokenIds = { - 1: 1000n, - 2: 2000n, - 3: 3000n, - }; - - beforeEach(async function () { - this.token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']); - await this.token.$_mintBatch(this.owner, Object.keys(tokenIds), Object.values(tokenIds), '0x'); - }); - - it("can't receive ERC1155 safeTransfer", async function () { - await expect( - this.token.connect(this.owner).safeTransferFrom( - this.owner, - this.mock, - ...Object.entries(tokenIds)[0], // id + amount - '0x', - ), - ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit'); - }); - - it("can't receive ERC1155 safeBatchTransfer", async function () { - await expect( - this.token - .connect(this.owner) - .safeBatchTransferFrom(this.owner, this.mock, Object.keys(tokenIds), Object.values(tokenIds), '0x'), - ).to.be.revertedWithCustomError(this.mock, 'GovernorDisabledDeposit'); - }); - }); - }); - }); - - it('clear queue of pending governor calls', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('nonGovernanceFunction'), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.queue(); - await this.helper.waitForEta(); - await this.helper.execute(); - - // This path clears _governanceCall as part of the afterExecute call, - // but we have not way to check that the cleanup actually happened other - // then coverage reports. - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorVotesQuorumFraction.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorVotesQuorumFraction.test.js deleted file mode 100644 index db728f0..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorVotesQuorumFraction.test.js +++ /dev/null @@ -1,165 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); - -const { GovernorHelper } = require('../../helpers/governance'); -const { ProposalState, VoteType } = require('../../helpers/enums'); -const time = require('../../helpers/time'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const name = 'OZ-Governor'; -const version = '1'; -const tokenName = 'MockToken'; -const tokenSymbol = 'MTKN'; -const tokenSupply = ethers.parseEther('100'); -const ratio = 8n; // percents -const newRatio = 6n; // percents -const votingDelay = 4n; -const votingPeriod = 16n; -const value = ethers.parseEther('1'); - -describe('GovernorVotesQuorumFraction', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - const [owner, voter1, voter2, voter3, voter4] = await ethers.getSigners(); - - const receiver = await ethers.deployContract('CallReceiverMock'); - - const token = await ethers.deployContract(Token, [tokenName, tokenSymbol, version]); - const mock = await ethers.deployContract('$GovernorMock', [name, votingDelay, votingPeriod, 0n, token, ratio]); - - 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, voter1, voter2, voter3, voter4, 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.quorum(0)).to.equal(0n); - expect(await this.mock.quorumNumerator()).to.equal(ratio); - expect(await this.mock.quorumDenominator()).to.equal(100n); - expect(await time.clock[mode]().then(clock => this.mock.quorum(clock - 1n))).to.equal( - (tokenSupply * ratio) / 100n, - ); - }); - - it('quroum reached', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await this.helper.execute(); - }); - - it('quroum not reached', async function () { - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter2).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorUnexpectedProposalState') - .withArgs( - this.proposal.id, - ProposalState.Defeated, - GovernorHelper.proposalStatesToBitMap([ProposalState.Succeeded, ProposalState.Queued]), - ); - }); - - describe('onlyGovernance updates', function () { - it('updateQuorumNumerator is protected', async function () { - await expect(this.mock.connect(this.owner).updateQuorumNumerator(newRatio)) - .to.be.revertedWithCustomError(this.mock, 'GovernorOnlyExecutor') - .withArgs(this.owner); - }); - - it('can updateQuorumNumerator through governance', async function () { - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('updateQuorumNumerator', [newRatio]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - await expect(this.helper.execute()).to.emit(this.mock, 'QuorumNumeratorUpdated').withArgs(ratio, newRatio); - - expect(await this.mock.quorumNumerator()).to.equal(newRatio); - expect(await this.mock.quorumDenominator()).to.equal(100n); - - // it takes one block for the new quorum to take effect - expect(await time.clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1n))).to.equal( - (tokenSupply * ratio) / 100n, - ); - - await mine(); - - expect(await time.clock[mode]().then(blockNumber => this.mock.quorum(blockNumber - 1n))).to.equal( - (tokenSupply * newRatio) / 100n, - ); - }); - - it('cannot updateQuorumNumerator over the maximum', async function () { - const quorumNumerator = 101n; - this.helper.setProposal( - [ - { - target: this.mock.target, - data: this.mock.interface.encodeFunctionData('updateQuorumNumerator', [quorumNumerator]), - }, - ], - '', - ); - - await this.helper.propose(); - await this.helper.waitForSnapshot(); - await this.helper.connect(this.voter1).vote({ support: VoteType.For }); - await this.helper.waitForDeadline(); - - const quorumDenominator = await this.mock.quorumDenominator(); - - await expect(this.helper.execute()) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidQuorumFraction') - .withArgs(quorumNumerator, quorumDenominator); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/extensions/GovernorWithParams.test.js b/lib_openzeppelin_contracts/test/governance/extensions/GovernorWithParams.test.js deleted file mode 100644 index 37e15f5..0000000 --- a/lib_openzeppelin_contracts/test/governance/extensions/GovernorWithParams.test.js +++ /dev/null @@ -1,245 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { GovernorHelper } = require('../../helpers/governance'); -const { VoteType } = require('../../helpers/enums'); -const { getDomain, ExtendedBallot } = require('../../helpers/eip712'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const name = 'OZ-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 params = { - decoded: [42n, 'These are my params'], - encoded: ethers.AbiCoder.defaultAbiCoder().encode(['uint256', 'string'], [42n, 'These are my params']), -}; - -describe('GovernorWithParams', 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, version]); - const mock = await ethers.deployContract('$GovernorWithParamsMock', [name, token]); - - 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); - }); - - 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); - }); - - it('Voting with params is properly supported', async function () { - await this.helper.connect(this.proposer).propose(); - await this.helper.waitForSnapshot(); - - const weight = ethers.parseEther('7') - params.decoded[0]; - - await expect( - this.helper.connect(this.voter2).vote({ - support: VoteType.For, - reason: 'no particular reason', - params: params.encoded, - }), - ) - .to.emit(this.mock, 'CountParams') - .withArgs(...params.decoded) - .to.emit(this.mock, 'VoteCastWithParams') - .withArgs( - this.voter2.address, - this.proposal.id, - VoteType.For, - weight, - 'no particular reason', - params.encoded, - ); - - expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, weight, 0n]); - }); - - describe('voting by signature', function () { - it('supports EOA signatures', async function () { - await this.token.connect(this.voter2).delegate(this.other); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - - // Prepare vote - const weight = ethers.parseEther('7') - params.decoded[0]; - const nonce = await this.mock.nonces(this.other); - const data = { - proposalId: this.proposal.id, - support: VoteType.For, - voter: this.other.address, - nonce, - reason: 'no particular reason', - params: params.encoded, - signature: (contract, message) => - getDomain(contract).then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message)), - }; - - // Vote - await expect(this.helper.vote(data)) - .to.emit(this.mock, 'CountParams') - .withArgs(...params.decoded) - .to.emit(this.mock, 'VoteCastWithParams') - .withArgs(data.voter, data.proposalId, data.support, weight, data.reason, data.params); - - expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, weight, 0n]); - expect(await this.mock.nonces(this.other)).to.equal(nonce + 1n); - }); - - it('supports EIP-1271 signature signatures', async function () { - const wallet = await ethers.deployContract('ERC1271WalletMock', [this.other]); - await this.token.connect(this.voter2).delegate(wallet); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - - // Prepare vote - const weight = ethers.parseEther('7') - params.decoded[0]; - const nonce = await this.mock.nonces(this.other); - const data = { - proposalId: this.proposal.id, - support: VoteType.For, - voter: wallet.target, - nonce, - reason: 'no particular reason', - params: params.encoded, - signature: (contract, message) => - getDomain(contract).then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message)), - }; - - // Vote - await expect(this.helper.vote(data)) - .to.emit(this.mock, 'CountParams') - .withArgs(...params.decoded) - .to.emit(this.mock, 'VoteCastWithParams') - .withArgs(data.voter, data.proposalId, data.support, weight, data.reason, data.params); - - expect(await this.mock.proposalVotes(this.proposal.id)).to.deep.equal([0n, weight, 0n]); - expect(await this.mock.nonces(wallet)).to.equal(nonce + 1n); - }); - - it('reverts if signature does not match signer', async function () { - await this.token.connect(this.voter2).delegate(this.other); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - - // Prepare vote - const nonce = await this.mock.nonces(this.other); - const data = { - proposalId: this.proposal.id, - support: VoteType.For, - voter: this.other.address, - nonce, - reason: 'no particular reason', - params: params.encoded, - // tampered signature - signature: (contract, message) => - getDomain(contract) - .then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message)) - .then(signature => { - const tamperedSig = ethers.toBeArray(signature); - tamperedSig[42] ^= 0xff; - return ethers.hexlify(tamperedSig); - }), - }; - - // Vote - await expect(this.helper.vote(data)) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') - .withArgs(data.voter); - }); - - it('reverts if vote nonce is incorrect', async function () { - await this.token.connect(this.voter2).delegate(this.other); - - // Run proposal - await this.helper.propose(); - await this.helper.waitForSnapshot(); - - // Prepare vote - const nonce = await this.mock.nonces(this.other); - const data = { - proposalId: this.proposal.id, - support: VoteType.For, - voter: this.other.address, - nonce: nonce + 1n, - reason: 'no particular reason', - params: params.encoded, - signature: (contract, message) => - getDomain(contract).then(domain => this.other.signTypedData(domain, { ExtendedBallot }, message)), - }; - - // Vote - await expect(this.helper.vote(data)) - .to.be.revertedWithCustomError(this.mock, 'GovernorInvalidSignature') - .withArgs(data.voter); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/governance/utils/ERC6372.behavior.js b/lib_openzeppelin_contracts/test/governance/utils/ERC6372.behavior.js deleted file mode 100644 index abcae43..0000000 --- a/lib_openzeppelin_contracts/test/governance/utils/ERC6372.behavior.js +++ /dev/null @@ -1,25 +0,0 @@ -const { expect } = require('chai'); - -const time = require('../../helpers/time'); - -function shouldBehaveLikeERC6372(mode = 'blocknumber') { - describe('should implement ERC-6372', 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('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); - }); - }); -} - -module.exports = { - shouldBehaveLikeERC6372, -}; diff --git a/lib_openzeppelin_contracts/test/governance/utils/Votes.behavior.js b/lib_openzeppelin_contracts/test/governance/utils/Votes.behavior.js deleted file mode 100644 index 0997701..0000000 --- a/lib_openzeppelin_contracts/test/governance/utils/Votes.behavior.js +++ /dev/null @@ -1,325 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { mine } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getDomain, Delegation } = require('../../helpers/eip712'); -const time = require('../../helpers/time'); - -const { shouldBehaveLikeERC6372 } = require('./ERC6372.behavior'); - -function shouldBehaveLikeVotes(tokens, { mode = 'blocknumber', fungible = true }) { - beforeEach(async function () { - [this.delegator, this.delegatee, this.alice, this.bob, this.other] = this.accounts; - this.domain = await getDomain(this.votes); - }); - - shouldBehaveLikeERC6372(mode); - - const getWeight = token => (fungible ? token : 1n); - - describe('run votes workflow', function () { - it('initial nonce is 0', async function () { - expect(await this.votes.nonces(this.alice)).to.equal(0n); - }); - - describe('delegation with signature', function () { - const token = tokens[0]; - - it('delegation without tokens', async function () { - expect(await this.votes.delegates(this.alice)).to.equal(ethers.ZeroAddress); - - await expect(this.votes.connect(this.alice).delegate(this.alice)) - .to.emit(this.votes, 'DelegateChanged') - .withArgs(this.alice, ethers.ZeroAddress, this.alice) - .to.not.emit(this.votes, 'DelegateVotesChanged'); - - expect(await this.votes.delegates(this.alice)).to.equal(this.alice); - }); - - it('delegation with tokens', async function () { - await this.votes.$_mint(this.alice, token); - const weight = getWeight(token); - - expect(await this.votes.delegates(this.alice)).to.equal(ethers.ZeroAddress); - - const tx = await this.votes.connect(this.alice).delegate(this.alice); - const timepoint = await time.clockFromReceipt[mode](tx); - - await expect(tx) - .to.emit(this.votes, 'DelegateChanged') - .withArgs(this.alice, ethers.ZeroAddress, this.alice) - .to.emit(this.votes, 'DelegateVotesChanged') - .withArgs(this.alice, 0n, weight); - - expect(await this.votes.delegates(this.alice)).to.equal(this.alice); - expect(await this.votes.getVotes(this.alice)).to.equal(weight); - expect(await this.votes.getPastVotes(this.alice, timepoint - 1n)).to.equal(0n); - await mine(); - expect(await this.votes.getPastVotes(this.alice, timepoint)).to.equal(weight); - }); - - it('delegation update', async function () { - await this.votes.connect(this.alice).delegate(this.alice); - await this.votes.$_mint(this.alice, token); - const weight = getWeight(token); - - expect(await this.votes.delegates(this.alice)).to.equal(this.alice); - expect(await this.votes.getVotes(this.alice)).to.equal(weight); - expect(await this.votes.getVotes(this.bob)).to.equal(0); - - const tx = await this.votes.connect(this.alice).delegate(this.bob); - const timepoint = await time.clockFromReceipt[mode](tx); - - await expect(tx) - .to.emit(this.votes, 'DelegateChanged') - .withArgs(this.alice, this.alice, this.bob) - .to.emit(this.votes, 'DelegateVotesChanged') - .withArgs(this.alice, weight, 0) - .to.emit(this.votes, 'DelegateVotesChanged') - .withArgs(this.bob, 0, weight); - - expect(await this.votes.delegates(this.alice)).to.equal(this.bob); - expect(await this.votes.getVotes(this.alice)).to.equal(0n); - expect(await this.votes.getVotes(this.bob)).to.equal(weight); - - expect(await this.votes.getPastVotes(this.alice, timepoint - 1n)).to.equal(weight); - expect(await this.votes.getPastVotes(this.bob, timepoint - 1n)).to.equal(0n); - await mine(); - expect(await this.votes.getPastVotes(this.alice, timepoint)).to.equal(0n); - expect(await this.votes.getPastVotes(this.bob, timepoint)).to.equal(weight); - }); - - describe('with signature', function () { - const nonce = 0n; - - it('accept signed delegation', async function () { - await this.votes.$_mint(this.delegator, token); - const weight = getWeight(token); - - const { r, s, v } = await this.delegator - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.delegatee.address, - nonce, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - expect(await this.votes.delegates(this.delegator)).to.equal(ethers.ZeroAddress); - - const tx = await this.votes.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s); - const timepoint = await time.clockFromReceipt[mode](tx); - - await expect(tx) - .to.emit(this.votes, 'DelegateChanged') - .withArgs(this.delegator, ethers.ZeroAddress, this.delegatee) - .to.emit(this.votes, 'DelegateVotesChanged') - .withArgs(this.delegatee, 0, weight); - - expect(await this.votes.delegates(this.delegator.address)).to.equal(this.delegatee); - expect(await this.votes.getVotes(this.delegator.address)).to.equal(0n); - expect(await this.votes.getVotes(this.delegatee)).to.equal(weight); - expect(await this.votes.getPastVotes(this.delegatee, timepoint - 1n)).to.equal(0n); - await mine(); - expect(await this.votes.getPastVotes(this.delegatee, timepoint)).to.equal(weight); - }); - - it('rejects reused signature', async function () { - const { r, s, v } = await this.delegator - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.delegatee.address, - nonce, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - await this.votes.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s); - - await expect(this.votes.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s)) - .to.be.revertedWithCustomError(this.votes, 'InvalidAccountNonce') - .withArgs(this.delegator, nonce + 1n); - }); - - it('rejects bad delegatee', async function () { - const { r, s, v } = await this.delegator - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.delegatee.address, - nonce, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - const tx = await this.votes.delegateBySig(this.other, nonce, ethers.MaxUint256, v, r, s); - const receipt = await tx.wait(); - - const [delegateChanged] = receipt.logs.filter( - log => this.votes.interface.parseLog(log)?.name === 'DelegateChanged', - ); - const { args } = this.votes.interface.parseLog(delegateChanged); - expect(args.delegator).to.not.be.equal(this.delegator); - expect(args.fromDelegate).to.equal(ethers.ZeroAddress); - expect(args.toDelegate).to.equal(this.other); - }); - - it('rejects bad nonce', async function () { - const { r, s, v } = await this.delegator - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.delegatee.address, - nonce: nonce + 1n, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - await expect(this.votes.delegateBySig(this.delegatee, nonce + 1n, ethers.MaxUint256, v, r, s)) - .to.be.revertedWithCustomError(this.votes, 'InvalidAccountNonce') - .withArgs(this.delegator, 0); - }); - - it('rejects expired permit', async function () { - const expiry = (await time.clock.timestamp()) - 1n; - const { r, s, v } = await this.delegator - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.delegatee.address, - nonce, - expiry, - }, - ) - .then(ethers.Signature.from); - - await expect(this.votes.delegateBySig(this.delegatee, nonce, expiry, v, r, s)) - .to.be.revertedWithCustomError(this.votes, 'VotesExpiredSignature') - .withArgs(expiry); - }); - }); - }); - - describe('getPastTotalSupply', function () { - beforeEach(async function () { - await this.votes.connect(this.alice).delegate(this.alice); - }); - - it('reverts if block number >= current block', async function () { - const timepoint = 5e10; - const clock = await this.votes.clock(); - await expect(this.votes.getPastTotalSupply(timepoint)) - .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') - .withArgs(timepoint, clock); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.votes.getPastTotalSupply(0n)).to.equal(0n); - }); - - it('returns the correct checkpointed total supply', async function () { - const weight = tokens.map(token => getWeight(token)); - - // t0 = mint #0 - const t0 = await this.votes.$_mint(this.alice, tokens[0]); - await mine(); - // t1 = mint #1 - const t1 = await this.votes.$_mint(this.alice, tokens[1]); - await mine(); - // t2 = burn #1 - const t2 = await this.votes.$_burn(...(fungible ? [this.alice] : []), tokens[1]); - await mine(); - // t3 = mint #2 - const t3 = await this.votes.$_mint(this.alice, tokens[2]); - await mine(); - // t4 = burn #0 - const t4 = await this.votes.$_burn(...(fungible ? [this.alice] : []), tokens[0]); - await mine(); - // t5 = burn #2 - const t5 = await this.votes.$_burn(...(fungible ? [this.alice] : []), tokens[2]); - await mine(); - - t0.timepoint = await time.clockFromReceipt[mode](t0); - t1.timepoint = await time.clockFromReceipt[mode](t1); - t2.timepoint = await time.clockFromReceipt[mode](t2); - t3.timepoint = await time.clockFromReceipt[mode](t3); - t4.timepoint = await time.clockFromReceipt[mode](t4); - t5.timepoint = await time.clockFromReceipt[mode](t5); - - expect(await this.votes.getPastTotalSupply(t0.timepoint - 1n)).to.equal(0); - expect(await this.votes.getPastTotalSupply(t0.timepoint)).to.equal(weight[0]); - expect(await this.votes.getPastTotalSupply(t0.timepoint + 1n)).to.equal(weight[0]); - expect(await this.votes.getPastTotalSupply(t1.timepoint)).to.equal(weight[0] + weight[1]); - expect(await this.votes.getPastTotalSupply(t1.timepoint + 1n)).to.equal(weight[0] + weight[1]); - expect(await this.votes.getPastTotalSupply(t2.timepoint)).to.equal(weight[0]); - expect(await this.votes.getPastTotalSupply(t2.timepoint + 1n)).to.equal(weight[0]); - expect(await this.votes.getPastTotalSupply(t3.timepoint)).to.equal(weight[0] + weight[2]); - expect(await this.votes.getPastTotalSupply(t3.timepoint + 1n)).to.equal(weight[0] + weight[2]); - expect(await this.votes.getPastTotalSupply(t4.timepoint)).to.equal(weight[2]); - expect(await this.votes.getPastTotalSupply(t4.timepoint + 1n)).to.equal(weight[2]); - expect(await this.votes.getPastTotalSupply(t5.timepoint)).to.equal(0); - await expect(this.votes.getPastTotalSupply(t5.timepoint + 1n)) - .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') - .withArgs(t5.timepoint + 1n, t5.timepoint + 1n); - }); - }); - - // The following tests are an adaptation of - // https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js. - describe('Compound test suite', function () { - beforeEach(async function () { - await this.votes.$_mint(this.alice, tokens[0]); - await this.votes.$_mint(this.alice, tokens[1]); - await this.votes.$_mint(this.alice, tokens[2]); - }); - - describe('getPastVotes', function () { - it('reverts if block number >= current block', async function () { - const clock = await this.votes.clock(); - const timepoint = 5e10; // far in the future - await expect(this.votes.getPastVotes(this.bob, timepoint)) - .to.be.revertedWithCustomError(this.votes, 'ERC5805FutureLookup') - .withArgs(timepoint, clock); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.votes.getPastVotes(this.bob, 0n)).to.equal(0n); - }); - - it('returns the latest block if >= last checkpoint block', async function () { - const delegate = await this.votes.connect(this.alice).delegate(this.bob); - const timepoint = await time.clockFromReceipt[mode](delegate); - await mine(2); - - const latest = await this.votes.getVotes(this.bob); - expect(await this.votes.getPastVotes(this.bob, timepoint)).to.equal(latest); - expect(await this.votes.getPastVotes(this.bob, timepoint + 1n)).to.equal(latest); - }); - - it('returns zero if < first checkpoint block', async function () { - await mine(); - const delegate = await this.votes.connect(this.alice).delegate(this.bob); - const timepoint = await time.clockFromReceipt[mode](delegate); - await mine(2); - - expect(await this.votes.getPastVotes(this.bob, timepoint - 1n)).to.equal(0n); - }); - }); - }); - }); -} - -module.exports = { - shouldBehaveLikeVotes, -}; diff --git a/lib_openzeppelin_contracts/test/governance/utils/Votes.test.js b/lib_openzeppelin_contracts/test/governance/utils/Votes.test.js deleted file mode 100644 index 7acacfc..0000000 --- a/lib_openzeppelin_contracts/test/governance/utils/Votes.test.js +++ /dev/null @@ -1,102 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = 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: '$VotesMock', - timestamp: '$VotesTimestampMock', -}; - -const AMOUNTS = [ethers.parseEther('10000000'), 10n, 20n]; - -describe('Votes', 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 = 'My Vote'; - 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); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/helpers/access-manager.js b/lib_openzeppelin_contracts/test/helpers/access-manager.js deleted file mode 100644 index 3b83430..0000000 --- a/lib_openzeppelin_contracts/test/helpers/access-manager.js +++ /dev/null @@ -1,85 +0,0 @@ -const { ethers } = require('hardhat'); - -const { MAX_UINT64 } = require('./constants'); -const time = require('./time'); -const { upgradeableSlot } = require('./storage'); - -function buildBaseRoles() { - const roles = { - ADMIN: { - id: 0n, - }, - SOME_ADMIN: { - id: 17n, - }, - SOME_GUARDIAN: { - id: 35n, - }, - SOME: { - id: 42n, - }, - PUBLIC: { - id: MAX_UINT64, - }, - }; - - // Names - Object.entries(roles).forEach(([name, role]) => (role.name = name)); - - // Defaults - for (const role of Object.keys(roles)) { - roles[role].admin = roles.ADMIN; - roles[role].guardian = roles.ADMIN; - } - - // Admins - roles.SOME.admin = roles.SOME_ADMIN; - - // Guardians - roles.SOME.guardian = roles.SOME_GUARDIAN; - - return roles; -} - -const formatAccess = access => [access[0], access[1].toString()]; - -const MINSETBACK = time.duration.days(5); -const EXPIRATION = time.duration.weeks(1); - -const EXECUTION_ID_STORAGE_SLOT = upgradeableSlot('AccessManager', 3n); -const CONSUMING_SCHEDULE_STORAGE_SLOT = upgradeableSlot('AccessManaged', 0n); - -/** - * @requires this.{manager, caller, target, calldata} - */ -async function prepareOperation(manager, { caller, target, calldata, delay }) { - const scheduledAt = (await time.clock.timestamp()) + 1n; - await time.increaseTo.timestamp(scheduledAt, false); // Fix next block timestamp for predictability - - return { - schedule: () => manager.connect(caller).schedule(target, calldata, scheduledAt + delay), - scheduledAt, - operationId: hashOperation(caller, target, calldata), - }; -} - -const lazyGetAddress = addressable => addressable.address ?? addressable.target ?? addressable; - -const hashOperation = (caller, target, data) => - ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode( - ['address', 'address', 'bytes'], - [lazyGetAddress(caller), lazyGetAddress(target), data], - ), - ); - -module.exports = { - buildBaseRoles, - formatAccess, - MINSETBACK, - EXPIRATION, - EXECUTION_ID_STORAGE_SLOT, - CONSUMING_SCHEDULE_STORAGE_SLOT, - prepareOperation, - hashOperation, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/account.js b/lib_openzeppelin_contracts/test/helpers/account.js deleted file mode 100644 index 96874b1..0000000 --- a/lib_openzeppelin_contracts/test/helpers/account.js +++ /dev/null @@ -1,14 +0,0 @@ -const { ethers } = require('hardhat'); -const { impersonateAccount, setBalance } = require('@nomicfoundation/hardhat-network-helpers'); - -// Hardhat default balance -const DEFAULT_BALANCE = 10000n * ethers.WeiPerEther; - -const impersonate = (account, balance = DEFAULT_BALANCE) => - impersonateAccount(account) - .then(() => setBalance(account, balance)) - .then(() => ethers.getSigner(account)); - -module.exports = { - impersonate, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/constants.js b/lib_openzeppelin_contracts/test/helpers/constants.js deleted file mode 100644 index 4dfda5e..0000000 --- a/lib_openzeppelin_contracts/test/helpers/constants.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - MAX_UINT48: 2n ** 48n - 1n, - MAX_UINT64: 2n ** 64n - 1n, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/eip712-types.js b/lib_openzeppelin_contracts/test/helpers/eip712-types.js deleted file mode 100644 index b2b6ccf..0000000 --- a/lib_openzeppelin_contracts/test/helpers/eip712-types.js +++ /dev/null @@ -1,52 +0,0 @@ -const { mapValues } = require('./iterate'); - -const formatType = schema => Object.entries(schema).map(([name, type]) => ({ name, type })); - -module.exports = mapValues( - { - EIP712Domain: { - name: 'string', - version: 'string', - chainId: 'uint256', - verifyingContract: 'address', - salt: 'bytes32', - }, - Permit: { - owner: 'address', - spender: 'address', - value: 'uint256', - nonce: 'uint256', - deadline: 'uint256', - }, - Ballot: { - proposalId: 'uint256', - support: 'uint8', - voter: 'address', - nonce: 'uint256', - }, - ExtendedBallot: { - proposalId: 'uint256', - support: 'uint8', - voter: 'address', - nonce: 'uint256', - reason: 'string', - params: 'bytes', - }, - Delegation: { - delegatee: 'address', - nonce: 'uint256', - expiry: 'uint256', - }, - ForwardRequest: { - from: 'address', - to: 'address', - value: 'uint256', - gas: 'uint256', - nonce: 'uint256', - deadline: 'uint48', - data: 'bytes', - }, - }, - formatType, -); -module.exports.formatType = formatType; diff --git a/lib_openzeppelin_contracts/test/helpers/eip712.js b/lib_openzeppelin_contracts/test/helpers/eip712.js deleted file mode 100644 index 3843ac0..0000000 --- a/lib_openzeppelin_contracts/test/helpers/eip712.js +++ /dev/null @@ -1,45 +0,0 @@ -const { ethers } = require('hardhat'); -const types = require('./eip712-types'); - -async function getDomain(contract) { - const { fields, name, version, chainId, verifyingContract, salt, extensions } = await contract.eip712Domain(); - - if (extensions.length > 0) { - throw Error('Extensions not implemented'); - } - - const domain = { - name, - version, - chainId, - verifyingContract, - salt, - }; - - for (const [i, { name }] of types.EIP712Domain.entries()) { - if (!(fields & (1 << i))) { - delete domain[name]; - } - } - - return domain; -} - -function domainType(domain) { - return types.EIP712Domain.filter(({ name }) => domain[name] !== undefined); -} - -function hashTypedData(domain, structHash) { - return ethers.solidityPackedKeccak256( - ['bytes', 'bytes32', 'bytes32'], - ['0x1901', ethers.TypedDataEncoder.hashDomain(domain), structHash], - ); -} - -module.exports = { - getDomain, - domainType, - domainSeparator: ethers.TypedDataEncoder.hashDomain, - hashTypedData, - ...types, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/enums.js b/lib_openzeppelin_contracts/test/helpers/enums.js deleted file mode 100644 index bb23779..0000000 --- a/lib_openzeppelin_contracts/test/helpers/enums.js +++ /dev/null @@ -1,12 +0,0 @@ -function Enum(...options) { - return Object.fromEntries(options.map((key, i) => [key, BigInt(i)])); -} - -module.exports = { - Enum, - ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'), - VoteType: Enum('Against', 'For', 'Abstain'), - Rounding: Enum('Floor', 'Ceil', 'Trunc', 'Expand'), - OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'), - RevertType: Enum('None', 'RevertWithoutMessage', 'RevertWithMessage', 'RevertWithCustomError', 'Panic'), -}; diff --git a/lib_openzeppelin_contracts/test/helpers/governance.js b/lib_openzeppelin_contracts/test/helpers/governance.js deleted file mode 100644 index dce5927..0000000 --- a/lib_openzeppelin_contracts/test/helpers/governance.js +++ /dev/null @@ -1,198 +0,0 @@ -const { ethers } = require('hardhat'); -const { ProposalState } = require('./enums'); -const { unique } = require('./iterate'); -const time = require('./time'); - -const timelockSalt = (address, descriptionHash) => - ethers.toBeHex((ethers.toBigInt(address) << 96n) ^ ethers.toBigInt(descriptionHash), 32); - -class GovernorHelper { - constructor(governor, mode = 'blocknumber') { - this.governor = governor; - this.mode = mode; - } - - connect(account) { - this.governor = this.governor.connect(account); - return this; - } - - /// Setter and getters - /** - * Specify a proposal either as - * 1) an array of objects [{ target, value, data }] - * 2) an object of arrays { targets: [], values: [], data: [] } - */ - setProposal(actions, description) { - if (Array.isArray(actions)) { - this.targets = actions.map(a => a.target); - this.values = actions.map(a => a.value || 0n); - this.data = actions.map(a => a.data || '0x'); - } else { - ({ targets: this.targets, values: this.values, data: this.data } = actions); - } - this.description = description; - return this; - } - - get id() { - return ethers.keccak256( - ethers.AbiCoder.defaultAbiCoder().encode(['address[]', 'uint256[]', 'bytes[]', 'bytes32'], this.shortProposal), - ); - } - - // used for checking events - get signatures() { - return this.data.map(() => ''); - } - - get descriptionHash() { - return ethers.id(this.description); - } - - // condensed version for queueing end executing - get shortProposal() { - return [this.targets, this.values, this.data, this.descriptionHash]; - } - - // full version for proposing - get fullProposal() { - return [this.targets, this.values, this.data, this.description]; - } - - get currentProposal() { - return this; - } - - /// Proposal lifecycle - delegate(delegation) { - return Promise.all([ - delegation.token.connect(delegation.to).delegate(delegation.to), - delegation.value === undefined || - delegation.token.connect(this.governor.runner).transfer(delegation.to, delegation.value), - delegation.tokenId === undefined || - delegation.token - .ownerOf(delegation.tokenId) - .then(owner => - delegation.token.connect(this.governor.runner).transferFrom(owner, delegation.to, delegation.tokenId), - ), - ]); - } - - propose() { - return this.governor.propose(...this.fullProposal); - } - - queue() { - return this.governor.queue(...this.shortProposal); - } - - execute() { - return this.governor.execute(...this.shortProposal); - } - - cancel(visibility = 'external') { - switch (visibility) { - case 'external': - return this.governor.cancel(...this.shortProposal); - - case 'internal': - return this.governor.$_cancel(...this.shortProposal); - - default: - throw new Error(`unsupported visibility "${visibility}"`); - } - } - - async vote(vote = {}) { - let method = 'castVote'; // default - let args = [this.id, vote.support]; // base - - if (vote.signature) { - const sign = await vote.signature(this.governor, this.forgeMessage(vote)); - if (vote.params || vote.reason) { - method = 'castVoteWithReasonAndParamsBySig'; - args.push(vote.voter, vote.reason ?? '', vote.params ?? '0x', sign); - } else { - method = 'castVoteBySig'; - args.push(vote.voter, sign); - } - } else if (vote.params) { - method = 'castVoteWithReasonAndParams'; - args.push(vote.reason ?? '', vote.params); - } else if (vote.reason) { - method = 'castVoteWithReason'; - args.push(vote.reason); - } - - return await this.governor[method](...args); - } - - /// Clock helpers - async waitForSnapshot(offset = 0n) { - const timepoint = await this.governor.proposalSnapshot(this.id); - return time.increaseTo[this.mode](timepoint + offset); - } - - async waitForDeadline(offset = 0n) { - const timepoint = await this.governor.proposalDeadline(this.id); - return time.increaseTo[this.mode](timepoint + offset); - } - - async waitForEta(offset = 0n) { - const timestamp = await this.governor.proposalEta(this.id); - return time.increaseTo.timestamp(timestamp + offset); - } - - /// Other helpers - forgeMessage(vote = {}) { - const message = { proposalId: this.id, support: vote.support, voter: vote.voter, nonce: vote.nonce }; - - if (vote.params || vote.reason) { - message.reason = vote.reason ?? ''; - message.params = vote.params ?? '0x'; - } - - return message; - } - - /** - * Encodes a list ProposalStates into a bytes32 representation where each bit enabled corresponds to - * the underlying position in the `ProposalState` enum. For example: - * - * 0x000...10000 - * ^^^^^^------ ... - * ^----- Succeeded - * ^---- Defeated - * ^--- Canceled - * ^-- Active - * ^- Pending - */ - static proposalStatesToBitMap(proposalStates, options = {}) { - if (!Array.isArray(proposalStates)) { - proposalStates = [proposalStates]; - } - const statesCount = ethers.toBigInt(Object.keys(ProposalState).length); - let result = 0n; - - for (const state of unique(proposalStates)) { - if (state < 0n || state >= statesCount) { - expect.fail(`ProposalState ${state} out of possible states (0...${statesCount}-1)`); - } else { - result |= 1n << state; - } - } - - if (options.inverted) { - const mask = 2n ** statesCount - 1n; - result = result ^ mask; - } - - return ethers.toBeHex(result, 32); - } -} - -module.exports = { - GovernorHelper, - timelockSalt, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/iterate.js b/lib_openzeppelin_contracts/test/helpers/iterate.js deleted file mode 100644 index ef4526e..0000000 --- a/lib_openzeppelin_contracts/test/helpers/iterate.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = { - // ================================================= Array helpers ================================================= - - // Cut an array into an array of sized-length arrays - // Example: chunk([1,2,3,4,5,6,7,8], 3) → [[1,2,3],[4,5,6],[7,8]] - chunk: (array, size = 1) => - Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)), - - // Cartesian cross product of an array of arrays - // Example: product([1,2],[a,b,c],[true]) → [[1,a,true],[1,b,true],[1,c,true],[2,a,true],[2,b,true],[2,c,true]] - product: (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]), - - // Range from start to end in increment - // Example: range(17,42,7) → [17,24,31,38] - range: (start, stop = undefined, step = 1) => { - if (!stop) { - stop = start; - start = 0; - } - return start < stop ? Array.from({ length: Math.ceil((stop - start) / step) }, (_, i) => start + i * step) : []; - }, - - // Unique elements, with an optional getter function - // Example: unique([1,1,2,3,4,8,1,3,8,13,42]) → [1,2,3,4,8,13,42] - unique: (array, op = x => x) => array.filter((obj, i) => array.findIndex(entry => op(obj) === op(entry)) === i), - - // Zip arrays together. If some arrays are smaller, undefined is used as a filler. - // Example: zip([1,2],[a,b,c],[true]) → [[1,a,true],[2,b,undefined],[undefined,c,undefined]] - zip: (...args) => Array.from({ length: Math.max(...args.map(arg => arg.length)) }, (_, i) => args.map(arg => arg[i])), - - // ================================================ Object helpers ================================================= - - // Create a new object by mapping the values through a function, keeping the keys - // Example: mapValues({a:1,b:2,c:3}, x => x**2) → {a:1,b:4,c:9} - mapValues: (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])), -}; diff --git a/lib_openzeppelin_contracts/test/helpers/math.js b/lib_openzeppelin_contracts/test/helpers/math.js deleted file mode 100644 index 133254a..0000000 --- a/lib_openzeppelin_contracts/test/helpers/math.js +++ /dev/null @@ -1,33 +0,0 @@ -// Array of number or bigint -const max = (...values) => values.slice(1).reduce((x, y) => (x > y ? x : y), values.at(0)); -const min = (...values) => values.slice(1).reduce((x, y) => (x < y ? x : y), values.at(0)); -const sum = (...values) => values.slice(1).reduce((x, y) => x + y, values.at(0)); - -// Computes modexp without BigInt overflow for large numbers -function modExp(b, e, m) { - let result = 1n; - - // If e is a power of two, modexp can be calculated as: - // for (let result = b, i = 0; i < log2(e); i++) result = modexp(result, 2, m) - // - // Given any natural number can be written in terms of powers of 2 (i.e. binary) - // then modexp can be calculated for any e, by multiplying b**i for all i where - // binary(e)[i] is 1 (i.e. a power of two). - for (let base = b % m; e > 0n; base = base ** 2n % m) { - // Least significant bit is 1 - if (e % 2n == 1n) { - result = (result * base) % m; - } - - e /= 2n; // Binary pop - } - - return result; -} - -module.exports = { - min, - max, - sum, - modExp, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/methods.js b/lib_openzeppelin_contracts/test/helpers/methods.js deleted file mode 100644 index a491897..0000000 --- a/lib_openzeppelin_contracts/test/helpers/methods.js +++ /dev/null @@ -1,14 +0,0 @@ -const { ethers } = require('hardhat'); - -const selector = signature => ethers.FunctionFragment.from(signature).selector; - -const interfaceId = signatures => - ethers.toBeHex( - signatures.reduce((acc, signature) => acc ^ ethers.toBigInt(selector(signature)), 0n), - 4, - ); - -module.exports = { - selector, - interfaceId, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/random.js b/lib_openzeppelin_contracts/test/helpers/random.js deleted file mode 100644 index 3adeed0..0000000 --- a/lib_openzeppelin_contracts/test/helpers/random.js +++ /dev/null @@ -1,19 +0,0 @@ -const { ethers } = require('hardhat'); - -const generators = { - address: () => ethers.Wallet.createRandom().address, - bytes32: () => ethers.hexlify(ethers.randomBytes(32)), - uint256: () => ethers.toBigInt(ethers.randomBytes(32)), - int256: () => ethers.toBigInt(ethers.randomBytes(32)) + ethers.MinInt256, - hexBytes: length => ethers.hexlify(ethers.randomBytes(length)), -}; - -generators.address.zero = ethers.ZeroAddress; -generators.bytes32.zero = ethers.ZeroHash; -generators.uint256.zero = 0n; -generators.int256.zero = 0n; -generators.hexBytes.zero = '0x'; - -module.exports = { - generators, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/storage.js b/lib_openzeppelin_contracts/test/helpers/storage.js deleted file mode 100644 index a75a306..0000000 --- a/lib_openzeppelin_contracts/test/helpers/storage.js +++ /dev/null @@ -1,48 +0,0 @@ -const { ethers } = require('hardhat'); -const { setStorageAt } = require('@nomicfoundation/hardhat-network-helpers'); - -const ImplementationLabel = 'eip1967.proxy.implementation'; -const AdminLabel = 'eip1967.proxy.admin'; -const BeaconLabel = 'eip1967.proxy.beacon'; - -const erc1967Slot = label => ethers.toBeHex(ethers.toBigInt(ethers.id(label)) - 1n); -const erc7201Slot = label => ethers.toBeHex(ethers.toBigInt(ethers.keccak256(erc1967Slot(label))) & ~0xffn); -const erc7201format = contractName => `openzeppelin.storage.${contractName}`; - -const getSlot = (address, slot) => - ethers.provider.getStorage(address, ethers.isBytesLike(slot) ? slot : erc1967Slot(slot)); - -const setSlot = (address, slot, value) => - Promise.all([ - ethers.isAddressable(address) ? address.getAddress() : Promise.resolve(address), - ethers.isAddressable(value) ? value.getAddress() : Promise.resolve(value), - ]).then(([address, value]) => setStorageAt(address, ethers.isBytesLike(slot) ? slot : erc1967Slot(slot), value)); - -const getAddressInSlot = (address, slot) => - getSlot(address, slot).then(slotValue => ethers.AbiCoder.defaultAbiCoder().decode(['address'], slotValue)[0]); - -const upgradeableSlot = (contractName, offset) => { - try { - // 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 (_) { - return offset; - } -}; - -module.exports = { - ImplementationLabel, - AdminLabel, - BeaconLabel, - ImplementationSlot: erc1967Slot(ImplementationLabel), - AdminSlot: erc1967Slot(AdminLabel), - BeaconSlot: erc1967Slot(BeaconLabel), - erc1967Slot, - erc7201Slot, - erc7201format, - setSlot, - getSlot, - getAddressInSlot, - upgradeableSlot, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/strings.js b/lib_openzeppelin_contracts/test/helpers/strings.js deleted file mode 100644 index 4f34099..0000000 --- a/lib_openzeppelin_contracts/test/helpers/strings.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - // Capitalize the first char of a string - // Example: capitalize('uint256') → 'Uint256' - capitalize: str => str.charAt(0).toUpperCase() + str.slice(1), -}; diff --git a/lib_openzeppelin_contracts/test/helpers/time.js b/lib_openzeppelin_contracts/test/helpers/time.js deleted file mode 100644 index f6ccc3c..0000000 --- a/lib_openzeppelin_contracts/test/helpers/time.js +++ /dev/null @@ -1,30 +0,0 @@ -const { ethers } = require('hardhat'); -const { time, mine, mineUpTo } = require('@nomicfoundation/hardhat-network-helpers'); -const { mapValues } = require('./iterate'); - -const clock = { - blocknumber: () => time.latestBlock().then(ethers.toBigInt), - timestamp: () => time.latest().then(ethers.toBigInt), -}; -const clockFromReceipt = { - blocknumber: receipt => Promise.resolve(ethers.toBigInt(receipt.blockNumber)), - timestamp: receipt => ethers.provider.getBlock(receipt.blockNumber).then(block => ethers.toBigInt(block.timestamp)), -}; -const increaseBy = { - blockNumber: mine, - timestamp: (delay, mine = true) => - time.latest().then(clock => increaseTo.timestamp(clock + ethers.toNumber(delay), mine)), -}; -const increaseTo = { - blocknumber: mineUpTo, - timestamp: (to, mine = true) => (mine ? time.increaseTo(to) : time.setNextBlockTimestamp(to)), -}; -const duration = mapValues(time.duration, fn => n => ethers.toBigInt(fn(ethers.toNumber(n)))); - -module.exports = { - clock, - clockFromReceipt, - increaseBy, - increaseTo, - duration, -}; diff --git a/lib_openzeppelin_contracts/test/helpers/txpool.js b/lib_openzeppelin_contracts/test/helpers/txpool.js deleted file mode 100644 index f01327b..0000000 --- a/lib_openzeppelin_contracts/test/helpers/txpool.js +++ /dev/null @@ -1,29 +0,0 @@ -const { network } = require('hardhat'); -const { expect } = require('chai'); -const { mine } = require('@nomicfoundation/hardhat-network-helpers'); - -const { unique } = require('./iterate'); - -async function batchInBlock(txs) { - try { - // disable auto-mining - await network.provider.send('evm_setAutomine', [false]); - // send all transactions - const responses = await Promise.all(txs.map(fn => fn())); - // mine one block - await mine(); - // fetch receipts - const receipts = await Promise.all(responses.map(response => response.wait())); - // Sanity check, all tx should be in the same block - expect(unique(receipts.map(receipt => receipt.blockNumber))).to.have.lengthOf(1); - // return responses - return receipts; - } finally { - // enable auto-mining - await network.provider.send('evm_setAutomine', [true]); - } -} - -module.exports = { - batchInBlock, -}; diff --git a/lib_openzeppelin_contracts/test/metatx/ERC2771Context.test.js b/lib_openzeppelin_contracts/test/metatx/ERC2771Context.test.js deleted file mode 100644 index 15da61d..0000000 --- a/lib_openzeppelin_contracts/test/metatx/ERC2771Context.test.js +++ /dev/null @@ -1,133 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { impersonate } = require('../helpers/account'); -const { getDomain, ForwardRequest } = require('../helpers/eip712'); -const { MAX_UINT48 } = require('../helpers/constants'); - -const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior'); - -async function fixture() { - const [sender, other] = await ethers.getSigners(); - - const forwarder = await ethers.deployContract('ERC2771Forwarder', []); - const forwarderAsSigner = await impersonate(forwarder.target); - const context = await ethers.deployContract('ERC2771ContextMock', [forwarder]); - const domain = await getDomain(forwarder); - const types = { ForwardRequest }; - - return { sender, other, forwarder, forwarderAsSigner, context, domain, types }; -} - -describe('ERC2771Context', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('recognize trusted forwarder', async function () { - expect(await this.context.isTrustedForwarder(this.forwarder)).to.be.true; - }); - - it('returns the trusted forwarder', async function () { - expect(await this.context.trustedForwarder()).to.equal(this.forwarder); - }); - - describe('when called directly', function () { - shouldBehaveLikeRegularContext(); - }); - - describe('when receiving a relayed call', function () { - describe('msgSender', function () { - it('returns the relayed transaction original sender', async function () { - const nonce = await this.forwarder.nonces(this.sender); - const data = this.context.interface.encodeFunctionData('msgSender'); - - const req = { - from: await this.sender.getAddress(), - to: await this.context.getAddress(), - value: 0n, - data, - gas: 100000n, - nonce, - deadline: MAX_UINT48, - }; - - req.signature = await this.sender.signTypedData(this.domain, this.types, req); - - expect(await this.forwarder.verify(req)).to.be.true; - - await expect(this.forwarder.execute(req)).to.emit(this.context, 'Sender').withArgs(this.sender); - }); - - it('returns the original sender when calldata length is less than 20 bytes (address length)', async function () { - // The forwarder doesn't produce calls with calldata length less than 20 bytes so `this.forwarderAsSigner` is used instead. - await expect(this.context.connect(this.forwarderAsSigner).msgSender()) - .to.emit(this.context, 'Sender') - .withArgs(this.forwarder); - }); - }); - - describe('msgData', function () { - it('returns the relayed transaction original data', async function () { - const args = [42n, 'OpenZeppelin']; - - const nonce = await this.forwarder.nonces(this.sender); - const data = this.context.interface.encodeFunctionData('msgData', args); - - const req = { - from: await this.sender.getAddress(), - to: await this.context.getAddress(), - value: 0n, - data, - gas: 100000n, - nonce, - deadline: MAX_UINT48, - }; - - req.signature = this.sender.signTypedData(this.domain, this.types, req); - - expect(await this.forwarder.verify(req)).to.be.true; - - await expect(this.forwarder.execute(req)) - .to.emit(this.context, 'Data') - .withArgs(data, ...args); - }); - }); - - it('returns the full original data when calldata length is less than 20 bytes (address length)', async function () { - const data = this.context.interface.encodeFunctionData('msgDataShort'); - - // The forwarder doesn't produce calls with calldata length less than 20 bytes so `this.forwarderAsSigner` is used instead. - await expect(await this.context.connect(this.forwarderAsSigner).msgDataShort()) - .to.emit(this.context, 'DataShort') - .withArgs(data); - }); - }); - - it('multicall poison attack', async function () { - const nonce = await this.forwarder.nonces(this.sender); - const data = this.context.interface.encodeFunctionData('multicall', [ - [ - // poisonned call to 'msgSender()' - ethers.concat([this.context.interface.encodeFunctionData('msgSender'), this.other.address]), - ], - ]); - - const req = { - from: await this.sender.getAddress(), - to: await this.context.getAddress(), - value: 0n, - data, - gas: 100000n, - nonce, - deadline: MAX_UINT48, - }; - - req.signature = await this.sender.signTypedData(this.domain, this.types, req); - - expect(await this.forwarder.verify(req)).to.be.true; - - await expect(this.forwarder.execute(req)).to.emit(this.context, 'Sender').withArgs(this.sender); - }); -}); diff --git a/lib_openzeppelin_contracts/test/metatx/ERC2771Forwarder.t.sol b/lib_openzeppelin_contracts/test/metatx/ERC2771Forwarder.t.sol deleted file mode 100644 index 6e980a0..0000000 --- a/lib_openzeppelin_contracts/test/metatx/ERC2771Forwarder.t.sol +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: MIT - -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; -} - -contract ERC2771ForwarderMock is ERC2771Forwarder { - constructor(string memory name) ERC2771Forwarder(name) {} - - function structHash(ForwardRequest calldata request) external view returns (bytes32) { - return - _hashTypedDataV4( - keccak256( - abi.encode( - _FORWARD_REQUEST_TYPEHASH, - request.from, - request.to, - request.value, - request.gas, - request.nonce, - request.deadline, - keccak256(request.data) - ) - ) - ); - } -} - -contract ERC2771ForwarderTest is Test { - ERC2771ForwarderMock internal _erc2771Forwarder; - CallReceiverMockTrustingForwarder internal _receiver; - - uint256 internal _signerPrivateKey; - uint256 internal _relayerPrivateKey; - - address internal _signer; - address internal _relayer; - - 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); - } - - 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 - }); - } - - function testExecuteAvoidsETHStuck(uint256 initialBalance, uint256 value, bool targetReverts) public { - initialBalance = bound(initialBalance, 0, _MAX_ETHER); - value = bound(value, 0, _MAX_ETHER); - - vm.deal(address(_erc2771Forwarder), initialBalance); - - uint256 nonce = _erc2771Forwarder.nonces(_signer); - - vm.deal(address(this), value); - - ERC2771Forwarder.ForwardRequestData memory requestData = _forgeRequestData({ - value: value, - nonce: nonce, - deadline: uint48(block.timestamp + 1), - data: targetReverts - ? abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()) - : abi.encodeCall(CallReceiverMock.mockFunction, ()) - }); - - if (targetReverts) { - vm.expectRevert(); - } - - _erc2771Forwarder.execute{value: value}(requestData); - assertEq(address(_erc2771Forwarder).balance, initialBalance); - } - - function testExecuteBatchAvoidsETHStuck(uint256 initialBalance, uint256 batchSize, uint256 value) public { - batchSize = bound(batchSize, 1, 10); - initialBalance = bound(initialBalance, 0, _MAX_ETHER); - value = bound(value, 0, _MAX_ETHER); - - vm.deal(address(_erc2771Forwarder), initialBalance); - uint256 nonce = _erc2771Forwarder.nonces(_signer); - - ERC2771Forwarder.ForwardRequestData[] memory batchRequestDatas = new ERC2771Forwarder.ForwardRequestData[]( - batchSize - ); - - uint256 expectedRefund; - - for (uint256 i = 0; i < batchSize; ++i) { - bytes memory data; - bool succeed = uint256(keccak256(abi.encodePacked(initialBalance, i))) % 2 == 0; - - if (succeed) { - data = abi.encodeCall(CallReceiverMock.mockFunction, ()); - } else { - expectedRefund += value; - data = abi.encodeCall(CallReceiverMock.mockFunctionRevertsNoReason, ()); - } - - batchRequestDatas[i] = _forgeRequestData({ - value: value, - nonce: nonce + i, - deadline: uint48(block.timestamp + 1), - data: data - }); - } - - address payable refundReceiver = payable(address(0xebe)); - uint256 totalValue = value * batchSize; - - vm.deal(address(this), totalValue); - _erc2771Forwarder.executeBatch{value: totalValue}(batchRequestDatas, refundReceiver); - - assertEq(address(_erc2771Forwarder).balance, initialBalance); - assertEq(refundReceiver.balance, expectedRefund); - } -} diff --git a/lib_openzeppelin_contracts/test/metatx/ERC2771Forwarder.test.js b/lib_openzeppelin_contracts/test/metatx/ERC2771Forwarder.test.js deleted file mode 100644 index 8653ad7..0000000 --- a/lib_openzeppelin_contracts/test/metatx/ERC2771Forwarder.test.js +++ /dev/null @@ -1,461 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getDomain, ForwardRequest } = require('../helpers/eip712'); -const { sum } = require('../helpers/math'); -const time = require('../helpers/time'); - -async function fixture() { - const [sender, refundReceiver, another, ...accounts] = await ethers.getSigners(); - - const forwarder = await ethers.deployContract('ERC2771Forwarder', ['ERC2771Forwarder']); - const receiver = await ethers.deployContract('CallReceiverMockTrustingForwarder', [forwarder]); - const domain = await getDomain(forwarder); - const types = { ForwardRequest }; - - const forgeRequest = async (override = {}, signer = sender) => { - const req = { - from: await signer.getAddress(), - to: await receiver.getAddress(), - value: 0n, - data: receiver.interface.encodeFunctionData('mockFunction'), - gas: 100000n, - deadline: (await time.clock.timestamp()) + 60n, - nonce: await forwarder.nonces(sender), - ...override, - }; - req.signature = await signer.signTypedData(domain, types, req); - return req; - }; - - const estimateRequest = request => - ethers.provider.estimateGas({ - from: forwarder, - to: request.to, - data: ethers.solidityPacked(['bytes', 'address'], [request.data, request.from]), - value: request.value, - gasLimit: request.gas, - }); - - return { - sender, - refundReceiver, - another, - accounts, - forwarder, - receiver, - forgeRequest, - estimateRequest, - domain, - types, - }; -} - -// 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)); - }); - - describe('verify', function () { - describe('with valid signature', function () { - it('returns true without altering the nonce', async function () { - const request = await this.forgeRequest(); - expect(await this.forwarder.nonces(request.from)).to.equal(request.nonce); - expect(await this.forwarder.verify(request)).to.be.true; - expect(await this.forwarder.nonces(request.from)).to.equal(request.nonce); - }); - }); - - 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; - }); - - it('returns false with valid signature for expired deadline', async function () { - const request = await this.forgeRequest({ deadline: (await time.clock.timestamp()) - 1n }); - expect(await this.forwarder.verify(request)).to.be.false; - }); - }); - }); - - describe('execute', function () { - describe('with valid requests', function () { - it('emits an event and consumes nonce for a successful request', async function () { - const request = await this.forgeRequest(); - - expect(await this.forwarder.nonces(request.from)).to.equal(request.nonce); - - await expect(this.forwarder.execute(request)) - .to.emit(this.receiver, 'MockFunctionCalled') - .to.emit(this.forwarder, 'ExecutedForwardRequest') - .withArgs(request.from, request.nonce, true); - - expect(await this.forwarder.nonces(request.from)).to.equal(request.nonce + 1n); - }); - - it('reverts with an unsuccessful request', async function () { - const request = await this.forgeRequest({ - data: this.receiver.interface.encodeFunctionData('mockFunctionRevertsNoReason'), - }); - - await expect(this.forwarder.execute(request)).to.be.revertedWithCustomError(this.forwarder, 'FailedCall'); - }); - }); - - 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(); - - // consume nonce - await this.forwarder.execute(request); - - // nonce has changed - await expect(this.forwarder.execute(request)) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs( - ethers.verifyTypedData( - this.domain, - this.types, - { ...request, nonce: request.nonce + 1n }, - request.signature, - ), - request.from, - ); - }); - - it('reverts with valid signature for expired deadline', async function () { - const request = await this.forgeRequest({ deadline: (await time.clock.timestamp()) - 1n }); - - await expect(this.forwarder.execute(request)) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderExpiredRequest') - .withArgs(request.deadline); - }); - - it('reverts with valid signature but mismatched value', async function () { - const request = await this.forgeRequest({ value: 100n }); - - await expect(this.forwarder.execute(request)) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderMismatchedValue') - .withArgs(request.value, 0n); - }); - }); - - it('bubbles out of gas', async function () { - const request = await this.forgeRequest({ - data: this.receiver.interface.encodeFunctionData('mockFunctionOutOfGas'), - gas: 1_000_000n, - }); - - const gasLimit = 100_000n; - await expect(this.forwarder.execute(request, { gasLimit })).to.be.revertedWithoutReason(); - - const { gasUsed } = await ethers.provider - .getBlock('latest') - .then(block => block.getTransaction(0)) - .then(tx => ethers.provider.getTransactionReceipt(tx.hash)); - - expect(gasUsed).to.equal(gasLimit); - }); - - it('bubbles out of gas forced by the relayer', async function () { - const request = await this.forgeRequest(); - - // If there's an incentive behind executing requests, a malicious relayer could grief - // the forwarder by executing requests and providing a top-level call gas limit that - // is too low to successfully finish the request after the 63/64 rule. - - // We set the baseline to the gas limit consumed by a successful request if it was executed - // normally. Note this includes the 21000 buffer that also the relayer will be charged to - // start a request execution. - const estimate = await this.estimateRequest(request); - - // Because the relayer call consumes gas until the `CALL` opcode, the gas left after failing - // the subcall won't enough to finish the top level call (after testing), so we add a - // moderated buffer. - const gasLimit = estimate + 2_000n; - - // The subcall out of gas should be caught by the contract and then bubbled up consuming - // the available gas with an `invalid` opcode. - await expect(this.forwarder.execute(request, { gasLimit })).to.be.revertedWithoutReason(); - - const { gasUsed } = await ethers.provider - .getBlock('latest') - .then(block => block.getTransaction(0)) - .then(tx => ethers.provider.getTransactionReceipt(tx.hash)); - - // We assert that indeed the gas was totally consumed. - expect(gasUsed).to.equal(gasLimit); - }); - }); - - describe('executeBatch', function () { - const requestsValue = requests => sum(...requests.map(request => request.value)); - const requestCount = 3; - const idx = 1; // index that will be tampered with - - beforeEach(async function () { - this.forgeRequests = override => - Promise.all(this.accounts.slice(0, requestCount).map(signer => this.forgeRequest(override, signer))); - this.requests = await this.forgeRequests({ value: 10n }); - this.value = requestsValue(this.requests); - }); - - describe('with valid requests', function () { - it('sanity', async function () { - for (const request of this.requests) { - expect(await this.forwarder.verify(request)).to.be.true; - } - }); - - it('emits events', async function () { - const receipt = this.forwarder.executeBatch(this.requests, this.another, { value: this.value }); - - for (const request of this.requests) { - await expect(receipt) - .to.emit(this.receiver, 'MockFunctionCalled') - .to.emit(this.forwarder, 'ExecutedForwardRequest') - .withArgs(request.from, request.nonce, true); - } - }); - - it('increase nonces', async function () { - await this.forwarder.executeBatch(this.requests, this.another, { value: this.value }); - - for (const request of this.requests) { - expect(await this.forwarder.nonces(request.from)).to.equal(request.nonce + 1n); - } - }); - }); - - describe('with tampered requests', function () { - it('reverts with mismatched value', async function () { - // tamper value of one of the request + resign - this.requests[idx] = await this.forgeRequest({ value: 100n }, this.accounts[1]); - - await expect(this.forwarder.executeBatch(this.requests, this.another, { value: this.value })) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderMismatchedValue') - .withArgs(requestsValue(this.requests), this.value); - }); - - describe('when the refund receiver is the zero address', function () { - beforeEach(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 }); - - // And then fail due to an already used nonce - await expect(this.forwarder.executeBatch(this.requests, this.refundReceiver, { value: this.value })) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderInvalidSigner') - .withArgs( - ethers.verifyTypedData( - this.domain, - this.types, - { ...this.requests[idx], nonce: this.requests[idx].nonce + 1n }, - this.requests[idx].signature, - ), - this.requests[idx].from, - ); - }); - - it('reverts with at least one valid signature for expired deadline', async function () { - this.requests[idx] = await this.forgeRequest( - { ...this.requests[idx], deadline: (await time.clock.timestamp()) - 1n }, - this.accounts[1], - ); - - await expect(this.forwarder.executeBatch(this.requests, this.refundReceiver, { value: this.amount })) - .to.be.revertedWithCustomError(this.forwarder, 'ERC2771ForwarderExpiredRequest') - .withArgs(this.requests[idx].deadline); - }); - }); - - describe('when the refund receiver is a known address', function () { - beforeEach(async function () { - this.initialRefundReceiverBalance = await ethers.provider.getBalance(this.refundReceiver); - 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 }); - this.initialTamperedRequestNonce++; // Should be already incremented by the individual `execute` - - // And then ignore the same request in a batch due to an already used nonce - const events = await this.forwarder - .executeBatch(this.requests, this.refundReceiver, { value: this.value }) - .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 expired deadline', async function () { - this.requests[idx] = await this.forgeRequest( - { ...this.requests[idx], deadline: (await time.clock.timestamp()) - 1n }, - this.accounts[1], - ); - - const events = await this.forwarder - .executeBatch(this.requests, this.refundReceiver, { value: this.value }) - .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); - }); - - afterEach(async function () { - // The invalid request value was refunded - expect(await ethers.provider.getBalance(this.refundReceiver)).to.equal( - this.initialRefundReceiverBalance + this.requests[idx].value, - ); - - // The invalid request from's nonce was not incremented - expect(await this.forwarder.nonces(this.requests[idx].from)).to.equal(this.initialTamperedRequestNonce); - }); - }); - - it('bubbles out of gas', async function () { - this.requests[idx] = await this.forgeRequest({ - data: this.receiver.interface.encodeFunctionData('mockFunctionOutOfGas'), - gas: 1_000_000n, - }); - - const gasLimit = 300_000n; - await expect( - this.forwarder.executeBatch(this.requests, ethers.ZeroAddress, { - gasLimit, - value: requestsValue(this.requests), - }), - ).to.be.revertedWithoutReason(); - - const { gasUsed } = await ethers.provider - .getBlock('latest') - .then(block => block.getTransaction(0)) - .then(tx => ethers.provider.getTransactionReceipt(tx.hash)); - - expect(gasUsed).to.equal(gasLimit); - }); - - it('bubbles out of gas forced by the relayer', async function () { - // Similarly to the single execute, a malicious relayer could grief requests. - - // We estimate until the selected request as if they were executed normally - const estimate = await Promise.all(this.requests.slice(0, idx + 1).map(this.estimateRequest)).then(gas => - sum(...gas), - ); - - // We add a Buffer to account for all the gas that's used before the selected call. - // Note is slightly bigger because the selected request is not the index 0 and it affects - // the buffer needed. - const gasLimit = estimate + 10_000n; - - // The subcall out of gas should be caught by the contract and then bubbled up consuming - // the available gas with an `invalid` opcode. - await expect( - this.forwarder.executeBatch(this.requests, ethers.ZeroAddress, { - gasLimit, - value: requestsValue(this.requests), - }), - ).to.be.revertedWithoutReason(); - - const { gasUsed } = await ethers.provider - .getBlock('latest') - .then(block => block.getTransaction(0)) - .then(tx => ethers.provider.getTransactionReceipt(tx.hash)); - - // We assert that indeed the gas was totally consumed. - expect(gasUsed).to.equal(gasLimit); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/Clones.behaviour.js b/lib_openzeppelin_contracts/test/proxy/Clones.behaviour.js deleted file mode 100644 index dcc6206..0000000 --- a/lib_openzeppelin_contracts/test/proxy/Clones.behaviour.js +++ /dev/null @@ -1,160 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -module.exports = function shouldBehaveLikeClone() { - const assertProxyInitialization = function ({ value, balance }) { - it('initializes the proxy', async function () { - const dummy = await ethers.getContractAt('DummyImplementation', this.proxy); - expect(await dummy.value()).to.equal(value); - }); - - it('has expected balance', async function () { - expect(await ethers.provider.getBalance(this.proxy)).to.equal(balance); - }); - }; - - describe('construct with value', function () { - const value = 10n; - - it('factory has enough balance', async function () { - await this.deployer.sendTransaction({ to: this.factory, value }); - - const instance = await this.createClone({ deployValue: value }); - await expect(instance.deploymentTransaction()).to.changeEtherBalances([this.factory, instance], [-value, value]); - - expect(await ethers.provider.getBalance(instance)).to.equal(value); - }); - - it('factory does not have enough balance', async function () { - await expect(this.createClone({ deployValue: value })) - .to.be.revertedWithCustomError(this.factory, 'InsufficientBalance') - .withArgs(0n, value); - }); - }); - - describe('initialization without parameters', function () { - describe('non payable', function () { - const expectedInitializedValue = 10n; - - beforeEach(async function () { - this.initializeData = await this.implementation.interface.encodeFunctionData('initializeNonPayable'); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createClone({ initData: this.initializeData }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0, - }); - }); - - describe('when sending some balance', function () { - const value = 10n ** 6n; - - it('reverts', async function () { - await expect(this.createClone({ initData: this.initializeData, initValue: value })).to.be.reverted; - }); - }); - }); - - describe('payable', function () { - const expectedInitializedValue = 100n; - - beforeEach(async function () { - this.initializeData = await this.implementation.interface.encodeFunctionData('initializePayable'); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createClone({ initData: this.initializeData }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0, - }); - }); - - describe('when sending some balance', function () { - const value = 10n ** 6n; - - beforeEach('creating proxy', async function () { - this.proxy = await this.createClone({ initData: this.initializeData, initValue: value }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: value, - }); - }); - }); - }); - - describe('initialization with parameters', function () { - describe('non payable', function () { - const expectedInitializedValue = 10n; - - beforeEach(async function () { - this.initializeData = await this.implementation.interface.encodeFunctionData('initializeNonPayableWithValue', [ - expectedInitializedValue, - ]); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createClone({ initData: this.initializeData }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0, - }); - }); - - describe('when sending some balance', function () { - const value = 10n ** 6n; - - it('reverts', async function () { - await expect(this.createClone({ initData: this.initializeData, initValue: value })).to.be.reverted; - }); - }); - }); - - describe('payable', function () { - const expectedInitializedValue = 42n; - - beforeEach(function () { - this.initializeData = this.implementation.interface.encodeFunctionData('initializePayableWithValue', [ - expectedInitializedValue, - ]); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createClone({ initData: this.initializeData }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0, - }); - }); - - describe('when sending some balance', function () { - const value = 10n ** 6n; - - beforeEach('creating proxy', async function () { - this.proxy = await this.createClone({ initData: this.initializeData, initValue: value }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: value, - }); - }); - }); - }); -}; diff --git a/lib_openzeppelin_contracts/test/proxy/Clones.t.sol b/lib_openzeppelin_contracts/test/proxy/Clones.t.sol deleted file mode 100644 index de1c3be..0000000 --- a/lib_openzeppelin_contracts/test/proxy/Clones.t.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; - -contract ClonesTest is Test { - function testPredictDeterministicAddressSpillage(address implementation, bytes32 salt) public { - address predicted = Clones.predictDeterministicAddress(implementation, salt); - bytes32 spillage; - /// @solidity memory-safe-assembly - assembly { - spillage := and(predicted, 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) - } - assertEq(spillage, bytes32(0)); - } -} diff --git a/lib_openzeppelin_contracts/test/proxy/Clones.test.js b/lib_openzeppelin_contracts/test/proxy/Clones.test.js deleted file mode 100644 index 70220fb..0000000 --- a/lib_openzeppelin_contracts/test/proxy/Clones.test.js +++ /dev/null @@ -1,95 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const shouldBehaveLikeClone = require('./Clones.behaviour'); - -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 }); - }; - - return { deployer, factory, implementation, newClone, newCloneDeterministic }; -} - -describe('Clones', function () { - beforeEach(async 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); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/ERC1967/ERC1967Proxy.test.js b/lib_openzeppelin_contracts/test/proxy/ERC1967/ERC1967Proxy.test.js deleted file mode 100644 index b222800..0000000 --- a/lib_openzeppelin_contracts/test/proxy/ERC1967/ERC1967Proxy.test.js +++ /dev/null @@ -1,23 +0,0 @@ -const { ethers } = require('hardhat'); - -const shouldBehaveLikeProxy = require('../Proxy.behaviour'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const fixture = async () => { - const [nonContractAddress] = await ethers.getSigners(); - - const implementation = await ethers.deployContract('DummyImplementation'); - - const createProxy = (implementation, initData, opts) => - ethers.deployContract('ERC1967Proxy', [implementation, initData], opts); - - return { nonContractAddress, implementation, createProxy }; -}; - -describe('ERC1967Proxy', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeProxy(); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/ERC1967/ERC1967Utils.test.js b/lib_openzeppelin_contracts/test/proxy/ERC1967/ERC1967Utils.test.js deleted file mode 100644 index 0890324..0000000 --- a/lib_openzeppelin_contracts/test/proxy/ERC1967/ERC1967Utils.test.js +++ /dev/null @@ -1,162 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getAddressInSlot, setSlot, ImplementationSlot, AdminSlot, BeaconSlot } = require('../../helpers/storage'); - -async function fixture() { - const [, admin, anotherAccount] = await ethers.getSigners(); - - const utils = await ethers.deployContract('$ERC1967Utils'); - const v1 = await ethers.deployContract('DummyImplementation'); - const v2 = await ethers.deployContract('CallReceiverMock'); - - return { admin, anotherAccount, utils, v1, v2 }; -} - -describe('ERC1967Utils', function () { - beforeEach('setup', async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('IMPLEMENTATION_SLOT', function () { - beforeEach('set v1 implementation', async function () { - await setSlot(this.utils, ImplementationSlot, this.v1); - }); - - describe('getImplementation', function () { - it('returns current implementation and matches implementation slot value', async function () { - expect(await this.utils.$getImplementation()).to.equal(this.v1); - expect(await getAddressInSlot(this.utils, ImplementationSlot)).to.equal(this.v1); - }); - }); - - describe('upgradeToAndCall', function () { - it('sets implementation in storage and emits event', async function () { - const newImplementation = this.v2; - const tx = await this.utils.$upgradeToAndCall(newImplementation, '0x'); - - expect(await getAddressInSlot(this.utils, ImplementationSlot)).to.equal(newImplementation); - await expect(tx).to.emit(this.utils, 'Upgraded').withArgs(newImplementation); - }); - - it('reverts when implementation does not contain code', async function () { - await expect(this.utils.$upgradeToAndCall(this.anotherAccount, '0x')) - .to.be.revertedWithCustomError(this.utils, 'ERC1967InvalidImplementation') - .withArgs(this.anotherAccount); - }); - - describe('when data is empty', function () { - it('reverts when value is sent', async function () { - await expect(this.utils.$upgradeToAndCall(this.v2, '0x', { value: 1 })).to.be.revertedWithCustomError( - this.utils, - 'ERC1967NonPayable', - ); - }); - }); - - describe('when data is not empty', function () { - it('delegates a call to the new implementation', async function () { - const initializeData = this.v2.interface.encodeFunctionData('mockFunction'); - const tx = await this.utils.$upgradeToAndCall(this.v2, initializeData); - await expect(tx).to.emit(await ethers.getContractAt('CallReceiverMock', this.utils), 'MockFunctionCalled'); - }); - }); - }); - }); - - describe('ADMIN_SLOT', function () { - beforeEach('set admin', async function () { - await setSlot(this.utils, AdminSlot, this.admin); - }); - - describe('getAdmin', function () { - it('returns current admin and matches admin slot value', async function () { - expect(await this.utils.$getAdmin()).to.equal(this.admin); - expect(await getAddressInSlot(this.utils, AdminSlot)).to.equal(this.admin); - }); - }); - - describe('changeAdmin', function () { - it('sets admin in storage and emits event', async function () { - const newAdmin = this.anotherAccount; - const tx = await this.utils.$changeAdmin(newAdmin); - - expect(await getAddressInSlot(this.utils, AdminSlot)).to.equal(newAdmin); - await expect(tx).to.emit(this.utils, 'AdminChanged').withArgs(this.admin, newAdmin); - }); - - it('reverts when setting the address zero as admin', async function () { - await expect(this.utils.$changeAdmin(ethers.ZeroAddress)) - .to.be.revertedWithCustomError(this.utils, 'ERC1967InvalidAdmin') - .withArgs(ethers.ZeroAddress); - }); - }); - }); - - describe('BEACON_SLOT', function () { - beforeEach('set beacon', async function () { - this.beacon = await ethers.deployContract('UpgradeableBeaconMock', [this.v1]); - await setSlot(this.utils, BeaconSlot, this.beacon); - }); - - describe('getBeacon', function () { - it('returns current beacon and matches beacon slot value', async function () { - expect(await this.utils.$getBeacon()).to.equal(this.beacon); - expect(await getAddressInSlot(this.utils, BeaconSlot)).to.equal(this.beacon); - }); - }); - - describe('upgradeBeaconToAndCall', function () { - it('sets beacon in storage and emits event', async function () { - const newBeacon = await ethers.deployContract('UpgradeableBeaconMock', [this.v2]); - const tx = await this.utils.$upgradeBeaconToAndCall(newBeacon, '0x'); - - expect(await getAddressInSlot(this.utils, BeaconSlot)).to.equal(newBeacon); - await expect(tx).to.emit(this.utils, 'BeaconUpgraded').withArgs(newBeacon); - }); - - it('reverts when beacon does not contain code', async function () { - await expect(this.utils.$upgradeBeaconToAndCall(this.anotherAccount, '0x')) - .to.be.revertedWithCustomError(this.utils, 'ERC1967InvalidBeacon') - .withArgs(this.anotherAccount); - }); - - it("reverts when beacon's implementation does not contain code", async function () { - const newBeacon = await ethers.deployContract('UpgradeableBeaconMock', [this.anotherAccount]); - - await expect(this.utils.$upgradeBeaconToAndCall(newBeacon, '0x')) - .to.be.revertedWithCustomError(this.utils, 'ERC1967InvalidImplementation') - .withArgs(this.anotherAccount); - }); - - describe('when data is empty', function () { - it('reverts when value is sent', async function () { - const newBeacon = await ethers.deployContract('UpgradeableBeaconMock', [this.v2]); - await expect(this.utils.$upgradeBeaconToAndCall(newBeacon, '0x', { value: 1 })).to.be.revertedWithCustomError( - this.utils, - 'ERC1967NonPayable', - ); - }); - }); - - describe('when data is not empty', function () { - it('delegates a call to the new implementation', async function () { - const initializeData = this.v2.interface.encodeFunctionData('mockFunction'); - const newBeacon = await ethers.deployContract('UpgradeableBeaconMock', [this.v2]); - const tx = await this.utils.$upgradeBeaconToAndCall(newBeacon, initializeData); - await expect(tx).to.emit(await ethers.getContractAt('CallReceiverMock', this.utils), 'MockFunctionCalled'); - }); - }); - - describe('reentrant beacon implementation() call', function () { - it('sees the new beacon implementation', async function () { - const newBeacon = await ethers.deployContract('UpgradeableBeaconReentrantMock'); - await expect(this.utils.$upgradeBeaconToAndCall(newBeacon, '0x')) - .to.be.revertedWithCustomError(newBeacon, 'BeaconProxyBeaconSlotAddress') - .withArgs(newBeacon); - }); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/Proxy.behaviour.js b/lib_openzeppelin_contracts/test/proxy/Proxy.behaviour.js deleted file mode 100644 index f459c09..0000000 --- a/lib_openzeppelin_contracts/test/proxy/Proxy.behaviour.js +++ /dev/null @@ -1,185 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -const { getAddressInSlot, ImplementationSlot } = require('../helpers/storage'); - -module.exports = function shouldBehaveLikeProxy() { - it('cannot be initialized with a non-contract address', async function () { - const initializeData = '0x'; - const contractFactory = await ethers.getContractFactory('ERC1967Proxy'); - await expect(this.createProxy(this.nonContractAddress, initializeData)) - .to.be.revertedWithCustomError(contractFactory, 'ERC1967InvalidImplementation') - .withArgs(this.nonContractAddress); - }); - - const assertProxyInitialization = function ({ value, balance }) { - it('sets the implementation address', async function () { - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.implementation); - }); - - it('initializes the proxy', async function () { - const dummy = this.implementation.attach(this.proxy); - expect(await dummy.value()).to.equal(value); - }); - - it('has expected balance', async function () { - expect(await ethers.provider.getBalance(this.proxy)).to.equal(balance); - }); - }; - - describe('without initialization', function () { - const initializeData = '0x'; - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createProxy(this.implementation, initializeData); - }); - - assertProxyInitialization({ value: 0n, balance: 0n }); - }); - - describe('when sending some balance', function () { - const value = 10n ** 5n; - - it('reverts', async function () { - await expect(this.createProxy(this.implementation, initializeData, { value })).to.be.reverted; - }); - }); - }); - - describe('initialization without parameters', function () { - describe('non payable', function () { - const expectedInitializedValue = 10n; - - beforeEach(function () { - this.initializeData = this.implementation.interface.encodeFunctionData('initializeNonPayable'); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createProxy(this.implementation, this.initializeData); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0n, - }); - }); - - describe('when sending some balance', function () { - const value = 10n ** 5n; - - it('reverts', async function () { - await expect(this.createProxy(this.implementation, this.initializeData, { value })).to.be.reverted; - }); - }); - }); - - describe('payable', function () { - const expectedInitializedValue = 100n; - - beforeEach(function () { - this.initializeData = this.implementation.interface.encodeFunctionData('initializePayable'); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createProxy(this.implementation, this.initializeData); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0n, - }); - }); - - describe('when sending some balance', function () { - const value = 10e5; - - beforeEach('creating proxy', async function () { - this.proxy = await this.createProxy(this.implementation, this.initializeData, { value }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: value, - }); - }); - }); - }); - - describe('initialization with parameters', function () { - describe('non payable', function () { - const expectedInitializedValue = 10n; - - beforeEach(function () { - this.initializeData = this.implementation.interface.encodeFunctionData('initializeNonPayableWithValue', [ - expectedInitializedValue, - ]); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createProxy(this.implementation, this.initializeData); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0, - }); - }); - - describe('when sending some balance', function () { - const value = 10e5; - - it('reverts', async function () { - await expect(this.createProxy(this.implementation, this.initializeData, { value })).to.be.reverted; - }); - }); - }); - - describe('payable', function () { - const expectedInitializedValue = 42n; - - beforeEach(function () { - this.initializeData = this.implementation.interface.encodeFunctionData('initializePayableWithValue', [ - expectedInitializedValue, - ]); - }); - - describe('when not sending balance', function () { - beforeEach('creating proxy', async function () { - this.proxy = await this.createProxy(this.implementation, this.initializeData); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: 0n, - }); - }); - - describe('when sending some balance', function () { - const value = 10n ** 5n; - - beforeEach('creating proxy', async function () { - this.proxy = await this.createProxy(this.implementation, this.initializeData, { value }); - }); - - assertProxyInitialization({ - value: expectedInitializedValue, - balance: value, - }); - }); - }); - - describe('reverting initialization', function () { - beforeEach(function () { - this.initializeData = this.implementation.interface.encodeFunctionData('reverts'); - }); - - it('reverts', async function () { - await expect(this.createProxy(this.implementation, this.initializeData)).to.be.reverted; - }); - }); - }); -}; diff --git a/lib_openzeppelin_contracts/test/proxy/beacon/BeaconProxy.test.js b/lib_openzeppelin_contracts/test/proxy/beacon/BeaconProxy.test.js deleted file mode 100644 index 0a08784..0000000 --- a/lib_openzeppelin_contracts/test/proxy/beacon/BeaconProxy.test.js +++ /dev/null @@ -1,141 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getAddressInSlot, BeaconSlot } = require('../../helpers/storage'); - -async function fixture() { - const [admin, other] = await ethers.getSigners(); - - const v1 = await ethers.deployContract('DummyImplementation'); - const v2 = await ethers.deployContract('DummyImplementationV2'); - const factory = await ethers.getContractFactory('BeaconProxy'); - const beacon = await ethers.deployContract('UpgradeableBeacon', [v1, admin]); - - const newBeaconProxy = (beacon, data, opts = {}) => factory.deploy(beacon, data, opts); - - return { admin, other, factory, beacon, v1, v2, newBeaconProxy }; -} - -describe('BeaconProxy', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('bad beacon is not accepted', function () { - it('non-contract beacon', async function () { - const notBeacon = this.other; - - await expect(this.newBeaconProxy(notBeacon, '0x')) - .to.be.revertedWithCustomError(this.factory, 'ERC1967InvalidBeacon') - .withArgs(notBeacon); - }); - - it('non-compliant beacon', async function () { - const badBeacon = await ethers.deployContract('BadBeaconNoImpl'); - - // BadBeaconNoImpl does not provide `implementation()` has no fallback. - // This causes ERC1967Utils._setBeacon to revert. - await expect(this.newBeaconProxy(badBeacon, '0x')).to.be.revertedWithoutReason(); - }); - - it('non-contract implementation', async function () { - const badBeacon = await ethers.deployContract('BadBeaconNotContract'); - - await expect(this.newBeaconProxy(badBeacon, '0x')) - .to.be.revertedWithCustomError(this.factory, 'ERC1967InvalidImplementation') - .withArgs(await badBeacon.implementation()); - }); - }); - - describe('initialization', function () { - async function assertInitialized({ value, balance }) { - const beaconAddress = await getAddressInSlot(this.proxy, BeaconSlot); - expect(beaconAddress).to.equal(this.beacon); - - const dummy = this.v1.attach(this.proxy); - expect(await dummy.value()).to.equal(value); - - expect(await ethers.provider.getBalance(this.proxy)).to.equal(balance); - } - - it('no initialization', async function () { - this.proxy = await this.newBeaconProxy(this.beacon, '0x'); - await assertInitialized.bind(this)({ value: 0n, balance: 0n }); - }); - - it('non-payable initialization', async function () { - const value = 55n; - const data = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value]); - - this.proxy = await this.newBeaconProxy(this.beacon, data); - await assertInitialized.bind(this)({ value, balance: 0n }); - }); - - it('payable initialization', async function () { - const value = 55n; - const data = this.v1.interface.encodeFunctionData('initializePayableWithValue', [value]); - const balance = 100n; - - this.proxy = await this.newBeaconProxy(this.beacon, data, { value: balance }); - await assertInitialized.bind(this)({ value, balance }); - }); - - it('reverting initialization due to value', async function () { - await expect(this.newBeaconProxy(this.beacon, '0x', { value: 1n })).to.be.revertedWithCustomError( - this.factory, - 'ERC1967NonPayable', - ); - }); - - it('reverting initialization function', async function () { - const data = this.v1.interface.encodeFunctionData('reverts'); - await expect(this.newBeaconProxy(this.beacon, data)).to.be.revertedWith('DummyImplementation reverted'); - }); - }); - - describe('upgrade', function () { - it('upgrade a proxy by upgrading its beacon', async function () { - const value = 10n; - const data = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value]); - const proxy = await this.newBeaconProxy(this.beacon, data).then(instance => this.v1.attach(instance)); - - // test initial values - expect(await proxy.value()).to.equal(value); - - // test initial version - expect(await proxy.version()).to.equal('V1'); - - // upgrade beacon - await this.beacon.connect(this.admin).upgradeTo(this.v2); - - // test upgraded version - expect(await proxy.version()).to.equal('V2'); - }); - - it('upgrade 2 proxies by upgrading shared beacon', async function () { - const value1 = 10n; - const data1 = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value1]); - const proxy1 = await this.newBeaconProxy(this.beacon, data1).then(instance => this.v1.attach(instance)); - - const value2 = 42n; - const data2 = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [value2]); - const proxy2 = await this.newBeaconProxy(this.beacon, data2).then(instance => this.v1.attach(instance)); - - // test initial values - expect(await proxy1.value()).to.equal(value1); - expect(await proxy2.value()).to.equal(value2); - - // test initial version - expect(await proxy1.version()).to.equal('V1'); - expect(await proxy2.version()).to.equal('V1'); - - // upgrade beacon - await this.beacon.connect(this.admin).upgradeTo(this.v2); - - // test upgraded version - expect(await proxy1.version()).to.equal('V2'); - expect(await proxy2.version()).to.equal('V2'); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/beacon/UpgradeableBeacon.test.js b/lib_openzeppelin_contracts/test/proxy/beacon/UpgradeableBeacon.test.js deleted file mode 100644 index 2da7d0a..0000000 --- a/lib_openzeppelin_contracts/test/proxy/beacon/UpgradeableBeacon.test.js +++ /dev/null @@ -1,55 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [admin, other] = await ethers.getSigners(); - - const v1 = await ethers.deployContract('Implementation1'); - const v2 = await ethers.deployContract('Implementation2'); - const beacon = await ethers.deployContract('UpgradeableBeacon', [v1, admin]); - - return { admin, other, beacon, v1, v2 }; -} - -describe('UpgradeableBeacon', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('cannot be created with non-contract implementation', async function () { - await expect(ethers.deployContract('UpgradeableBeacon', [this.other, this.admin])) - .to.be.revertedWithCustomError(this.beacon, 'BeaconInvalidImplementation') - .withArgs(this.other); - }); - - describe('once deployed', function () { - it('emits Upgraded event to the first implementation', async function () { - await expect(this.beacon.deploymentTransaction()).to.emit(this.beacon, 'Upgraded').withArgs(this.v1); - }); - - it('returns implementation', async function () { - expect(await this.beacon.implementation()).to.equal(this.v1); - }); - - it('can be upgraded by the admin', async function () { - await expect(this.beacon.connect(this.admin).upgradeTo(this.v2)) - .to.emit(this.beacon, 'Upgraded') - .withArgs(this.v2); - - expect(await this.beacon.implementation()).to.equal(this.v2); - }); - - it('cannot be upgraded to a non-contract', async function () { - await expect(this.beacon.connect(this.admin).upgradeTo(this.other)) - .to.be.revertedWithCustomError(this.beacon, 'BeaconInvalidImplementation') - .withArgs(this.other); - }); - - it('cannot be upgraded by other account', async function () { - await expect(this.beacon.connect(this.other).upgradeTo(this.v2)) - .to.be.revertedWithCustomError(this.beacon, 'OwnableUnauthorizedAccount') - .withArgs(this.other); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/transparent/ProxyAdmin.test.js b/lib_openzeppelin_contracts/test/proxy/transparent/ProxyAdmin.test.js deleted file mode 100644 index df430d4..0000000 --- a/lib_openzeppelin_contracts/test/proxy/transparent/ProxyAdmin.test.js +++ /dev/null @@ -1,82 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getAddressInSlot, ImplementationSlot } = require('../../helpers/storage'); - -async function fixture() { - const [admin, other] = await ethers.getSigners(); - - const v1 = await ethers.deployContract('DummyImplementation'); - const v2 = await ethers.deployContract('DummyImplementationV2'); - - const proxy = await ethers - .deployContract('TransparentUpgradeableProxy', [v1, admin, '0x']) - .then(instance => ethers.getContractAt('ITransparentUpgradeableProxy', instance)); - - const proxyAdmin = await ethers.getContractAt( - 'ProxyAdmin', - ethers.getCreateAddress({ from: proxy.target, nonce: 1n }), - ); - - return { admin, other, v1, v2, proxy, proxyAdmin }; -} - -describe('ProxyAdmin', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('has an owner', async function () { - expect(await this.proxyAdmin.owner()).to.equal(this.admin); - }); - - it('has an interface version', async function () { - expect(await this.proxyAdmin.UPGRADE_INTERFACE_VERSION()).to.equal('5.0.0'); - }); - - describe('without data', function () { - describe('with unauthorized account', function () { - it('fails to upgrade', async function () { - await expect(this.proxyAdmin.connect(this.other).upgradeAndCall(this.proxy, this.v2, '0x')) - .to.be.revertedWithCustomError(this.proxyAdmin, 'OwnableUnauthorizedAccount') - .withArgs(this.other); - }); - }); - - describe('with authorized account', function () { - it('upgrades implementation', async function () { - await this.proxyAdmin.connect(this.admin).upgradeAndCall(this.proxy, this.v2, '0x'); - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.v2); - }); - }); - }); - - describe('with data', function () { - describe('with unauthorized account', function () { - it('fails to upgrade', async function () { - const data = this.v1.interface.encodeFunctionData('initializeNonPayableWithValue', [1337n]); - await expect(this.proxyAdmin.connect(this.other).upgradeAndCall(this.proxy, this.v2, data)) - .to.be.revertedWithCustomError(this.proxyAdmin, 'OwnableUnauthorizedAccount') - .withArgs(this.other); - }); - }); - - describe('with authorized account', function () { - describe('with invalid callData', function () { - it('fails to upgrade', async function () { - const data = '0x12345678'; - await expect(this.proxyAdmin.connect(this.admin).upgradeAndCall(this.proxy, this.v2, data)).to.be.reverted; - }); - }); - - describe('with valid callData', function () { - it('upgrades implementation', async function () { - const data = this.v2.interface.encodeFunctionData('initializeNonPayableWithValue', [1337n]); - await this.proxyAdmin.connect(this.admin).upgradeAndCall(this.proxy, this.v2, data); - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.v2); - }); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js b/lib_openzeppelin_contracts/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js deleted file mode 100644 index d90bd56..0000000 --- a/lib_openzeppelin_contracts/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +++ /dev/null @@ -1,357 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -const { impersonate } = require('../../helpers/account'); -const { getAddressInSlot, ImplementationSlot, AdminSlot } = require('../../helpers/storage'); - -// createProxy, initialOwner, accounts -module.exports = function shouldBehaveLikeTransparentUpgradeableProxy() { - before(async function () { - const implementationV0 = await ethers.deployContract('DummyImplementation'); - const implementationV1 = await ethers.deployContract('DummyImplementation'); - - const createProxyWithImpersonatedProxyAdmin = async (logic, initData, opts = undefined) => { - const [proxy, tx] = await this.createProxy(logic, initData, opts).then(instance => - Promise.all([ethers.getContractAt('ITransparentUpgradeableProxy', instance), instance.deploymentTransaction()]), - ); - - const proxyAdmin = await ethers.getContractAt( - 'ProxyAdmin', - ethers.getCreateAddress({ from: proxy.target, nonce: 1n }), - ); - const proxyAdminAsSigner = await proxyAdmin.getAddress().then(impersonate); - - return { - instance: logic.attach(proxy.target), // attaching proxy directly works well for everything except for event resolution - proxy, - proxyAdmin, - proxyAdminAsSigner, - tx, - }; - }; - - Object.assign(this, { - implementationV0, - implementationV1, - createProxyWithImpersonatedProxyAdmin, - }); - }); - - beforeEach(async function () { - Object.assign(this, await this.createProxyWithImpersonatedProxyAdmin(this.implementationV0, '0x')); - }); - - describe('implementation', function () { - it('returns the current implementation address', async function () { - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.implementationV0); - }); - - it('delegates to the implementation', async function () { - expect(await this.instance.get()).to.be.true; - }); - }); - - describe('proxy admin', function () { - it('emits AdminChanged event during construction', async function () { - await expect(this.tx).to.emit(this.proxy, 'AdminChanged').withArgs(ethers.ZeroAddress, this.proxyAdmin); - }); - - it('sets the proxy admin in storage with the correct initial owner', async function () { - expect(await getAddressInSlot(this.proxy, AdminSlot)).to.equal(this.proxyAdmin); - - expect(await this.proxyAdmin.owner()).to.equal(this.owner); - }); - - it('can overwrite the admin by the implementation', async function () { - await this.instance.unsafeOverrideAdmin(this.other); - - const ERC1967AdminSlotValue = await getAddressInSlot(this.proxy, AdminSlot); - expect(ERC1967AdminSlotValue).to.equal(this.other); - expect(ERC1967AdminSlotValue).to.not.equal(this.proxyAdmin); - - // Still allows previous admin to execute admin operations - await expect(this.proxy.connect(this.proxyAdminAsSigner).upgradeToAndCall(this.implementationV1, '0x')) - .to.emit(this.proxy, 'Upgraded') - .withArgs(this.implementationV1); - }); - }); - - describe('upgradeToAndCall', function () { - describe('without migrations', function () { - beforeEach(async function () { - this.behavior = await ethers.deployContract('InitializableMock'); - }); - - describe('when the call does not fail', function () { - beforeEach(function () { - this.initializeData = this.behavior.interface.encodeFunctionData('initializeWithX', [42n]); - }); - - describe('when the sender is the admin', function () { - const value = 10n ** 5n; - - beforeEach(async function () { - this.tx = await this.proxy - .connect(this.proxyAdminAsSigner) - .upgradeToAndCall(this.behavior, this.initializeData, { - value, - }); - }); - - it('upgrades to the requested implementation', async function () { - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.behavior); - }); - - it('emits an event', async function () { - await expect(this.tx).to.emit(this.proxy, 'Upgraded').withArgs(this.behavior); - }); - - it('calls the initializer function', async function () { - expect(await this.behavior.attach(this.proxy).x()).to.equal(42n); - }); - - it('sends given value to the proxy', async function () { - expect(await ethers.provider.getBalance(this.proxy)).to.equal(value); - }); - - it('uses the storage of the proxy', async function () { - // storage layout should look as follows: - // - 0: Initializable storage ++ initializerRan ++ onlyInitializingRan - // - 1: x - expect(await ethers.provider.getStorage(this.proxy, 1n)).to.equal(42n); - }); - }); - - describe('when the sender is not the admin', function () { - it('reverts', async function () { - await expect(this.proxy.connect(this.other).upgradeToAndCall(this.behavior, this.initializeData)).to.be - .reverted; - }); - }); - }); - - describe('when the call does fail', function () { - beforeEach(function () { - this.initializeData = this.behavior.interface.encodeFunctionData('fail'); - }); - - it('reverts', async function () { - await expect(this.proxy.connect(this.proxyAdminAsSigner).upgradeToAndCall(this.behavior, this.initializeData)) - .to.be.reverted; - }); - }); - }); - - describe('with migrations', function () { - describe('when the sender is the admin', function () { - const value = 10n ** 5n; - - describe('when upgrading to V1', function () { - beforeEach(async function () { - this.behaviorV1 = await ethers.deployContract('MigratableMockV1'); - const v1MigrationData = this.behaviorV1.interface.encodeFunctionData('initialize', [42n]); - - this.balancePreviousV1 = await ethers.provider.getBalance(this.proxy); - this.tx = await this.proxy - .connect(this.proxyAdminAsSigner) - .upgradeToAndCall(this.behaviorV1, v1MigrationData, { - value, - }); - }); - - it('upgrades to the requested version and emits an event', async function () { - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.behaviorV1); - - await expect(this.tx).to.emit(this.proxy, 'Upgraded').withArgs(this.behaviorV1); - }); - - it("calls the 'initialize' function and sends given value to the proxy", async function () { - expect(await this.behaviorV1.attach(this.proxy).x()).to.equal(42n); - expect(await ethers.provider.getBalance(this.proxy)).to.equal(this.balancePreviousV1 + value); - }); - - describe('when upgrading to V2', function () { - beforeEach(async function () { - this.behaviorV2 = await ethers.deployContract('MigratableMockV2'); - const v2MigrationData = this.behaviorV2.interface.encodeFunctionData('migrate', [10n, 42n]); - - this.balancePreviousV2 = await ethers.provider.getBalance(this.proxy); - this.tx = await this.proxy - .connect(this.proxyAdminAsSigner) - .upgradeToAndCall(this.behaviorV2, v2MigrationData, { - value, - }); - }); - - it('upgrades to the requested version and emits an event', async function () { - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.behaviorV2); - - await expect(this.tx).to.emit(this.proxy, 'Upgraded').withArgs(this.behaviorV2); - }); - - it("calls the 'migrate' function and sends given value to the proxy", async function () { - expect(await this.behaviorV2.attach(this.proxy).x()).to.equal(10n); - expect(await this.behaviorV2.attach(this.proxy).y()).to.equal(42n); - expect(await ethers.provider.getBalance(this.proxy)).to.equal(this.balancePreviousV2 + value); - }); - - describe('when upgrading to V3', function () { - beforeEach(async function () { - this.behaviorV3 = await ethers.deployContract('MigratableMockV3'); - const v3MigrationData = this.behaviorV3.interface.encodeFunctionData('migrate()'); - - this.balancePreviousV3 = await ethers.provider.getBalance(this.proxy); - this.tx = await this.proxy - .connect(this.proxyAdminAsSigner) - .upgradeToAndCall(this.behaviorV3, v3MigrationData, { - value, - }); - }); - - it('upgrades to the requested version and emits an event', async function () { - expect(await getAddressInSlot(this.proxy, ImplementationSlot)).to.equal(this.behaviorV3); - - await expect(this.tx).to.emit(this.proxy, 'Upgraded').withArgs(this.behaviorV3); - }); - - it("calls the 'migrate' function and sends given value to the proxy", async function () { - expect(await this.behaviorV3.attach(this.proxy).x()).to.equal(42n); - expect(await this.behaviorV3.attach(this.proxy).y()).to.equal(10n); - expect(await ethers.provider.getBalance(this.proxy)).to.equal(this.balancePreviousV3 + value); - }); - }); - }); - }); - }); - - describe('when the sender is not the admin', function () { - it('reverts', async function () { - const behaviorV1 = await ethers.deployContract('MigratableMockV1'); - const v1MigrationData = behaviorV1.interface.encodeFunctionData('initialize', [42n]); - await expect(this.proxy.connect(this.other).upgradeToAndCall(behaviorV1, v1MigrationData)).to.be.reverted; - }); - }); - }); - }); - - describe('transparent proxy', function () { - beforeEach('creating proxy', async function () { - this.clashingImplV0 = await ethers.deployContract('ClashingImplementation'); - this.clashingImplV1 = await ethers.deployContract('ClashingImplementation'); - - Object.assign(this, await this.createProxyWithImpersonatedProxyAdmin(this.clashingImplV0, '0x')); - }); - - it('proxy admin cannot call delegated functions', async function () { - const interface = await ethers.getContractFactory('TransparentUpgradeableProxy'); - - await expect(this.instance.connect(this.proxyAdminAsSigner).delegatedFunction()).to.be.revertedWithCustomError( - interface, - 'ProxyDeniedAdminAccess', - ); - }); - - describe('when function names clash', function () { - it('executes the proxy function if the sender is the admin', async function () { - await expect(this.proxy.connect(this.proxyAdminAsSigner).upgradeToAndCall(this.clashingImplV1, '0x')) - .to.emit(this.proxy, 'Upgraded') - .withArgs(this.clashingImplV1); - }); - - it('delegates the call to implementation when sender is not the admin', async function () { - await expect(this.proxy.connect(this.other).upgradeToAndCall(this.clashingImplV1, '0x')) - .to.emit(this.instance, 'ClashingImplementationCall') - .to.not.emit(this.proxy, 'Upgraded'); - }); - }); - }); - - describe('regression', function () { - const initializeData = '0x'; - - it('should add new function', async function () { - const impl1 = await ethers.deployContract('Implementation1'); - const impl2 = await ethers.deployContract('Implementation2'); - const { instance, proxy, proxyAdminAsSigner } = await this.createProxyWithImpersonatedProxyAdmin( - impl1, - initializeData, - ); - - await instance.setValue(42n); - - // `getValue` is not available in impl1 - await expect(impl2.attach(instance).getValue()).to.be.reverted; - - // do upgrade - await proxy.connect(proxyAdminAsSigner).upgradeToAndCall(impl2, '0x'); - - // `getValue` is available in impl2 - expect(await impl2.attach(instance).getValue()).to.equal(42n); - }); - - it('should remove function', async function () { - const impl1 = await ethers.deployContract('Implementation1'); - const impl2 = await ethers.deployContract('Implementation2'); - const { instance, proxy, proxyAdminAsSigner } = await this.createProxyWithImpersonatedProxyAdmin( - impl2, - initializeData, - ); - - await instance.setValue(42n); - - // `getValue` is available in impl2 - expect(await impl2.attach(instance).getValue()).to.equal(42n); - - // do downgrade - await proxy.connect(proxyAdminAsSigner).upgradeToAndCall(impl1, '0x'); - - // `getValue` is not available in impl1 - await expect(impl2.attach(instance).getValue()).to.be.reverted; - }); - - it('should change function signature', async function () { - const impl1 = await ethers.deployContract('Implementation1'); - const impl3 = await ethers.deployContract('Implementation3'); - const { instance, proxy, proxyAdminAsSigner } = await this.createProxyWithImpersonatedProxyAdmin( - impl1, - initializeData, - ); - - await instance.setValue(42n); - - await proxy.connect(proxyAdminAsSigner).upgradeToAndCall(impl3, '0x'); - - expect(await impl3.attach(instance).getValue(8n)).to.equal(50n); - }); - - it('should add fallback function', async function () { - const impl1 = await ethers.deployContract('Implementation1'); - const impl4 = await ethers.deployContract('Implementation4'); - const { instance, proxy, proxyAdminAsSigner } = await this.createProxyWithImpersonatedProxyAdmin( - impl1, - initializeData, - ); - - await proxy.connect(proxyAdminAsSigner).upgradeToAndCall(impl4, '0x'); - - await this.other.sendTransaction({ to: proxy }); - - expect(await impl4.attach(instance).getValue()).to.equal(1n); - }); - - it('should remove fallback function', async function () { - const impl2 = await ethers.deployContract('Implementation2'); - const impl4 = await ethers.deployContract('Implementation4'); - const { instance, proxy, proxyAdminAsSigner } = await this.createProxyWithImpersonatedProxyAdmin( - impl4, - initializeData, - ); - - await proxy.connect(proxyAdminAsSigner).upgradeToAndCall(impl2, '0x'); - - await expect(this.other.sendTransaction({ to: proxy })).to.be.reverted; - - expect(await impl2.attach(instance).getValue()).to.equal(0n); - }); - }); -}; diff --git a/lib_openzeppelin_contracts/test/proxy/transparent/TransparentUpgradeableProxy.test.js b/lib_openzeppelin_contracts/test/proxy/transparent/TransparentUpgradeableProxy.test.js deleted file mode 100644 index 61e1801..0000000 --- a/lib_openzeppelin_contracts/test/proxy/transparent/TransparentUpgradeableProxy.test.js +++ /dev/null @@ -1,28 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const shouldBehaveLikeProxy = require('../Proxy.behaviour'); -const shouldBehaveLikeTransparentUpgradeableProxy = require('./TransparentUpgradeableProxy.behaviour'); - -async function fixture() { - const [owner, other, ...accounts] = await ethers.getSigners(); - - const implementation = await ethers.deployContract('DummyImplementation'); - - const createProxy = function (logic, initData, opts = undefined) { - return ethers.deployContract('TransparentUpgradeableProxy', [logic, owner, initData], opts); - }; - - return { nonContractAddress: owner, owner, other, accounts, implementation, createProxy }; -} - -describe('TransparentUpgradeableProxy', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeProxy(); - - // createProxy, owner, otherAccounts - shouldBehaveLikeTransparentUpgradeableProxy(); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/utils/Initializable.test.js b/lib_openzeppelin_contracts/test/proxy/utils/Initializable.test.js deleted file mode 100644 index 6bf213f..0000000 --- a/lib_openzeppelin_contracts/test/proxy/utils/Initializable.test.js +++ /dev/null @@ -1,216 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { MAX_UINT64 } = require('../../helpers/constants'); - -describe('Initializable', function () { - describe('basic testing without inheritance', function () { - beforeEach('deploying', async function () { - this.mock = await ethers.deployContract('InitializableMock'); - }); - - describe('before initialize', function () { - it('initializer has not run', async function () { - expect(await this.mock.initializerRan()).to.be.false; - }); - - it('_initializing returns false before initialization', async function () { - expect(await this.mock.isInitializing()).to.be.false; - }); - }); - - describe('after initialize', function () { - beforeEach('initializing', async function () { - await this.mock.initialize(); - }); - - it('initializer has run', async function () { - expect(await this.mock.initializerRan()).to.be.true; - }); - - it('_initializing returns false after initialization', async function () { - expect(await this.mock.isInitializing()).to.be.false; - }); - - it('initializer does not run again', async function () { - await expect(this.mock.initialize()).to.be.revertedWithCustomError(this.mock, 'InvalidInitialization'); - }); - }); - - describe('nested under an initializer', function () { - it('initializer modifier reverts', async function () { - await expect(this.mock.initializerNested()).to.be.revertedWithCustomError(this.mock, 'InvalidInitialization'); - }); - - it('onlyInitializing modifier succeeds', async function () { - await this.mock.onlyInitializingNested(); - expect(await this.mock.onlyInitializingRan()).to.be.true; - }); - }); - - it('cannot call onlyInitializable function outside the scope of an initializable function', async function () { - await expect(this.mock.initializeOnlyInitializing()).to.be.revertedWithCustomError(this.mock, 'NotInitializing'); - }); - }); - - it('nested initializer can run during construction', async function () { - const mock = await ethers.deployContract('ConstructorInitializableMock'); - expect(await mock.initializerRan()).to.be.true; - expect(await mock.onlyInitializingRan()).to.be.true; - }); - - it('multiple constructor levels can be initializers', async function () { - const mock = await ethers.deployContract('ChildConstructorInitializableMock'); - expect(await mock.initializerRan()).to.be.true; - expect(await mock.childInitializerRan()).to.be.true; - expect(await mock.onlyInitializingRan()).to.be.true; - }); - - describe('reinitialization', function () { - beforeEach('deploying', async function () { - this.mock = await ethers.deployContract('ReinitializerMock'); - }); - - it('can reinitialize', async function () { - expect(await this.mock.counter()).to.equal(0n); - await this.mock.initialize(); - expect(await this.mock.counter()).to.equal(1n); - await this.mock.reinitialize(2); - expect(await this.mock.counter()).to.equal(2n); - await this.mock.reinitialize(3); - expect(await this.mock.counter()).to.equal(3n); - }); - - it('can jump multiple steps', async function () { - expect(await this.mock.counter()).to.equal(0n); - await this.mock.initialize(); - expect(await this.mock.counter()).to.equal(1n); - await this.mock.reinitialize(128); - expect(await this.mock.counter()).to.equal(2n); - }); - - it('cannot nest reinitializers', async function () { - expect(await this.mock.counter()).to.equal(0n); - await expect(this.mock.nestedReinitialize(2, 2)).to.be.revertedWithCustomError( - this.mock, - 'InvalidInitialization', - ); - await expect(this.mock.nestedReinitialize(2, 3)).to.be.revertedWithCustomError( - this.mock, - 'InvalidInitialization', - ); - await expect(this.mock.nestedReinitialize(3, 2)).to.be.revertedWithCustomError( - this.mock, - 'InvalidInitialization', - ); - }); - - it('can chain reinitializers', async function () { - expect(await this.mock.counter()).to.equal(0n); - await this.mock.chainReinitialize(2, 3); - expect(await this.mock.counter()).to.equal(2n); - }); - - it('_getInitializedVersion returns right version', async function () { - await this.mock.initialize(); - expect(await this.mock.getInitializedVersion()).to.equal(1n); - await this.mock.reinitialize(12); - expect(await this.mock.getInitializedVersion()).to.equal(12n); - }); - - describe('contract locking', function () { - it('prevents initialization', async function () { - await this.mock.disableInitializers(); - await expect(this.mock.initialize()).to.be.revertedWithCustomError(this.mock, 'InvalidInitialization'); - }); - - it('prevents re-initialization', async function () { - await this.mock.disableInitializers(); - await expect(this.mock.reinitialize(255n)).to.be.revertedWithCustomError(this.mock, 'InvalidInitialization'); - }); - - it('can lock contract after initialization', async function () { - await this.mock.initialize(); - await this.mock.disableInitializers(); - await expect(this.mock.reinitialize(255n)).to.be.revertedWithCustomError(this.mock, 'InvalidInitialization'); - }); - }); - }); - - describe('events', function () { - it('constructor initialization emits event', async function () { - const mock = await ethers.deployContract('ConstructorInitializableMock'); - await expect(mock.deploymentTransaction()).to.emit(mock, 'Initialized').withArgs(1n); - }); - - it('initialization emits event', async function () { - const mock = await ethers.deployContract('ReinitializerMock'); - await expect(mock.initialize()).to.emit(mock, 'Initialized').withArgs(1n); - }); - - it('reinitialization emits event', async function () { - const mock = await ethers.deployContract('ReinitializerMock'); - await expect(mock.reinitialize(128)).to.emit(mock, 'Initialized').withArgs(128n); - }); - - it('chained reinitialization emits multiple events', async function () { - const mock = await ethers.deployContract('ReinitializerMock'); - - await expect(mock.chainReinitialize(2, 3)) - .to.emit(mock, 'Initialized') - .withArgs(2n) - .to.emit(mock, 'Initialized') - .withArgs(3n); - }); - }); - - describe('complex testing with inheritance', function () { - const mother = 12n; - const gramps = '56'; - const father = 34n; - const child = 78n; - - beforeEach('deploying', async function () { - this.mock = await ethers.deployContract('SampleChild'); - await this.mock.initialize(mother, gramps, father, child); - }); - - it('initializes human', async function () { - expect(await this.mock.isHuman()).to.be.true; - }); - - it('initializes mother', async function () { - expect(await this.mock.mother()).to.equal(mother); - }); - - it('initializes gramps', async function () { - expect(await this.mock.gramps()).to.equal(gramps); - }); - - it('initializes father', async function () { - expect(await this.mock.father()).to.equal(father); - }); - - it('initializes child', async function () { - expect(await this.mock.child()).to.equal(child); - }); - }); - - describe('disabling initialization', function () { - it('old and new patterns in bad sequence', async function () { - const DisableBad1 = await ethers.getContractFactory('DisableBad1'); - await expect(DisableBad1.deploy()).to.be.revertedWithCustomError(DisableBad1, 'InvalidInitialization'); - - const DisableBad2 = await ethers.getContractFactory('DisableBad2'); - await expect(DisableBad2.deploy()).to.be.revertedWithCustomError(DisableBad2, 'InvalidInitialization'); - }); - - it('old and new patterns in good sequence', async function () { - const ok = await ethers.deployContract('DisableOk'); - await expect(ok.deploymentTransaction()) - .to.emit(ok, 'Initialized') - .withArgs(1n) - .to.emit(ok, 'Initialized') - .withArgs(MAX_UINT64); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/proxy/utils/UUPSUpgradeable.test.js b/lib_openzeppelin_contracts/test/proxy/utils/UUPSUpgradeable.test.js deleted file mode 100644 index 17f8657..0000000 --- a/lib_openzeppelin_contracts/test/proxy/utils/UUPSUpgradeable.test.js +++ /dev/null @@ -1,120 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getAddressInSlot, ImplementationSlot } = require('../../helpers/storage'); - -async function fixture() { - const implInitial = await ethers.deployContract('UUPSUpgradeableMock'); - const implUpgradeOk = await ethers.deployContract('UUPSUpgradeableMock'); - const implUpgradeUnsafe = await ethers.deployContract('UUPSUpgradeableUnsafeMock'); - const implUpgradeNonUUPS = await ethers.deployContract('NonUpgradeableMock'); - const implUnsupportedUUID = await ethers.deployContract('UUPSUnsupportedProxiableUUID'); - // Used for testing non ERC1967 compliant proxies (clones are proxies that don't use the ERC1967 implementation slot) - const cloneFactory = await ethers.deployContract('$Clones'); - - const instance = await ethers - .deployContract('ERC1967Proxy', [implInitial, '0x']) - .then(proxy => implInitial.attach(proxy.target)); - - return { - implInitial, - implUpgradeOk, - implUpgradeUnsafe, - implUpgradeNonUUPS, - implUnsupportedUUID, - cloneFactory, - instance, - }; -} - -describe('UUPSUpgradeable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('has an interface version', async function () { - expect(await this.instance.UPGRADE_INTERFACE_VERSION()).to.equal('5.0.0'); - }); - - it('upgrade to upgradeable implementation', async function () { - await expect(this.instance.upgradeToAndCall(this.implUpgradeOk, '0x')) - .to.emit(this.instance, 'Upgraded') - .withArgs(this.implUpgradeOk); - - expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.equal(this.implUpgradeOk); - }); - - it('upgrade to upgradeable implementation with call', async function () { - expect(await this.instance.current()).to.equal(0n); - - await expect( - this.instance.upgradeToAndCall(this.implUpgradeOk, this.implUpgradeOk.interface.encodeFunctionData('increment')), - ) - .to.emit(this.instance, 'Upgraded') - .withArgs(this.implUpgradeOk); - - expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.equal(this.implUpgradeOk); - - expect(await this.instance.current()).to.equal(1n); - }); - - it('calling upgradeTo on the implementation reverts', async function () { - await expect(this.implInitial.upgradeToAndCall(this.implUpgradeOk, '0x')).to.be.revertedWithCustomError( - this.implInitial, - 'UUPSUnauthorizedCallContext', - ); - }); - - it('calling upgradeToAndCall on the implementation reverts', async function () { - await expect( - this.implInitial.upgradeToAndCall( - this.implUpgradeOk, - this.implUpgradeOk.interface.encodeFunctionData('increment'), - ), - ).to.be.revertedWithCustomError(this.implUpgradeOk, 'UUPSUnauthorizedCallContext'); - }); - - it('calling upgradeToAndCall from a contract that is not an ERC1967 proxy (with the right implementation) reverts', async function () { - const instance = await this.cloneFactory.$clone - .staticCall(this.implUpgradeOk) - .then(address => this.implInitial.attach(address)); - await this.cloneFactory.$clone(this.implUpgradeOk); - - await expect(instance.upgradeToAndCall(this.implUpgradeUnsafe, '0x')).to.be.revertedWithCustomError( - instance, - 'UUPSUnauthorizedCallContext', - ); - }); - - it('rejects upgrading to an unsupported UUID', async function () { - await expect(this.instance.upgradeToAndCall(this.implUnsupportedUUID, '0x')) - .to.be.revertedWithCustomError(this.instance, 'UUPSUnsupportedProxiableUUID') - .withArgs(ethers.id('invalid UUID')); - }); - - it('upgrade to and unsafe upgradeable implementation', async function () { - await expect(this.instance.upgradeToAndCall(this.implUpgradeUnsafe, '0x')) - .to.emit(this.instance, 'Upgraded') - .withArgs(this.implUpgradeUnsafe); - - expect(await getAddressInSlot(this.instance, ImplementationSlot)).to.equal(this.implUpgradeUnsafe); - }); - - // delegate to a non existing upgradeTo function causes a low level revert - it('reject upgrade to non uups implementation', async function () { - await expect(this.instance.upgradeToAndCall(this.implUpgradeNonUUPS, '0x')) - .to.be.revertedWithCustomError(this.instance, 'ERC1967InvalidImplementation') - .withArgs(this.implUpgradeNonUUPS); - }); - - it('reject proxy address as implementation', async function () { - const otherInstance = await ethers - .deployContract('ERC1967Proxy', [this.implInitial, '0x']) - .then(proxy => this.implInitial.attach(proxy.target)); - - await expect(this.instance.upgradeToAndCall(otherInstance, '0x')) - .to.be.revertedWithCustomError(this.instance, 'ERC1967InvalidImplementation') - .withArgs(otherInstance); - }); -}); diff --git a/lib_openzeppelin_contracts/test/sanity.test.js b/lib_openzeppelin_contracts/test/sanity.test.js deleted file mode 100644 index ea0175c..0000000 --- a/lib_openzeppelin_contracts/test/sanity.test.js +++ /dev/null @@ -1,27 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - return {}; -} - -describe('Environment sanity', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('snapshot', function () { - let blockNumberBefore; - - it('cache and mine', async function () { - blockNumberBefore = await ethers.provider.getBlockNumber(); - await mine(); - expect(await ethers.provider.getBlockNumber()).to.equal(blockNumberBefore + 1); - }); - - it('check snapshot', async function () { - expect(await ethers.provider.getBlockNumber()).to.equal(blockNumberBefore); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/ERC1155.behavior.js b/lib_openzeppelin_contracts/test/token/ERC1155/ERC1155.behavior.js deleted file mode 100644 index d19b732..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/ERC1155.behavior.js +++ /dev/null @@ -1,763 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); - -const { RevertType } = require('../../helpers/enums'); -const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); - -function shouldBehaveLikeERC1155() { - const firstTokenId = 1n; - const secondTokenId = 2n; - const unknownTokenId = 3n; - - const firstTokenValue = 1000n; - const secondTokenValue = 2000n; - - const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61'; - const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81'; - - beforeEach(async function () { - [this.recipient, this.proxy, this.alice, this.bruce] = this.otherAccounts; - }); - - describe('like an ERC1155', function () { - describe('balanceOf', function () { - it('should return 0 when queried about the zero address', async function () { - expect(await this.token.balanceOf(ethers.ZeroAddress, firstTokenId)).to.equal(0n); - }); - - describe("when accounts don't own tokens", function () { - it('returns zero for given addresses', async function () { - expect(await this.token.balanceOf(this.alice, firstTokenId)).to.equal(0n); - expect(await this.token.balanceOf(this.bruce, secondTokenId)).to.equal(0n); - expect(await this.token.balanceOf(this.alice, unknownTokenId)).to.equal(0n); - }); - }); - - describe('when accounts own some tokens', function () { - beforeEach(async function () { - await this.token.$_mint(this.alice, firstTokenId, firstTokenValue, '0x'); - await this.token.$_mint(this.bruce, secondTokenId, secondTokenValue, '0x'); - }); - - it('returns the amount of tokens owned by the given addresses', async function () { - expect(await this.token.balanceOf(this.alice, firstTokenId)).to.equal(firstTokenValue); - expect(await this.token.balanceOf(this.bruce, secondTokenId)).to.equal(secondTokenValue); - expect(await this.token.balanceOf(this.alice, unknownTokenId)).to.equal(0n); - }); - }); - }); - - describe('balanceOfBatch', function () { - it("reverts when input arrays don't match up", async function () { - const accounts1 = [this.alice, this.bruce, this.alice, this.bruce]; - const ids1 = [firstTokenId, secondTokenId, unknownTokenId]; - - await expect(this.token.balanceOfBatch(accounts1, ids1)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(ids1.length, accounts1.length); - - const accounts2 = [this.alice, this.bruce]; - const ids2 = [firstTokenId, secondTokenId, unknownTokenId]; - await expect(this.token.balanceOfBatch(accounts2, ids2)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(ids2.length, accounts2.length); - }); - - it('should return 0 as the balance when one of the addresses is the zero address', async function () { - const result = await this.token.balanceOfBatch( - [this.alice, this.bruce, ethers.ZeroAddress], - [firstTokenId, secondTokenId, unknownTokenId], - ); - expect(result).to.deep.equal([0n, 0n, 0n]); - }); - - describe("when accounts don't own tokens", function () { - it('returns zeros for each account', async function () { - const result = await this.token.balanceOfBatch( - [this.alice, this.bruce, this.alice], - [firstTokenId, secondTokenId, unknownTokenId], - ); - expect(result).to.deep.equal([0n, 0n, 0n]); - }); - }); - - describe('when accounts own some tokens', function () { - beforeEach(async function () { - await this.token.$_mint(this.alice, firstTokenId, firstTokenValue, '0x'); - await this.token.$_mint(this.bruce, secondTokenId, secondTokenValue, '0x'); - }); - - it('returns amounts owned by each account in order passed', async function () { - const result = await this.token.balanceOfBatch( - [this.bruce, this.alice, this.alice], - [secondTokenId, firstTokenId, unknownTokenId], - ); - expect(result).to.deep.equal([secondTokenValue, firstTokenValue, 0n]); - }); - - it('returns multiple times the balance of the same address when asked', async function () { - const result = await this.token.balanceOfBatch( - [this.alice, this.bruce, this.alice], - [firstTokenId, secondTokenId, firstTokenId], - ); - expect(result).to.deep.equal([firstTokenValue, secondTokenValue, firstTokenValue]); - }); - }); - }); - - describe('setApprovalForAll', function () { - beforeEach(async function () { - this.tx = await this.token.connect(this.holder).setApprovalForAll(this.proxy, true); - }); - - it('sets approval status which can be queried via isApprovedForAll', async function () { - expect(await this.token.isApprovedForAll(this.holder, this.proxy)).to.be.true; - }); - - it('emits an ApprovalForAll log', async function () { - await expect(this.tx).to.emit(this.token, 'ApprovalForAll').withArgs(this.holder, this.proxy, true); - }); - - it('can unset approval for an operator', async function () { - await this.token.connect(this.holder).setApprovalForAll(this.proxy, false); - expect(await this.token.isApprovedForAll(this.holder, this.proxy)).to.be.false; - }); - - it('reverts if attempting to approve zero address as an operator', async function () { - await expect(this.token.connect(this.holder).setApprovalForAll(ethers.ZeroAddress, true)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidOperator') - .withArgs(ethers.ZeroAddress); - }); - }); - - describe('safeTransferFrom', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x'); - await this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x'); - }); - - it('reverts when transferring more than balance', async function () { - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, this.recipient, firstTokenId, firstTokenValue + 1n, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance') - .withArgs(this.holder, firstTokenValue, firstTokenValue + 1n, firstTokenId); - }); - - it('reverts when transferring to zero address', async function () { - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, ethers.ZeroAddress, firstTokenId, firstTokenValue, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - - function transferWasSuccessful() { - it('debits transferred balance from sender', async function () { - expect(await this.token.balanceOf(this.args.from, this.args.id)).to.equal(0n); - }); - - it('credits transferred balance to receiver', async function () { - expect(await this.token.balanceOf(this.args.to, this.args.id)).to.equal(this.args.value); - }); - - it('emits a TransferSingle log', async function () { - await expect(this.tx) - .to.emit(this.token, 'TransferSingle') - .withArgs(this.args.operator, this.args.from, this.args.to, this.args.id, this.args.value); - }); - } - - describe('when called by the holder', function () { - beforeEach(async function () { - this.args = { - operator: this.holder, - from: this.holder, - to: this.recipient, - id: firstTokenId, - value: firstTokenValue, - data: '0x', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data); - }); - - transferWasSuccessful(); - - it('preserves existing balances which are not transferred by holder', async function () { - expect(await this.token.balanceOf(this.holder, secondTokenId)).to.equal(secondTokenValue); - expect(await this.token.balanceOf(this.recipient, secondTokenId)).to.equal(0n); - }); - }); - - describe('when called by an operator on behalf of the holder', function () { - describe('when operator is not approved by holder', function () { - beforeEach(async function () { - await this.token.connect(this.holder).setApprovalForAll(this.proxy, false); - }); - - it('reverts', async function () { - await expect( - this.token - .connect(this.proxy) - .safeTransferFrom(this.holder, this.recipient, firstTokenId, firstTokenValue, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll') - .withArgs(this.proxy, this.holder); - }); - }); - - describe('when operator is approved by holder', function () { - beforeEach(async function () { - await this.token.connect(this.holder).setApprovalForAll(this.proxy, true); - - this.args = { - operator: this.proxy, - from: this.holder, - to: this.recipient, - id: firstTokenId, - value: firstTokenValue, - data: '0x', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data); - }); - - transferWasSuccessful(); - - it("preserves operator's balances not involved in the transfer", async function () { - expect(await this.token.balanceOf(this.proxy, firstTokenId)).to.equal(0n); - expect(await this.token.balanceOf(this.proxy, secondTokenId)).to.equal(0n); - }); - }); - }); - - describe('when sending to a valid receiver', function () { - beforeEach(async function () { - this.receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ]); - }); - - describe('without data', function () { - beforeEach(async function () { - this.args = { - operator: this.holder, - from: this.holder, - to: this.receiver, - id: firstTokenId, - value: firstTokenValue, - data: '0x', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data); - }); - - transferWasSuccessful(); - - it('calls onERC1155Received', async function () { - await expect(this.tx) - .to.emit(this.receiver, 'Received') - .withArgs(this.args.operator, this.args.from, this.args.id, this.args.value, this.args.data, anyValue); - }); - }); - - describe('with data', function () { - beforeEach(async function () { - this.args = { - operator: this.holder, - from: this.holder, - to: this.receiver, - id: firstTokenId, - value: firstTokenValue, - data: '0xf00dd00d', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeTransferFrom(this.args.from, this.args.to, this.args.id, this.args.value, this.args.data); - }); - - transferWasSuccessful(); - - it('calls onERC1155Received', async function () { - await expect(this.tx) - .to.emit(this.receiver, 'Received') - .withArgs(this.args.operator, this.args.from, this.args.id, this.args.value, this.args.data, anyValue); - }); - }); - }); - - describe('to a receiver contract returning unexpected value', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - '0x00c0ffee', - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ]); - - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(receiver); - }); - }); - - describe('to a receiver contract that reverts', function () { - describe('with a revert string', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.RevertWithMessage, - ]); - - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'), - ).to.be.revertedWith('ERC1155ReceiverMock: reverting on receive'); - }); - }); - - describe('without a revert string', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.RevertWithoutMessage, - ]); - - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(receiver); - }); - }); - - describe('with a custom error', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.RevertWithCustomError, - ]); - - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'), - ) - .to.be.revertedWithCustomError(receiver, 'CustomError') - .withArgs(RECEIVER_SINGLE_MAGIC_VALUE); - }); - }); - - describe('with a panic', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.Panic, - ]); - - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, receiver, firstTokenId, firstTokenValue, '0x'), - ).to.be.revertedWithPanic(); - }); - }); - }); - - describe('to a contract that does not implement the required function', function () { - it('reverts', async function () { - const invalidReceiver = this.token; - - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, invalidReceiver, firstTokenId, firstTokenValue, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(invalidReceiver); - }); - }); - }); - - describe('safeBatchTransferFrom', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x'); - await this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x'); - }); - - it('reverts when transferring value more than any of balances', async function () { - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - this.recipient, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue + 1n], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance') - .withArgs(this.holder, secondTokenValue, secondTokenValue + 1n, secondTokenId); - }); - - it("reverts when ids array length doesn't match values array length", async function () { - const ids1 = [firstTokenId]; - const tokenValues1 = [firstTokenValue, secondTokenValue]; - - await expect( - this.token.connect(this.holder).safeBatchTransferFrom(this.holder, this.recipient, ids1, tokenValues1, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(ids1.length, tokenValues1.length); - - const ids2 = [firstTokenId, secondTokenId]; - const tokenValues2 = [firstTokenValue]; - - await expect( - this.token.connect(this.holder).safeBatchTransferFrom(this.holder, this.recipient, ids2, tokenValues2, '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(ids2.length, tokenValues2.length); - }); - - it('reverts when transferring to zero address', async function () { - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - ethers.ZeroAddress, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - - it('reverts when transferring from zero address', async function () { - await expect( - this.token.$_safeBatchTransferFrom(ethers.ZeroAddress, this.holder, [firstTokenId], [firstTokenValue], '0x'), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidSender') - .withArgs(ethers.ZeroAddress); - }); - - function batchTransferWasSuccessful() { - it('debits transferred balances from sender', async function () { - const newBalances = await this.token.balanceOfBatch( - this.args.ids.map(() => this.args.from), - this.args.ids, - ); - expect(newBalances).to.deep.equal(this.args.ids.map(() => 0n)); - }); - - it('credits transferred balances to receiver', async function () { - const newBalances = await this.token.balanceOfBatch( - this.args.ids.map(() => this.args.to), - this.args.ids, - ); - expect(newBalances).to.deep.equal(this.args.values); - }); - - it('emits a TransferBatch log', async function () { - await expect(this.tx) - .to.emit(this.token, 'TransferBatch') - .withArgs(this.args.operator, this.args.from, this.args.to, this.args.ids, this.args.values); - }); - } - - describe('when called by the holder', function () { - beforeEach(async function () { - this.args = { - operator: this.holder, - from: this.holder, - to: this.recipient, - ids: [firstTokenId, secondTokenId], - values: [firstTokenValue, secondTokenValue], - data: '0x', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data); - }); - - batchTransferWasSuccessful(); - }); - - describe('when called by an operator on behalf of the holder', function () { - describe('when operator is not approved by holder', function () { - beforeEach(async function () { - await this.token.connect(this.holder).setApprovalForAll(this.proxy, false); - }); - - it('reverts', async function () { - await expect( - this.token - .connect(this.proxy) - .safeBatchTransferFrom( - this.holder, - this.recipient, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll') - .withArgs(this.proxy, this.holder); - }); - }); - - describe('when operator is approved by holder', function () { - beforeEach(async function () { - await this.token.connect(this.holder).setApprovalForAll(this.proxy, true); - - this.args = { - operator: this.proxy, - from: this.holder, - to: this.recipient, - ids: [firstTokenId, secondTokenId], - values: [firstTokenValue, secondTokenValue], - data: '0x', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data); - }); - - batchTransferWasSuccessful(); - - it("preserves operator's balances not involved in the transfer", async function () { - expect(await this.token.balanceOf(this.proxy, firstTokenId)).to.equal(0n); - expect(await this.token.balanceOf(this.proxy, secondTokenId)).to.equal(0n); - }); - }); - }); - - describe('when sending to a valid receiver', function () { - beforeEach(async function () { - this.receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.None, - ]); - }); - - describe('without data', function () { - beforeEach(async function () { - this.args = { - operator: this.holder, - from: this.holder, - to: this.receiver, - ids: [firstTokenId, secondTokenId], - values: [firstTokenValue, secondTokenValue], - data: '0x', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data); - }); - - batchTransferWasSuccessful(); - - it('calls onERC1155BatchReceived', async function () { - await expect(this.tx) - .to.emit(this.receiver, 'BatchReceived') - .withArgs(this.holder, this.holder, this.args.ids, this.args.values, this.args.data, anyValue); - }); - }); - - describe('with data', function () { - beforeEach(async function () { - this.args = { - operator: this.holder, - from: this.holder, - to: this.receiver, - ids: [firstTokenId, secondTokenId], - values: [firstTokenValue, secondTokenValue], - data: '0xf00dd00d', - }; - this.tx = await this.token - .connect(this.args.operator) - .safeBatchTransferFrom(this.args.from, this.args.to, this.args.ids, this.args.values, this.args.data); - }); - - batchTransferWasSuccessful(); - - it('calls onERC1155Received', async function () { - await expect(this.tx) - .to.emit(this.receiver, 'BatchReceived') - .withArgs(this.holder, this.holder, this.args.ids, this.args.values, this.args.data, anyValue); - }); - }); - }); - - describe('to a receiver contract returning unexpected value', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_SINGLE_MAGIC_VALUE, - RevertType.None, - ]); - - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - receiver, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(receiver); - }); - }); - - describe('to a receiver contract that reverts', function () { - describe('with a revert string', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.RevertWithMessage, - ]); - - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - receiver, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ).to.be.revertedWith('ERC1155ReceiverMock: reverting on batch receive'); - }); - }); - - describe('without a revert string', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.RevertWithoutMessage, - ]); - - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - receiver, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(receiver); - }); - }); - - describe('with a custom error', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.RevertWithCustomError, - ]); - - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - receiver, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(receiver, 'CustomError') - .withArgs(RECEIVER_SINGLE_MAGIC_VALUE); - }); - }); - - describe('with a panic', function () { - it('reverts', async function () { - const receiver = await ethers.deployContract('$ERC1155ReceiverMock', [ - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - RevertType.Panic, - ]); - - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - receiver, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ).to.be.revertedWithPanic(); - }); - }); - }); - - describe('to a contract that does not implement the required function', function () { - it('reverts', async function () { - const invalidReceiver = this.token; - - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom( - this.holder, - invalidReceiver, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(invalidReceiver); - }); - }); - }); - - shouldSupportInterfaces(['ERC1155', 'ERC1155MetadataURI']); - }); -} - -module.exports = { - shouldBehaveLikeERC1155, -}; diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/ERC1155.test.js b/lib_openzeppelin_contracts/test/token/ERC1155/ERC1155.test.js deleted file mode 100644 index 8b0a672..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/ERC1155.test.js +++ /dev/null @@ -1,213 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { zip } = require('../../helpers/iterate'); -const { shouldBehaveLikeERC1155 } = require('./ERC1155.behavior'); - -const initialURI = 'https://token-cdn-domain/{id}.json'; - -async function fixture() { - const [operator, holder, ...otherAccounts] = await ethers.getSigners(); - const token = await ethers.deployContract('$ERC1155', [initialURI]); - return { token, operator, holder, otherAccounts }; -} - -describe('ERC1155', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeERC1155(); - - describe('internal functions', function () { - const tokenId = 1990n; - const mintValue = 9001n; - const burnValue = 3000n; - - const tokenBatchIds = [2000n, 2010n, 2020n]; - const mintValues = [5000n, 10000n, 42195n]; - const burnValues = [5000n, 9001n, 195n]; - - const data = '0x12345678'; - - describe('_mint', function () { - it('reverts with a zero destination address', async function () { - await expect(this.token.$_mint(ethers.ZeroAddress, tokenId, mintValue, data)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - - describe('with minted tokens', function () { - beforeEach(async function () { - this.tx = await this.token.connect(this.operator).$_mint(this.holder, tokenId, mintValue, data); - }); - - it('emits a TransferSingle event', async function () { - await expect(this.tx) - .to.emit(this.token, 'TransferSingle') - .withArgs(this.operator, ethers.ZeroAddress, this.holder, tokenId, mintValue); - }); - - it('credits the minted token value', async function () { - expect(await this.token.balanceOf(this.holder, tokenId)).to.equal(mintValue); - }); - }); - }); - - describe('_mintBatch', function () { - it('reverts with a zero destination address', async function () { - await expect(this.token.$_mintBatch(ethers.ZeroAddress, tokenBatchIds, mintValues, data)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - - it('reverts if length of inputs do not match', async function () { - await expect(this.token.$_mintBatch(this.holder, tokenBatchIds, mintValues.slice(1), data)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(tokenBatchIds.length, mintValues.length - 1); - - await expect(this.token.$_mintBatch(this.holder, tokenBatchIds.slice(1), mintValues, data)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(tokenBatchIds.length - 1, mintValues.length); - }); - - describe('with minted batch of tokens', function () { - beforeEach(async function () { - this.tx = await this.token.connect(this.operator).$_mintBatch(this.holder, tokenBatchIds, mintValues, data); - }); - - it('emits a TransferBatch event', async function () { - await expect(this.tx) - .to.emit(this.token, 'TransferBatch') - .withArgs(this.operator, ethers.ZeroAddress, this.holder, tokenBatchIds, mintValues); - }); - - it('credits the minted batch of tokens', async function () { - const holderBatchBalances = await this.token.balanceOfBatch( - tokenBatchIds.map(() => this.holder), - tokenBatchIds, - ); - - expect(holderBatchBalances).to.deep.equal(mintValues); - }); - }); - }); - - describe('_burn', function () { - it("reverts when burning the zero account's tokens", async function () { - await expect(this.token.$_burn(ethers.ZeroAddress, tokenId, mintValue)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidSender') - .withArgs(ethers.ZeroAddress); - }); - - it('reverts when burning a non-existent token id', async function () { - await expect(this.token.$_burn(this.holder, tokenId, mintValue)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance') - .withArgs(this.holder, 0, mintValue, tokenId); - }); - - it('reverts when burning more than available tokens', async function () { - await this.token.connect(this.operator).$_mint(this.holder, tokenId, mintValue, data); - - await expect(this.token.$_burn(this.holder, tokenId, mintValue + 1n)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance') - .withArgs(this.holder, mintValue, mintValue + 1n, tokenId); - }); - - describe('with minted-then-burnt tokens', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, tokenId, mintValue, data); - this.tx = await this.token.connect(this.operator).$_burn(this.holder, tokenId, burnValue); - }); - - it('emits a TransferSingle event', async function () { - await expect(this.tx) - .to.emit(this.token, 'TransferSingle') - .withArgs(this.operator, this.holder, ethers.ZeroAddress, tokenId, burnValue); - }); - - it('accounts for both minting and burning', async function () { - expect(await this.token.balanceOf(this.holder, tokenId)).to.equal(mintValue - burnValue); - }); - }); - }); - - describe('_burnBatch', function () { - it("reverts when burning the zero account's tokens", async function () { - await expect(this.token.$_burnBatch(ethers.ZeroAddress, tokenBatchIds, burnValues)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidSender') - .withArgs(ethers.ZeroAddress); - }); - - it('reverts if length of inputs do not match', async function () { - await expect(this.token.$_burnBatch(this.holder, tokenBatchIds, burnValues.slice(1))) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(tokenBatchIds.length, burnValues.length - 1); - - await expect(this.token.$_burnBatch(this.holder, tokenBatchIds.slice(1), burnValues)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InvalidArrayLength') - .withArgs(tokenBatchIds.length - 1, burnValues.length); - }); - - it('reverts when burning a non-existent token id', async function () { - await expect(this.token.$_burnBatch(this.holder, tokenBatchIds, burnValues)) - .to.be.revertedWithCustomError(this.token, 'ERC1155InsufficientBalance') - .withArgs(this.holder, 0, burnValues[0], tokenBatchIds[0]); - }); - - describe('with minted-then-burnt tokens', function () { - beforeEach(async function () { - await this.token.$_mintBatch(this.holder, tokenBatchIds, mintValues, data); - this.tx = await this.token.connect(this.operator).$_burnBatch(this.holder, tokenBatchIds, burnValues); - }); - - it('emits a TransferBatch event', async function () { - await expect(this.tx) - .to.emit(this.token, 'TransferBatch') - .withArgs(this.operator, this.holder, ethers.ZeroAddress, tokenBatchIds, burnValues); - }); - - it('accounts for both minting and burning', async function () { - const holderBatchBalances = await this.token.balanceOfBatch( - tokenBatchIds.map(() => this.holder), - tokenBatchIds, - ); - - expect(holderBatchBalances).to.deep.equal( - zip(mintValues, burnValues).map(([mintValue, burnValue]) => mintValue - burnValue), - ); - }); - }); - }); - }); - - describe('ERC1155MetadataURI', function () { - const firstTokenID = 42n; - const secondTokenID = 1337n; - - it('emits no URI event in constructor', async function () { - await expect(this.token.deploymentTransaction()).to.not.emit(this.token, 'URI'); - }); - - it('sets the initial URI for all token types', async function () { - expect(await this.token.uri(firstTokenID)).to.equal(initialURI); - expect(await this.token.uri(secondTokenID)).to.equal(initialURI); - }); - - describe('_setURI', function () { - const newURI = 'https://token-cdn-domain/{locale}/{id}.json'; - - it('emits no URI event', async function () { - await expect(this.token.$_setURI(newURI)).to.not.emit(this.token, 'URI'); - }); - - it('sets the new URI for all token types', async function () { - await this.token.$_setURI(newURI); - - expect(await this.token.uri(firstTokenID)).to.equal(newURI); - expect(await this.token.uri(secondTokenID)).to.equal(newURI); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Burnable.test.js b/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Burnable.test.js deleted file mode 100644 index 01e7ee2..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Burnable.test.js +++ /dev/null @@ -1,66 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const ids = [42n, 1137n]; -const values = [3000n, 9902n]; - -async function fixture() { - const [holder, operator, other] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC1155Burnable', ['https://token-cdn-domain/{id}.json']); - await token.$_mint(holder, ids[0], values[0], '0x'); - await token.$_mint(holder, ids[1], values[1], '0x'); - - return { token, holder, operator, other }; -} - -describe('ERC1155Burnable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('burn', function () { - it('holder can burn their tokens', async function () { - await this.token.connect(this.holder).burn(this.holder, ids[0], values[0] - 1n); - - expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n); - }); - - it("approved operators can burn the holder's tokens", async function () { - await this.token.connect(this.holder).setApprovalForAll(this.operator, true); - await this.token.connect(this.operator).burn(this.holder, ids[0], values[0] - 1n); - - expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n); - }); - - it("unapproved accounts cannot burn the holder's tokens", async function () { - await expect(this.token.connect(this.other).burn(this.holder, ids[0], values[0] - 1n)) - .to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll') - .withArgs(this.other, this.holder); - }); - }); - - describe('burnBatch', function () { - it('holder can burn their tokens', async function () { - await this.token.connect(this.holder).burnBatch(this.holder, ids, [values[0] - 1n, values[1] - 2n]); - - expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n); - expect(await this.token.balanceOf(this.holder, ids[1])).to.equal(2n); - }); - - it("approved operators can burn the holder's tokens", async function () { - await this.token.connect(this.holder).setApprovalForAll(this.operator, true); - await this.token.connect(this.operator).burnBatch(this.holder, ids, [values[0] - 1n, values[1] - 2n]); - - expect(await this.token.balanceOf(this.holder, ids[0])).to.equal(1n); - expect(await this.token.balanceOf(this.holder, ids[1])).to.equal(2n); - }); - - it("unapproved accounts cannot burn the holder's tokens", async function () { - await expect(this.token.connect(this.other).burnBatch(this.holder, ids, [values[0] - 1n, values[1] - 2n])) - .to.be.revertedWithCustomError(this.token, 'ERC1155MissingApprovalForAll') - .withArgs(this.other, this.holder); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Pausable.test.js b/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Pausable.test.js deleted file mode 100644 index 81c4f69..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Pausable.test.js +++ /dev/null @@ -1,105 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [holder, operator, receiver, other] = await ethers.getSigners(); - const token = await ethers.deployContract('$ERC1155Pausable', ['https://token-cdn-domain/{id}.json']); - return { token, holder, operator, receiver, other }; -} - -describe('ERC1155Pausable', function () { - const firstTokenId = 37n; - const firstTokenValue = 42n; - const secondTokenId = 19842n; - const secondTokenValue = 23n; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('when token is paused', function () { - beforeEach(async function () { - await this.token.connect(this.holder).setApprovalForAll(this.operator, true); - await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x'); - await this.token.$_pause(); - }); - - it('reverts when trying to safeTransferFrom from holder', async function () { - await expect( - this.token - .connect(this.holder) - .safeTransferFrom(this.holder, this.receiver, firstTokenId, firstTokenValue, '0x'), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to safeTransferFrom from operator', async function () { - await expect( - this.token - .connect(this.operator) - .safeTransferFrom(this.holder, this.receiver, firstTokenId, firstTokenValue, '0x'), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to safeBatchTransferFrom from holder', async function () { - await expect( - this.token - .connect(this.holder) - .safeBatchTransferFrom(this.holder, this.receiver, [firstTokenId], [firstTokenValue], '0x'), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to safeBatchTransferFrom from operator', async function () { - await expect( - this.token - .connect(this.operator) - .safeBatchTransferFrom(this.holder, this.receiver, [firstTokenId], [firstTokenValue], '0x'), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to mint', async function () { - await expect(this.token.$_mint(this.holder, secondTokenId, secondTokenValue, '0x')).to.be.revertedWithCustomError( - this.token, - 'EnforcedPause', - ); - }); - - it('reverts when trying to mintBatch', async function () { - await expect( - this.token.$_mintBatch(this.holder, [secondTokenId], [secondTokenValue], '0x'), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to burn', async function () { - await expect(this.token.$_burn(this.holder, firstTokenId, firstTokenValue)).to.be.revertedWithCustomError( - this.token, - 'EnforcedPause', - ); - }); - - it('reverts when trying to burnBatch', async function () { - await expect( - this.token.$_burnBatch(this.holder, [firstTokenId], [firstTokenValue]), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - describe('setApprovalForAll', function () { - it('approves an operator', async function () { - await this.token.connect(this.holder).setApprovalForAll(this.other, true); - expect(await this.token.isApprovedForAll(this.holder, this.other)).to.be.true; - }); - }); - - describe('balanceOf', function () { - it('returns the token value owned by the given address', async function () { - expect(await this.token.balanceOf(this.holder, firstTokenId)).to.equal(firstTokenValue); - }); - }); - - describe('isApprovedForAll', function () { - it('returns the approval of the operator', async function () { - expect(await this.token.isApprovedForAll(this.holder, this.operator)).to.be.true; - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Supply.test.js b/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Supply.test.js deleted file mode 100644 index cca36a0..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155Supply.test.js +++ /dev/null @@ -1,119 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [holder] = await ethers.getSigners(); - const token = await ethers.deployContract('$ERC1155Supply', ['https://token-cdn-domain/{id}.json']); - return { token, holder }; -} - -describe('ERC1155Supply', function () { - const firstTokenId = 37n; - const firstTokenValue = 42n; - const secondTokenId = 19842n; - const secondTokenValue = 23n; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('before mint', function () { - it('exist', async function () { - expect(await this.token.exists(firstTokenId)).to.be.false; - }); - - it('totalSupply', async function () { - expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n); - expect(await this.token.totalSupply()).to.equal(0n); - }); - }); - - describe('after mint', function () { - describe('single', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x'); - }); - - it('exist', async function () { - expect(await this.token.exists(firstTokenId)).to.be.true; - }); - - it('totalSupply', async function () { - expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(firstTokenValue); - expect(await this.token.totalSupply()).to.equal(firstTokenValue); - }); - }); - - describe('batch', function () { - beforeEach(async function () { - await this.token.$_mintBatch( - this.holder, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ); - }); - - it('exist', async function () { - expect(await this.token.exists(firstTokenId)).to.be.true; - expect(await this.token.exists(secondTokenId)).to.be.true; - }); - - it('totalSupply', async function () { - expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(firstTokenValue); - expect(await this.token.totalSupply(ethers.Typed.uint256(secondTokenId))).to.equal(secondTokenValue); - expect(await this.token.totalSupply()).to.equal(firstTokenValue + secondTokenValue); - }); - }); - }); - - describe('after burn', function () { - describe('single', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, firstTokenId, firstTokenValue, '0x'); - await this.token.$_burn(this.holder, firstTokenId, firstTokenValue); - }); - - it('exist', async function () { - expect(await this.token.exists(firstTokenId)).to.be.false; - }); - - it('totalSupply', async function () { - expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n); - expect(await this.token.totalSupply()).to.equal(0n); - }); - }); - - describe('batch', function () { - beforeEach(async function () { - await this.token.$_mintBatch( - this.holder, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ); - await this.token.$_burnBatch(this.holder, [firstTokenId, secondTokenId], [firstTokenValue, secondTokenValue]); - }); - - it('exist', async function () { - expect(await this.token.exists(firstTokenId)).to.be.false; - expect(await this.token.exists(secondTokenId)).to.be.false; - }); - - it('totalSupply', async function () { - expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n); - expect(await this.token.totalSupply(ethers.Typed.uint256(secondTokenId))).to.equal(0n); - expect(await this.token.totalSupply()).to.equal(0n); - }); - }); - }); - - describe('other', function () { - it('supply unaffected by no-op', async function () { - await this.token.$_update(ethers.ZeroAddress, ethers.ZeroAddress, [firstTokenId], [firstTokenValue]); - expect(await this.token.totalSupply(ethers.Typed.uint256(firstTokenId))).to.equal(0n); - expect(await this.token.totalSupply()).to.equal(0n); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155URIStorage.test.js b/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155URIStorage.test.js deleted file mode 100644 index a0d9b57..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/extensions/ERC1155URIStorage.test.js +++ /dev/null @@ -1,70 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const erc1155Uri = 'https://token.com/nfts/'; -const baseUri = 'https://token.com/'; -const tokenId = 1n; -const value = 3000n; - -describe('ERC1155URIStorage', function () { - describe('with base uri set', function () { - async function fixture() { - const [holder] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC1155URIStorage', [erc1155Uri]); - await token.$_setBaseURI(baseUri); - await token.$_mint(holder, tokenId, value, '0x'); - - return { token, holder }; - } - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('can request the token uri, returning the erc1155 uri if no token uri was set', async function () { - expect(await this.token.uri(tokenId)).to.equal(erc1155Uri); - }); - - it('can request the token uri, returning the concatenated uri if a token uri was set', async function () { - const tokenUri = '1234/'; - const expectedUri = `${baseUri}${tokenUri}`; - - await expect(this.token.$_setURI(ethers.Typed.uint256(tokenId), tokenUri)) - .to.emit(this.token, 'URI') - .withArgs(expectedUri, tokenId); - - expect(await this.token.uri(tokenId)).to.equal(expectedUri); - }); - }); - - describe('with base uri set to the empty string', function () { - async function fixture() { - const [holder] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC1155URIStorage', ['']); - await token.$_mint(holder, tokenId, value, '0x'); - - return { token, holder }; - } - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('can request the token uri, returning an empty string if no token uri was set', async function () { - expect(await this.token.uri(tokenId)).to.equal(''); - }); - - it('can request the token uri, returning the token uri if a token uri was set', async function () { - const tokenUri = 'ipfs://1234/'; - - await expect(this.token.$_setURI(ethers.Typed.uint256(tokenId), tokenUri)) - .to.emit(this.token, 'URI') - .withArgs(tokenUri, tokenId); - - expect(await this.token.uri(tokenId)).to.equal(tokenUri); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/utils/ERC1155Holder.test.js b/lib_openzeppelin_contracts/test/token/ERC1155/utils/ERC1155Holder.test.js deleted file mode 100644 index 9bff487..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/utils/ERC1155Holder.test.js +++ /dev/null @@ -1,56 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); - -const ids = [1n, 2n, 3n]; -const values = [1000n, 2000n, 3000n]; -const data = '0x12345678'; - -async function fixture() { - const [owner] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC1155', ['https://token-cdn-domain/{id}.json']); - const mock = await ethers.deployContract('$ERC1155Holder'); - - await token.$_mintBatch(owner, ids, values, '0x'); - - return { owner, token, mock }; -} - -describe('ERC1155Holder', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldSupportInterfaces(['ERC1155Receiver']); - - it('receives ERC1155 tokens from a single ID', async function () { - await this.token.connect(this.owner).safeTransferFrom(this.owner, this.mock, ids[0], values[0], data); - - expect(await this.token.balanceOf(this.mock, ids[0])).to.equal(values[0]); - - for (let i = 1; i < ids.length; i++) { - expect(await this.token.balanceOf(this.mock, ids[i])).to.equal(0n); - } - }); - - it('receives ERC1155 tokens from a multiple IDs', async function () { - expect( - await this.token.balanceOfBatch( - ids.map(() => this.mock), - ids, - ), - ).to.deep.equal(ids.map(() => 0n)); - - await this.token.connect(this.owner).safeBatchTransferFrom(this.owner, this.mock, ids, values, data); - - expect( - await this.token.balanceOfBatch( - ids.map(() => this.mock), - ids, - ), - ).to.deep.equal(values); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC1155/utils/ERC1155Utils.test.js b/lib_openzeppelin_contracts/test/token/ERC1155/utils/ERC1155Utils.test.js deleted file mode 100644 index 5687568..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC1155/utils/ERC1155Utils.test.js +++ /dev/null @@ -1,299 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { RevertType } = require('../../../helpers/enums'); -const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); - -const firstTokenId = 1n; -const secondTokenId = 2n; -const firstTokenValue = 1000n; -const secondTokenValue = 1000n; - -const RECEIVER_SINGLE_MAGIC_VALUE = '0xf23a6e61'; -const RECEIVER_BATCH_MAGIC_VALUE = '0xbc197c81'; - -const deployReceiver = ( - revertType, - returnValueSingle = RECEIVER_SINGLE_MAGIC_VALUE, - returnValueBatched = RECEIVER_BATCH_MAGIC_VALUE, -) => ethers.deployContract('$ERC1155ReceiverMock', [returnValueSingle, returnValueBatched, revertType]); - -const fixture = async () => { - const [eoa, operator, owner] = await ethers.getSigners(); - const utils = await ethers.deployContract('$ERC1155Utils'); - - const receivers = { - correct: await deployReceiver(RevertType.None), - invalid: await deployReceiver(RevertType.None, '0xdeadbeef', '0xdeadbeef'), - message: await deployReceiver(RevertType.RevertWithMessage), - empty: await deployReceiver(RevertType.RevertWithoutMessage), - customError: await deployReceiver(RevertType.RevertWithCustomError), - panic: await deployReceiver(RevertType.Panic), - nonReceiver: await ethers.deployContract('CallReceiverMock'), - eoa, - }; - - return { operator, owner, utils, receivers }; -}; - -describe('ERC1155Utils', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('onERC1155Received', function () { - it('succeeds when called by an EOA', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.eoa, - firstTokenId, - firstTokenValue, - '0x', - ), - ).to.not.be.reverted; - }); - - it('succeeds when data is passed', async function () { - const data = '0x12345678'; - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.correct, - firstTokenId, - firstTokenValue, - data, - ), - ).to.not.be.reverted; - }); - - it('succeeds when data is empty', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.correct, - firstTokenId, - firstTokenValue, - '0x', - ), - ).to.not.be.reverted; - }); - - it('reverts when receiver returns invalid value', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.invalid, - firstTokenId, - firstTokenValue, - '0x', - ), - ) - .to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver') - .withArgs(this.receivers.invalid); - }); - - it('reverts when receiver reverts with message', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.message, - firstTokenId, - firstTokenValue, - '0x', - ), - ).to.be.revertedWith('ERC1155ReceiverMock: reverting on receive'); - }); - - it('reverts when receiver reverts without message', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.empty, - firstTokenId, - firstTokenValue, - '0x', - ), - ) - .to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver') - .withArgs(this.receivers.empty); - }); - - it('reverts when receiver reverts with custom error', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.customError, - firstTokenId, - firstTokenValue, - '0x', - ), - ) - .to.be.revertedWithCustomError(this.receivers.customError, 'CustomError') - .withArgs(RECEIVER_SINGLE_MAGIC_VALUE); - }); - - it('reverts when receiver panics', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.panic, - firstTokenId, - firstTokenValue, - '0x', - ), - ).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO); - }); - - it('reverts when receiver does not implement onERC1155Received', async function () { - await expect( - this.utils.$checkOnERC1155Received( - this.operator, - this.owner, - this.receivers.nonReceiver, - firstTokenId, - firstTokenValue, - '0x', - ), - ) - .to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver') - .withArgs(this.receivers.nonReceiver); - }); - }); - - describe('onERC1155BatchReceived', function () { - it('succeeds when called by an EOA', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.eoa, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ).to.not.be.reverted; - }); - - it('succeeds when data is passed', async function () { - const data = '0x12345678'; - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.correct, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - data, - ), - ).to.not.be.reverted; - }); - - it('succeeds when data is empty', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.correct, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ).to.not.be.reverted; - }); - - it('reverts when receiver returns invalid value', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.invalid, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver') - .withArgs(this.receivers.invalid); - }); - - it('reverts when receiver reverts with message', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.message, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ).to.be.revertedWith('ERC1155ReceiverMock: reverting on batch receive'); - }); - - it('reverts when receiver reverts without message', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.empty, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver') - .withArgs(this.receivers.empty); - }); - - it('reverts when receiver reverts with custom error', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.customError, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.receivers.customError, 'CustomError') - .withArgs(RECEIVER_SINGLE_MAGIC_VALUE); - }); - - it('reverts when receiver panics', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.panic, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO); - }); - - it('reverts when receiver does not implement onERC1155BatchReceived', async function () { - await expect( - this.utils.$checkOnERC1155BatchReceived( - this.operator, - this.owner, - this.receivers.nonReceiver, - [firstTokenId, secondTokenId], - [firstTokenValue, secondTokenValue], - '0x', - ), - ) - .to.be.revertedWithCustomError(this.utils, 'ERC1155InvalidReceiver') - .withArgs(this.receivers.nonReceiver); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/ERC20.behavior.js b/lib_openzeppelin_contracts/test/token/ERC20/ERC20.behavior.js deleted file mode 100644 index 6754bff..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/ERC20.behavior.js +++ /dev/null @@ -1,260 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -function shouldBehaveLikeERC20(initialSupply, opts = {}) { - const { forcedApproval } = opts; - - beforeEach(async function () { - [this.holder, this.recipient, this.other] = this.accounts; - }); - - it('total supply: returns the total token value', async function () { - expect(await this.token.totalSupply()).to.equal(initialSupply); - }); - - describe('balanceOf', function () { - it('returns zero when the requested account has no tokens', async function () { - expect(await this.token.balanceOf(this.other)).to.equal(0n); - }); - - it('returns the total token value when the requested account has some tokens', async function () { - expect(await this.token.balanceOf(this.holder)).to.equal(initialSupply); - }); - }); - - describe('transfer', function () { - beforeEach(function () { - this.transfer = (from, to, value) => this.token.connect(from).transfer(to, value); - }); - - shouldBehaveLikeERC20Transfer(initialSupply); - }); - - describe('transfer from', function () { - describe('when the token owner is not the zero address', function () { - describe('when the recipient is not the zero address', function () { - describe('when the spender has enough allowance', function () { - beforeEach(async function () { - await this.token.connect(this.holder).approve(this.recipient, initialSupply); - }); - - describe('when the token owner has enough balance', function () { - const value = initialSupply; - - beforeEach(async function () { - this.tx = await this.token.connect(this.recipient).transferFrom(this.holder, this.other, value); - }); - - it('transfers the requested value', async function () { - await expect(this.tx).to.changeTokenBalances(this.token, [this.holder, this.other], [-value, value]); - }); - - it('decreases the spender allowance', async function () { - expect(await this.token.allowance(this.holder, this.recipient)).to.equal(0n); - }); - - it('emits a transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(this.holder, this.other, value); - }); - - if (forcedApproval) { - it('emits an approval event', async function () { - await expect(this.tx) - .to.emit(this.token, 'Approval') - .withArgs( - this.holder.address, - this.recipient.address, - await this.token.allowance(this.holder, this.recipient), - ); - }); - } else { - it('does not emit an approval event', async function () { - await expect(this.tx).to.not.emit(this.token, 'Approval'); - }); - } - }); - - it('reverts when the token owner does not have enough balance', async function () { - const value = initialSupply; - await this.token.connect(this.holder).transfer(this.other, 1n); - await expect(this.token.connect(this.recipient).transferFrom(this.holder, this.other, value)) - .to.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.holder, value - 1n, value); - }); - }); - - describe('when the spender does not have enough allowance', function () { - const allowance = initialSupply - 1n; - - beforeEach(async function () { - await this.token.connect(this.holder).approve(this.recipient, allowance); - }); - - it('reverts when the token owner has enough balance', async function () { - const value = initialSupply; - await expect(this.token.connect(this.recipient).transferFrom(this.holder, this.other, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance') - .withArgs(this.recipient, allowance, value); - }); - - it('reverts when the token owner does not have enough balance', async function () { - const value = allowance; - await this.token.connect(this.holder).transfer(this.other, 2); - await expect(this.token.connect(this.recipient).transferFrom(this.holder, this.other, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.holder, value - 1n, value); - }); - }); - - describe('when the spender has unlimited allowance', function () { - beforeEach(async function () { - await this.token.connect(this.holder).approve(this.recipient, ethers.MaxUint256); - this.tx = await this.token.connect(this.recipient).transferFrom(this.holder, this.other, 1n); - }); - - it('does not decrease the spender allowance', async function () { - expect(await this.token.allowance(this.holder, this.recipient)).to.equal(ethers.MaxUint256); - }); - - it('does not emit an approval event', async function () { - await expect(this.tx).to.not.emit(this.token, 'Approval'); - }); - }); - }); - - it('reverts when the recipient is the zero address', async function () { - const value = initialSupply; - await this.token.connect(this.holder).approve(this.recipient, value); - await expect(this.token.connect(this.recipient).transferFrom(this.holder, ethers.ZeroAddress, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - }); - - it('reverts when the token owner is the zero address', async function () { - const value = 0n; - await expect(this.token.connect(this.recipient).transferFrom(ethers.ZeroAddress, this.recipient, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidApprover') - .withArgs(ethers.ZeroAddress); - }); - }); - - describe('approve', function () { - beforeEach(function () { - this.approve = (owner, spender, value) => this.token.connect(owner).approve(spender, value); - }); - - shouldBehaveLikeERC20Approve(initialSupply); - }); -} - -function shouldBehaveLikeERC20Transfer(balance) { - describe('when the recipient is not the zero address', function () { - it('reverts when the sender does not have enough balance', async function () { - const value = balance + 1n; - await expect(this.transfer(this.holder, this.recipient, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.holder, balance, value); - }); - - describe('when the sender transfers all balance', function () { - const value = balance; - - beforeEach(async function () { - this.tx = await this.transfer(this.holder, this.recipient, value); - }); - - it('transfers the requested value', async function () { - await expect(this.tx).to.changeTokenBalances(this.token, [this.holder, this.recipient], [-value, value]); - }); - - it('emits a transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(this.holder, this.recipient, value); - }); - }); - - describe('when the sender transfers zero tokens', function () { - const value = 0n; - - beforeEach(async function () { - this.tx = await this.transfer(this.holder, this.recipient, value); - }); - - it('transfers the requested value', async function () { - await expect(this.tx).to.changeTokenBalances(this.token, [this.holder, this.recipient], [0n, 0n]); - }); - - it('emits a transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(this.holder, this.recipient, value); - }); - }); - }); - - it('reverts when the recipient is the zero address', async function () { - await expect(this.transfer(this.holder, ethers.ZeroAddress, balance)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); -} - -function shouldBehaveLikeERC20Approve(supply) { - describe('when the spender is not the zero address', function () { - describe('when the sender has enough balance', function () { - const value = supply; - - it('emits an approval event', async function () { - await expect(this.approve(this.holder, this.recipient, value)) - .to.emit(this.token, 'Approval') - .withArgs(this.holder, this.recipient, value); - }); - - it('approves the requested value when there was no approved value before', async function () { - await this.approve(this.holder, this.recipient, value); - - expect(await this.token.allowance(this.holder, this.recipient)).to.equal(value); - }); - - it('approves the requested value and replaces the previous one when the spender had an approved value', async function () { - await this.approve(this.holder, this.recipient, 1n); - await this.approve(this.holder, this.recipient, value); - - expect(await this.token.allowance(this.holder, this.recipient)).to.equal(value); - }); - }); - - describe('when the sender does not have enough balance', function () { - const value = supply + 1n; - - it('emits an approval event', async function () { - await expect(this.approve(this.holder, this.recipient, value)) - .to.emit(this.token, 'Approval') - .withArgs(this.holder, this.recipient, value); - }); - - it('approves the requested value when there was no approved value before', async function () { - await this.approve(this.holder, this.recipient, value); - - expect(await this.token.allowance(this.holder, this.recipient)).to.equal(value); - }); - - it('approves the requested value and replaces the previous one when the spender had an approved value', async function () { - await this.approve(this.holder, this.recipient, 1n); - await this.approve(this.holder, this.recipient, value); - - expect(await this.token.allowance(this.holder, this.recipient)).to.equal(value); - }); - }); - }); - - it('reverts when the spender is the zero address', async function () { - await expect(this.approve(this.holder, ethers.ZeroAddress, supply)) - .to.be.revertedWithCustomError(this.token, `ERC20InvalidSpender`) - .withArgs(ethers.ZeroAddress); - }); -} - -module.exports = { - shouldBehaveLikeERC20, - shouldBehaveLikeERC20Transfer, - shouldBehaveLikeERC20Approve, -}; diff --git a/lib_openzeppelin_contracts/test/token/ERC20/ERC20.test.js b/lib_openzeppelin_contracts/test/token/ERC20/ERC20.test.js deleted file mode 100644 index 2d9eefe..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/ERC20.test.js +++ /dev/null @@ -1,199 +0,0 @@ -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 { - shouldBehaveLikeERC20, - shouldBehaveLikeERC20Transfer, - shouldBehaveLikeERC20Approve, -} = require('./ERC20.behavior'); - -const TOKENS = [{ Token: '$ERC20' }, { Token: '$ERC20ApprovalMock', forcedApproval: true }]; - -const name = 'My Token'; -const symbol = 'MTKN'; -const initialSupply = 100n; - -describe('ERC20', function () { - for (const { Token, forcedApproval } of TOKENS) { - describe(Token, function () { - const fixture = async () => { - // this.accounts is used by shouldBehaveLikeERC20 - const accounts = await ethers.getSigners(); - const [holder, recipient] = accounts; - - const token = await ethers.deployContract(Token, [name, symbol]); - await token.$_mint(holder, initialSupply); - - return { accounts, holder, recipient, token }; - }; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeERC20(initialSupply, { forcedApproval }); - - it('has a name', async function () { - expect(await this.token.name()).to.equal(name); - }); - - it('has a symbol', async function () { - expect(await this.token.symbol()).to.equal(symbol); - }); - - it('has 18 decimals', async function () { - expect(await this.token.decimals()).to.equal(18n); - }); - - describe('_mint', function () { - const value = 50n; - it('rejects a null account', async function () { - await expect(this.token.$_mint(ethers.ZeroAddress, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - - it('rejects overflow', async function () { - await expect(this.token.$_mint(this.recipient, ethers.MaxUint256)).to.be.revertedWithPanic( - PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW, - ); - }); - - describe('for a non zero account', function () { - beforeEach('minting', async function () { - this.tx = await this.token.$_mint(this.recipient, value); - }); - - it('increments totalSupply', async function () { - await expect(await this.token.totalSupply()).to.equal(initialSupply + value); - }); - - it('increments recipient balance', async function () { - await expect(this.tx).to.changeTokenBalance(this.token, this.recipient, value); - }); - - it('emits Transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, this.recipient, value); - }); - }); - }); - - describe('_burn', function () { - it('rejects a null account', async function () { - await expect(this.token.$_burn(ethers.ZeroAddress, 1n)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidSender') - .withArgs(ethers.ZeroAddress); - }); - - describe('for a non zero account', function () { - it('rejects burning more than balance', async function () { - await expect(this.token.$_burn(this.holder, initialSupply + 1n)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.holder, initialSupply, initialSupply + 1n); - }); - - const describeBurn = function (description, value) { - describe(description, function () { - beforeEach('burning', async function () { - this.tx = await this.token.$_burn(this.holder, value); - }); - - it('decrements totalSupply', async function () { - expect(await this.token.totalSupply()).to.equal(initialSupply - value); - }); - - it('decrements holder balance', async function () { - await expect(this.tx).to.changeTokenBalance(this.token, this.holder, -value); - }); - - it('emits Transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(this.holder, ethers.ZeroAddress, value); - }); - }); - }; - - describeBurn('for entire balance', initialSupply); - describeBurn('for less value than balance', initialSupply - 1n); - }); - }); - - describe('_update', function () { - const value = 1n; - - beforeEach(async function () { - this.totalSupply = await this.token.totalSupply(); - }); - - it('from is the zero address', async function () { - const tx = await this.token.$_update(ethers.ZeroAddress, this.holder, value); - await expect(tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, this.holder, value); - - expect(await this.token.totalSupply()).to.equal(this.totalSupply + value); - await expect(tx).to.changeTokenBalance(this.token, this.holder, value); - }); - - it('to is the zero address', async function () { - const tx = await this.token.$_update(this.holder, ethers.ZeroAddress, value); - await expect(tx).to.emit(this.token, 'Transfer').withArgs(this.holder, ethers.ZeroAddress, value); - - expect(await this.token.totalSupply()).to.equal(this.totalSupply - value); - await expect(tx).to.changeTokenBalance(this.token, this.holder, -value); - }); - - describe('from and to are the same address', function () { - it('zero address', async function () { - const tx = await this.token.$_update(ethers.ZeroAddress, ethers.ZeroAddress, value); - await expect(tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, ethers.ZeroAddress, value); - - expect(await this.token.totalSupply()).to.equal(this.totalSupply); - await expect(tx).to.changeTokenBalance(this.token, ethers.ZeroAddress, 0n); - }); - - describe('non zero address', function () { - it('reverts without balance', async function () { - await expect(this.token.$_update(this.recipient, this.recipient, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.recipient, 0n, value); - }); - - it('executes with balance', async function () { - const tx = await this.token.$_update(this.holder, this.holder, value); - await expect(tx).to.changeTokenBalance(this.token, this.holder, 0n); - await expect(tx).to.emit(this.token, 'Transfer').withArgs(this.holder, this.holder, value); - }); - }); - }); - }); - - describe('_transfer', function () { - beforeEach(function () { - this.transfer = this.token.$_transfer; - }); - - shouldBehaveLikeERC20Transfer(initialSupply); - - it('reverts when the sender is the zero address', async function () { - await expect(this.token.$_transfer(ethers.ZeroAddress, this.recipient, initialSupply)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidSender') - .withArgs(ethers.ZeroAddress); - }); - }); - - describe('_approve', function () { - beforeEach(function () { - this.approve = this.token.$_approve; - }); - - shouldBehaveLikeERC20Approve(initialSupply); - - it('reverts when the owner is the zero address', async function () { - await expect(this.token.$_approve(ethers.ZeroAddress, this.recipient, initialSupply)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidApprover') - .withArgs(ethers.ZeroAddress); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC1363.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC1363.test.js deleted file mode 100644 index 3d1f4e5..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC1363.test.js +++ /dev/null @@ -1,370 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { - shouldBehaveLikeERC20, - shouldBehaveLikeERC20Transfer, - shouldBehaveLikeERC20Approve, -} = require('../ERC20.behavior.js'); -const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); -const { RevertType } = require('../../../helpers/enums.js'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const value = 1000n; -const data = '0x123456'; - -async function fixture() { - // this.accounts is used by shouldBehaveLikeERC20 - const accounts = await ethers.getSigners(); - const [holder, other] = accounts; - - const receiver = await ethers.deployContract('ERC1363ReceiverMock'); - const spender = await ethers.deployContract('ERC1363SpenderMock'); - const token = await ethers.deployContract('$ERC1363', [name, symbol]); - - await token.$_mint(holder, value); - - return { - accounts, - holder, - other, - token, - receiver, - spender, - selectors: { - onTransferReceived: receiver.interface.getFunction('onTransferReceived(address,address,uint256,bytes)').selector, - onApprovalReceived: spender.interface.getFunction('onApprovalReceived(address,uint256,bytes)').selector, - }, - }; -} - -describe('ERC1363', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldSupportInterfaces(['ERC165', 'ERC1363']); - shouldBehaveLikeERC20(value); - - describe('transferAndCall', function () { - describe('as a transfer', function () { - beforeEach(async function () { - this.recipient = this.receiver; - this.transfer = (holder, ...rest) => - this.token.connect(holder).getFunction('transferAndCall(address,uint256)')(...rest); - }); - - shouldBehaveLikeERC20Transfer(value); - }); - - it('reverts transferring to an EOA', async function () { - await expect(this.token.connect(this.holder).getFunction('transferAndCall(address,uint256)')(this.other, value)) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') - .withArgs(this.other.address); - }); - - it('succeeds without data', async function () { - await expect( - this.token.connect(this.holder).getFunction('transferAndCall(address,uint256)')(this.receiver, value), - ) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder.address, this.receiver.target, value) - .to.emit(this.receiver, 'Received') - .withArgs(this.holder.address, this.holder.address, value, '0x'); - }); - - it('succeeds with data', async function () { - await expect( - this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( - this.receiver, - value, - data, - ), - ) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder.address, this.receiver.target, value) - .to.emit(this.receiver, 'Received') - .withArgs(this.holder.address, this.holder.address, value, data); - }); - - it('reverts with reverting hook (without reason)', async function () { - await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithoutMessage); - - await expect( - this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( - this.receiver, - value, - data, - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') - .withArgs(this.receiver.target); - }); - - it('reverts with reverting hook (with reason)', async function () { - await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithMessage); - - await expect( - this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( - this.receiver, - value, - data, - ), - ).to.be.revertedWith('ERC1363ReceiverMock: reverting'); - }); - - it('reverts with reverting hook (with custom error)', async function () { - const reason = '0x12345678'; - await this.receiver.setUp(reason, RevertType.RevertWithCustomError); - - await expect( - this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( - this.receiver, - value, - data, - ), - ) - .to.be.revertedWithCustomError(this.receiver, 'CustomError') - .withArgs(reason); - }); - - it('panics with reverting hook (with panic)', async function () { - await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.Panic); - - await expect( - this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( - this.receiver, - value, - data, - ), - ).to.be.revertedWithPanic(); - }); - - it('reverts with bad return value', async function () { - await this.receiver.setUp('0x12345678', RevertType.None); - - await expect( - this.token.connect(this.holder).getFunction('transferAndCall(address,uint256,bytes)')( - this.receiver, - value, - data, - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') - .withArgs(this.receiver.target); - }); - }); - - describe('transferFromAndCall', function () { - beforeEach(async function () { - await this.token.connect(this.holder).approve(this.other, ethers.MaxUint256); - }); - - describe('as a transfer', function () { - beforeEach(async function () { - this.recipient = this.receiver; - this.transfer = this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)'); - }); - - shouldBehaveLikeERC20Transfer(value); - }); - - it('reverts transferring to an EOA', async function () { - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)')( - this.holder, - this.other, - value, - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') - .withArgs(this.other.address); - }); - - it('succeeds without data', async function () { - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256)')( - this.holder, - this.receiver, - value, - ), - ) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder.address, this.receiver.target, value) - .to.emit(this.receiver, 'Received') - .withArgs(this.other.address, this.holder.address, value, '0x'); - }); - - it('succeeds with data', async function () { - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( - this.holder, - this.receiver, - value, - data, - ), - ) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder.address, this.receiver.target, value) - .to.emit(this.receiver, 'Received') - .withArgs(this.other.address, this.holder.address, value, data); - }); - - it('reverts with reverting hook (without reason)', async function () { - await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithoutMessage); - - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( - this.holder, - this.receiver, - value, - data, - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') - .withArgs(this.receiver.target); - }); - - it('reverts with reverting hook (with reason)', async function () { - await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.RevertWithMessage); - - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( - this.holder, - this.receiver, - value, - data, - ), - ).to.be.revertedWith('ERC1363ReceiverMock: reverting'); - }); - - it('reverts with reverting hook (with custom error)', async function () { - const reason = '0x12345678'; - await this.receiver.setUp(reason, RevertType.RevertWithCustomError); - - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( - this.holder, - this.receiver, - value, - data, - ), - ) - .to.be.revertedWithCustomError(this.receiver, 'CustomError') - .withArgs(reason); - }); - - it('panics with reverting hook (with panic)', async function () { - await this.receiver.setUp(this.selectors.onTransferReceived, RevertType.Panic); - - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( - this.holder, - this.receiver, - value, - data, - ), - ).to.be.revertedWithPanic(); - }); - - it('reverts with bad return value', async function () { - await this.receiver.setUp('0x12345678', RevertType.None); - - await expect( - this.token.connect(this.other).getFunction('transferFromAndCall(address,address,uint256,bytes)')( - this.holder, - this.receiver, - value, - data, - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') - .withArgs(this.receiver.target); - }); - }); - - describe('approveAndCall', function () { - describe('as an approval', function () { - beforeEach(async function () { - this.recipient = this.spender; - this.approve = (holder, ...rest) => - this.token.connect(holder).getFunction('approveAndCall(address,uint256)')(...rest); - }); - - shouldBehaveLikeERC20Approve(value); - }); - - it('reverts approving an EOA', async function () { - await expect(this.token.connect(this.holder).getFunction('approveAndCall(address,uint256)')(this.other, value)) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender') - .withArgs(this.other.address); - }); - - it('succeeds without data', async function () { - await expect(this.token.connect(this.holder).getFunction('approveAndCall(address,uint256)')(this.spender, value)) - .to.emit(this.token, 'Approval') - .withArgs(this.holder.address, this.spender.target, value) - .to.emit(this.spender, 'Approved') - .withArgs(this.holder.address, value, '0x'); - }); - - it('succeeds with data', async function () { - await expect( - this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), - ) - .to.emit(this.token, 'Approval') - .withArgs(this.holder.address, this.spender.target, value) - .to.emit(this.spender, 'Approved') - .withArgs(this.holder.address, value, data); - }); - - it('with reverting hook (without reason)', async function () { - await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.RevertWithoutMessage); - - await expect( - this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender') - .withArgs(this.spender.target); - }); - - it('reverts with reverting hook (with reason)', async function () { - await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.RevertWithMessage); - - await expect( - this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), - ).to.be.revertedWith('ERC1363SpenderMock: reverting'); - }); - - it('reverts with reverting hook (with custom error)', async function () { - const reason = '0x12345678'; - await this.spender.setUp(reason, RevertType.RevertWithCustomError); - - await expect( - this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), - ) - .to.be.revertedWithCustomError(this.spender, 'CustomError') - .withArgs(reason); - }); - - it('panics with reverting hook (with panic)', async function () { - await this.spender.setUp(this.selectors.onApprovalReceived, RevertType.Panic); - - await expect( - this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), - ).to.be.revertedWithPanic(); - }); - - it('reverts with bad return value', async function () { - await this.spender.setUp('0x12345678', RevertType.None); - - await expect( - this.token.connect(this.holder).getFunction('approveAndCall(address,uint256,bytes)')(this.spender, value, data), - ) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidSpender') - .withArgs(this.spender.target); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Burnable.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Burnable.test.js deleted file mode 100644 index dc40c79..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Burnable.test.js +++ /dev/null @@ -1,105 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const initialBalance = 1000n; - -async function fixture() { - const [owner, burner] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC20Burnable', [name, symbol], owner); - await token.$_mint(owner, initialBalance); - - return { owner, burner, token, initialBalance }; -} - -describe('ERC20Burnable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('burn', function () { - it('reverts if not enough balance', async function () { - const value = this.initialBalance + 1n; - - await expect(this.token.connect(this.owner).burn(value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.owner, this.initialBalance, value); - }); - - describe('on success', function () { - for (const { title, value } of [ - { title: 'for a zero value', value: 0n }, - { title: 'for a non-zero value', value: 100n }, - ]) { - describe(title, function () { - beforeEach(async function () { - this.tx = await this.token.connect(this.owner).burn(value); - }); - - it('burns the requested value', async function () { - await expect(this.tx).to.changeTokenBalance(this.token, this.owner, -value); - }); - - it('emits a transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(this.owner, ethers.ZeroAddress, value); - }); - }); - } - }); - }); - - describe('burnFrom', function () { - describe('reverts', function () { - it('if not enough balance', async function () { - const value = this.initialBalance + 1n; - - await this.token.connect(this.owner).approve(this.burner, value); - - await expect(this.token.connect(this.burner).burnFrom(this.owner, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.owner, this.initialBalance, value); - }); - - it('if not enough allowance', async function () { - const allowance = 100n; - - await this.token.connect(this.owner).approve(this.burner, allowance); - - await expect(this.token.connect(this.burner).burnFrom(this.owner, allowance + 1n)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance') - .withArgs(this.burner, allowance, allowance + 1n); - }); - }); - - describe('on success', function () { - for (const { title, value } of [ - { title: 'for a zero value', value: 0n }, - { title: 'for a non-zero value', value: 100n }, - ]) { - describe(title, function () { - const originalAllowance = value * 3n; - - beforeEach(async function () { - await this.token.connect(this.owner).approve(this.burner, originalAllowance); - this.tx = await this.token.connect(this.burner).burnFrom(this.owner, value); - }); - - it('burns the requested value', async function () { - await expect(this.tx).to.changeTokenBalance(this.token, this.owner, -value); - }); - - it('decrements allowance', async function () { - expect(await this.token.allowance(this.owner, this.burner)).to.equal(originalAllowance - value); - }); - - it('emits a transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(this.owner, ethers.ZeroAddress, value); - }); - }); - } - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Capped.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Capped.test.js deleted file mode 100644 index a32ec43..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Capped.test.js +++ /dev/null @@ -1,55 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const cap = 1000n; - -async function fixture() { - const [user] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC20Capped', [name, symbol, cap]); - - return { user, token, cap }; -} - -describe('ERC20Capped', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('requires a non-zero cap', async function () { - const ERC20Capped = await ethers.getContractFactory('$ERC20Capped'); - - await expect(ERC20Capped.deploy(name, symbol, 0)) - .to.be.revertedWithCustomError(ERC20Capped, 'ERC20InvalidCap') - .withArgs(0); - }); - - describe('capped token', function () { - it('starts with the correct cap', async function () { - expect(await this.token.cap()).to.equal(this.cap); - }); - - it('mints when value is less than cap', async function () { - const value = this.cap - 1n; - await this.token.$_mint(this.user, value); - expect(await this.token.totalSupply()).to.equal(value); - }); - - it('fails to mint if the value exceeds the cap', async function () { - await this.token.$_mint(this.user, this.cap - 1n); - await expect(this.token.$_mint(this.user, 2)) - .to.be.revertedWithCustomError(this.token, 'ERC20ExceededCap') - .withArgs(this.cap + 1n, this.cap); - }); - - it('fails to mint after cap is reached', async function () { - await this.token.$_mint(this.user, this.cap); - await expect(this.token.$_mint(this.user, 1)) - .to.be.revertedWithCustomError(this.token, 'ERC20ExceededCap') - .withArgs(this.cap + 1n, this.cap); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20FlashMint.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20FlashMint.test.js deleted file mode 100644 index 1c751f7..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20FlashMint.test.js +++ /dev/null @@ -1,164 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const initialSupply = 100n; -const loanValue = 10_000_000_000_000n; - -async function fixture() { - const [holder, other] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC20FlashMintMock', [name, symbol]); - await token.$_mint(holder, initialSupply); - - return { holder, other, token }; -} - -describe('ERC20FlashMint', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('maxFlashLoan', function () { - it('token match', async function () { - expect(await this.token.maxFlashLoan(this.token)).to.equal(ethers.MaxUint256 - initialSupply); - }); - - it('token mismatch', async function () { - expect(await this.token.maxFlashLoan(ethers.ZeroAddress)).to.equal(0n); - }); - }); - - describe('flashFee', function () { - it('token match', async function () { - expect(await this.token.flashFee(this.token, loanValue)).to.equal(0n); - }); - - it('token mismatch', async function () { - await expect(this.token.flashFee(ethers.ZeroAddress, loanValue)) - .to.be.revertedWithCustomError(this.token, 'ERC3156UnsupportedToken') - .withArgs(ethers.ZeroAddress); - }); - }); - - describe('flashFeeReceiver', function () { - it('default receiver', async function () { - expect(await this.token.$_flashFeeReceiver()).to.equal(ethers.ZeroAddress); - }); - }); - - describe('flashLoan', function () { - it('success', async function () { - const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); - - const tx = await this.token.flashLoan(receiver, this.token, loanValue, '0x'); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, receiver, loanValue) - .to.emit(this.token, 'Transfer') - .withArgs(receiver, ethers.ZeroAddress, loanValue) - .to.emit(receiver, 'BalanceOf') - .withArgs(this.token, receiver, loanValue) - .to.emit(receiver, 'TotalSupply') - .withArgs(this.token, initialSupply + loanValue); - await expect(tx).to.changeTokenBalance(this.token, receiver, 0); - - expect(await this.token.totalSupply()).to.equal(initialSupply); - expect(await this.token.allowance(receiver, this.token)).to.equal(0n); - }); - - it('missing return value', async function () { - const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [false, true]); - await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x')) - .to.be.revertedWithCustomError(this.token, 'ERC3156InvalidReceiver') - .withArgs(receiver); - }); - - it('missing approval', async function () { - const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, false]); - await expect(this.token.flashLoan(receiver, this.token, loanValue, '0x')) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance') - .withArgs(this.token, 0, loanValue); - }); - - it('unavailable funds', async function () { - const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); - const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]); - await expect(this.token.flashLoan(receiver, this.token, loanValue, data)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(receiver, loanValue - 10n, loanValue); - }); - - it('more than maxFlashLoan', async function () { - const receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); - const data = this.token.interface.encodeFunctionData('transfer', [this.other.address, 10]); - await expect(this.token.flashLoan(receiver, this.token, ethers.MaxUint256, data)) - .to.be.revertedWithCustomError(this.token, 'ERC3156ExceededMaxLoan') - .withArgs(ethers.MaxUint256 - initialSupply); - }); - - describe('custom flash fee & custom fee receiver', function () { - const receiverInitialBalance = 200_000n; - const flashFee = 5_000n; - - beforeEach('init receiver balance & set flash fee', async function () { - this.receiver = await ethers.deployContract('ERC3156FlashBorrowerMock', [true, true]); - - const tx = await this.token.$_mint(this.receiver, receiverInitialBalance); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.receiver, receiverInitialBalance); - await expect(tx).to.changeTokenBalance(this.token, this.receiver, receiverInitialBalance); - - await this.token.setFlashFee(flashFee); - expect(await this.token.flashFee(this.token, loanValue)).to.equal(flashFee); - }); - - it('default flash fee receiver', async function () { - const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x'); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.receiver, loanValue) - .to.emit(this.token, 'Transfer') - .withArgs(this.receiver, ethers.ZeroAddress, loanValue + flashFee) - .to.emit(this.receiver, 'BalanceOf') - .withArgs(this.token, this.receiver, receiverInitialBalance + loanValue) - .to.emit(this.receiver, 'TotalSupply') - .withArgs(this.token, initialSupply + receiverInitialBalance + loanValue); - await expect(tx).to.changeTokenBalances(this.token, [this.receiver, ethers.ZeroAddress], [-flashFee, 0]); - - expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance - flashFee); - expect(await this.token.allowance(this.receiver, this.token)).to.equal(0n); - }); - - it('custom flash fee receiver', async function () { - const flashFeeReceiverAddress = this.other; - await this.token.setFlashFeeReceiver(flashFeeReceiverAddress); - expect(await this.token.$_flashFeeReceiver()).to.equal(flashFeeReceiverAddress); - - const tx = await this.token.flashLoan(this.receiver, this.token, loanValue, '0x'); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.receiver, loanValue) - .to.emit(this.token, 'Transfer') - .withArgs(this.receiver, ethers.ZeroAddress, loanValue) - .to.emit(this.token, 'Transfer') - .withArgs(this.receiver, flashFeeReceiverAddress, flashFee) - .to.emit(this.receiver, 'BalanceOf') - .withArgs(this.token, this.receiver, receiverInitialBalance + loanValue) - .to.emit(this.receiver, 'TotalSupply') - .withArgs(this.token, initialSupply + receiverInitialBalance + loanValue); - await expect(tx).to.changeTokenBalances( - this.token, - [this.receiver, flashFeeReceiverAddress], - [-flashFee, flashFee], - ); - - expect(await this.token.totalSupply()).to.equal(initialSupply + receiverInitialBalance); - expect(await this.token.allowance(this.receiver, flashFeeReceiverAddress)).to.equal(0n); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Pausable.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Pausable.test.js deleted file mode 100644 index 1f1157c..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Pausable.test.js +++ /dev/null @@ -1,129 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const initialSupply = 100n; - -async function fixture() { - const [holder, recipient, approved] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC20Pausable', [name, symbol]); - await token.$_mint(holder, initialSupply); - - return { holder, recipient, approved, token }; -} - -describe('ERC20Pausable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('pausable token', function () { - describe('transfer', function () { - it('allows to transfer when unpaused', async function () { - await expect(this.token.connect(this.holder).transfer(this.recipient, initialSupply)).to.changeTokenBalances( - this.token, - [this.holder, this.recipient], - [-initialSupply, initialSupply], - ); - }); - - it('allows to transfer when paused and then unpaused', async function () { - await this.token.$_pause(); - await this.token.$_unpause(); - - await expect(this.token.connect(this.holder).transfer(this.recipient, initialSupply)).to.changeTokenBalances( - this.token, - [this.holder, this.recipient], - [-initialSupply, initialSupply], - ); - }); - - it('reverts when trying to transfer when paused', async function () { - await this.token.$_pause(); - - await expect( - this.token.connect(this.holder).transfer(this.recipient, initialSupply), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - }); - - describe('transfer from', function () { - const allowance = 40n; - - beforeEach(async function () { - await this.token.connect(this.holder).approve(this.approved, allowance); - }); - - it('allows to transfer from when unpaused', async function () { - await expect( - this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance), - ).to.changeTokenBalances(this.token, [this.holder, this.recipient], [-allowance, allowance]); - }); - - it('allows to transfer when paused and then unpaused', async function () { - await this.token.$_pause(); - await this.token.$_unpause(); - - await expect( - this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance), - ).to.changeTokenBalances(this.token, [this.holder, this.recipient], [-allowance, allowance]); - }); - - it('reverts when trying to transfer from when paused', async function () { - await this.token.$_pause(); - - await expect( - this.token.connect(this.approved).transferFrom(this.holder, this.recipient, allowance), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - }); - - describe('mint', function () { - const value = 42n; - - it('allows to mint when unpaused', async function () { - await expect(this.token.$_mint(this.recipient, value)).to.changeTokenBalance(this.token, this.recipient, value); - }); - - it('allows to mint when paused and then unpaused', async function () { - await this.token.$_pause(); - await this.token.$_unpause(); - - await expect(this.token.$_mint(this.recipient, value)).to.changeTokenBalance(this.token, this.recipient, value); - }); - - it('reverts when trying to mint when paused', async function () { - await this.token.$_pause(); - - await expect(this.token.$_mint(this.recipient, value)).to.be.revertedWithCustomError( - this.token, - 'EnforcedPause', - ); - }); - }); - - describe('burn', function () { - const value = 42n; - - it('allows to burn when unpaused', async function () { - await expect(this.token.$_burn(this.holder, value)).to.changeTokenBalance(this.token, this.holder, -value); - }); - - it('allows to burn when paused and then unpaused', async function () { - await this.token.$_pause(); - await this.token.$_unpause(); - - await expect(this.token.$_burn(this.holder, value)).to.changeTokenBalance(this.token, this.holder, -value); - }); - - it('reverts when trying to burn when paused', async function () { - await this.token.$_pause(); - - await expect(this.token.$_burn(this.holder, value)).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Permit.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Permit.test.js deleted file mode 100644 index c3c80d7..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Permit.test.js +++ /dev/null @@ -1,109 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getDomain, domainSeparator, Permit } = require('../../../helpers/eip712'); -const time = require('../../../helpers/time'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const initialSupply = 100n; - -async function fixture() { - const [holder, spender, owner, other] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC20Permit', [name, symbol, name]); - await token.$_mint(holder, initialSupply); - - return { - holder, - spender, - owner, - other, - token, - }; -} - -describe('ERC20Permit', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('initial nonce is 0', async function () { - expect(await this.token.nonces(this.holder)).to.equal(0n); - }); - - it('domain separator', async function () { - expect(await this.token.DOMAIN_SEPARATOR()).to.equal(await getDomain(this.token).then(domainSeparator)); - }); - - describe('permit', function () { - const value = 42n; - const nonce = 0n; - const maxDeadline = ethers.MaxUint256; - - beforeEach(function () { - this.buildData = (contract, deadline = maxDeadline) => - getDomain(contract).then(domain => ({ - domain, - types: { Permit }, - message: { - owner: this.owner.address, - spender: this.spender.address, - value, - nonce, - deadline, - }, - })); - }); - - it('accepts owner signature', async function () { - const { v, r, s } = await this.buildData(this.token) - .then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message)) - .then(ethers.Signature.from); - - await this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s); - - expect(await this.token.nonces(this.owner)).to.equal(1n); - expect(await this.token.allowance(this.owner, this.spender)).to.equal(value); - }); - - it('rejects reused signature', async function () { - const { v, r, s, serialized } = await this.buildData(this.token) - .then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message)) - .then(ethers.Signature.from); - - await this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s); - - const recovered = await this.buildData(this.token).then(({ domain, types, message }) => - ethers.verifyTypedData(domain, types, { ...message, nonce: nonce + 1n, deadline: maxDeadline }, serialized), - ); - - await expect(this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s)) - .to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner') - .withArgs(recovered, this.owner); - }); - - it('rejects other signature', async function () { - const { v, r, s } = await this.buildData(this.token) - .then(({ domain, types, message }) => this.other.signTypedData(domain, types, message)) - .then(ethers.Signature.from); - - await expect(this.token.permit(this.owner, this.spender, value, maxDeadline, v, r, s)) - .to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner') - .withArgs(this.other, this.owner); - }); - - it('rejects expired permit', async function () { - const deadline = (await time.clock.timestamp()) - time.duration.weeks(1); - - const { v, r, s } = await this.buildData(this.token, deadline) - .then(({ domain, types, message }) => this.owner.signTypedData(domain, types, message)) - .then(ethers.Signature.from); - - await expect(this.token.permit(this.owner, this.spender, value, deadline, v, r, s)) - .to.be.revertedWithCustomError(this.token, 'ERC2612ExpiredSignature') - .withArgs(deadline); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Votes.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Votes.test.js deleted file mode 100644 index 3c595c9..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Votes.test.js +++ /dev/null @@ -1,546 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getDomain, Delegation } = require('../../../helpers/eip712'); -const { batchInBlock } = require('../../../helpers/txpool'); -const time = require('../../../helpers/time'); - -const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); - -const TOKENS = [ - { Token: '$ERC20Votes', mode: 'blocknumber' }, - { Token: '$ERC20VotesTimestampMock', mode: 'timestamp' }, -]; - -const name = 'My Token'; -const symbol = 'MTKN'; -const version = '1'; -const supply = ethers.parseEther('10000000'); - -describe('ERC20Votes', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - // accounts is required by shouldBehaveLikeVotes - const accounts = await ethers.getSigners(); - const [holder, recipient, delegatee, other1, other2] = accounts; - - const token = await ethers.deployContract(Token, [name, symbol, name, version]); - const domain = await getDomain(token); - - return { accounts, holder, recipient, delegatee, other1, other2, token, domain }; - }; - - describe(`vote with ${mode}`, function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - this.votes = this.token; - }); - - // includes ERC6372 behavior check - shouldBehaveLikeVotes([1, 17, 42], { mode, fungible: true }); - - it('initial nonce is 0', async function () { - expect(await this.token.nonces(this.holder)).to.equal(0n); - }); - - it('minting restriction', async function () { - const value = 2n ** 208n; - await expect(this.token.$_mint(this.holder, value)) - .to.be.revertedWithCustomError(this.token, 'ERC20ExceededSafeSupply') - .withArgs(value, value - 1n); - }); - - it('recent checkpoints', async function () { - await this.token.connect(this.holder).delegate(this.holder); - for (let i = 0; i < 6; i++) { - await this.token.$_mint(this.holder, 1n); - } - const timepoint = await time.clock[mode](); - expect(await this.token.numCheckpoints(this.holder)).to.equal(6n); - // recent - expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(5n); - // non-recent - expect(await this.token.getPastVotes(this.holder, timepoint - 6n)).to.equal(0n); - }); - - describe('set delegation', function () { - describe('call', function () { - it('delegation with balance', async function () { - await this.token.$_mint(this.holder, supply); - expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress); - - const tx = await this.token.connect(this.holder).delegate(this.holder); - const timepoint = await time.clockFromReceipt[mode](tx); - - await expect(tx) - .to.emit(this.token, 'DelegateChanged') - .withArgs(this.holder, ethers.ZeroAddress, this.holder) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.holder, 0n, supply); - - expect(await this.token.delegates(this.holder)).to.equal(this.holder); - expect(await this.token.getVotes(this.holder)).to.equal(supply); - expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(0n); - await mine(); - expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(supply); - }); - - it('delegation without balance', async function () { - expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress); - - await expect(this.token.connect(this.holder).delegate(this.holder)) - .to.emit(this.token, 'DelegateChanged') - .withArgs(this.holder, ethers.ZeroAddress, this.holder) - .to.not.emit(this.token, 'DelegateVotesChanged'); - - expect(await this.token.delegates(this.holder)).to.equal(this.holder); - }); - }); - - describe('with signature', function () { - const nonce = 0n; - - beforeEach(async function () { - await this.token.$_mint(this.holder, supply); - }); - - it('accept signed delegation', async function () { - const { r, s, v } = await this.holder - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.holder.address, - nonce, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - expect(await this.token.delegates(this.holder)).to.equal(ethers.ZeroAddress); - - const tx = await this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s); - const timepoint = await time.clockFromReceipt[mode](tx); - - await expect(tx) - .to.emit(this.token, 'DelegateChanged') - .withArgs(this.holder, ethers.ZeroAddress, this.holder) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.holder, 0n, supply); - - expect(await this.token.delegates(this.holder)).to.equal(this.holder); - - expect(await this.token.getVotes(this.holder)).to.equal(supply); - expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(0n); - await mine(); - expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(supply); - }); - - it('rejects reused signature', async function () { - const { r, s, v } = await this.holder - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.holder.address, - nonce, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - await this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s); - - await expect(this.token.delegateBySig(this.holder, nonce, ethers.MaxUint256, v, r, s)) - .to.be.revertedWithCustomError(this.token, 'InvalidAccountNonce') - .withArgs(this.holder, nonce + 1n); - }); - - it('rejects bad delegatee', async function () { - const { r, s, v } = await this.holder - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.holder.address, - nonce, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - const tx = await this.token.delegateBySig(this.delegatee, nonce, ethers.MaxUint256, v, r, s); - - const { args } = await tx - .wait() - .then(receipt => receipt.logs.find(event => event.fragment.name == 'DelegateChanged')); - expect(args[0]).to.not.equal(this.holder); - expect(args[1]).to.equal(ethers.ZeroAddress); - expect(args[2]).to.equal(this.delegatee); - }); - - it('rejects bad nonce', async function () { - const { r, s, v, serialized } = await this.holder - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.holder.address, - nonce, - expiry: ethers.MaxUint256, - }, - ) - .then(ethers.Signature.from); - - const recovered = ethers.verifyTypedData( - this.domain, - { Delegation }, - { - delegatee: this.holder.address, - nonce: nonce + 1n, - expiry: ethers.MaxUint256, - }, - serialized, - ); - - await expect(this.token.delegateBySig(this.holder, nonce + 1n, ethers.MaxUint256, v, r, s)) - .to.be.revertedWithCustomError(this.token, 'InvalidAccountNonce') - .withArgs(recovered, nonce); - }); - - it('rejects expired permit', async function () { - const expiry = (await time.clock.timestamp()) - time.duration.weeks(1); - - const { r, s, v } = await this.holder - .signTypedData( - this.domain, - { Delegation }, - { - delegatee: this.holder.address, - nonce, - expiry, - }, - ) - .then(ethers.Signature.from); - - await expect(this.token.delegateBySig(this.holder, nonce, expiry, v, r, s)) - .to.be.revertedWithCustomError(this.token, 'VotesExpiredSignature') - .withArgs(expiry); - }); - }); - }); - - describe('change delegation', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, supply); - await this.token.connect(this.holder).delegate(this.holder); - }); - - it('call', async function () { - expect(await this.token.delegates(this.holder)).to.equal(this.holder); - - const tx = await this.token.connect(this.holder).delegate(this.delegatee); - const timepoint = await time.clockFromReceipt[mode](tx); - - await expect(tx) - .to.emit(this.token, 'DelegateChanged') - .withArgs(this.holder, this.holder, this.delegatee) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.holder, supply, 0n) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.delegatee, 0n, supply); - - expect(await this.token.delegates(this.holder)).to.equal(this.delegatee); - - expect(await this.token.getVotes(this.holder)).to.equal(0n); - expect(await this.token.getVotes(this.delegatee)).to.equal(supply); - expect(await this.token.getPastVotes(this.holder, timepoint - 1n)).to.equal(supply); - expect(await this.token.getPastVotes(this.delegatee, timepoint - 1n)).to.equal(0n); - await mine(); - expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(0n); - expect(await this.token.getPastVotes(this.delegatee, timepoint)).to.equal(supply); - }); - }); - - describe('transfers', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, supply); - }); - - it('no delegation', async function () { - await expect(this.token.connect(this.holder).transfer(this.recipient, 1n)) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, 1n) - .to.not.emit(this.token, 'DelegateVotesChanged'); - - this.holderVotes = 0n; - this.recipientVotes = 0n; - }); - - it('sender delegation', async function () { - await this.token.connect(this.holder).delegate(this.holder); - - const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, 1n) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.holder, supply, supply - 1n); - - const { logs } = await tx.wait(); - const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged'); - for (const event of logs.filter(event => event.fragment.name == 'Transfer')) { - expect(event.index).to.lt(index); - } - - this.holderVotes = supply - 1n; - this.recipientVotes = 0n; - }); - - it('receiver delegation', async function () { - await this.token.connect(this.recipient).delegate(this.recipient); - - const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, 1n) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.recipient, 0n, 1n); - - const { logs } = await tx.wait(); - const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged'); - for (const event of logs.filter(event => event.fragment.name == 'Transfer')) { - expect(event.index).to.lt(index); - } - - this.holderVotes = 0n; - this.recipientVotes = 1n; - }); - - it('full delegation', async function () { - await this.token.connect(this.holder).delegate(this.holder); - await this.token.connect(this.recipient).delegate(this.recipient); - - const tx = await this.token.connect(this.holder).transfer(this.recipient, 1n); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, 1n) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.holder, supply, supply - 1n) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.recipient, 0n, 1n); - - const { logs } = await tx.wait(); - const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged'); - for (const event of logs.filter(event => event.fragment.name == 'Transfer')) { - expect(event.index).to.lt(index); - } - - this.holderVotes = supply - 1n; - this.recipientVotes = 1n; - }); - - afterEach(async function () { - expect(await this.token.getVotes(this.holder)).to.equal(this.holderVotes); - expect(await this.token.getVotes(this.recipient)).to.equal(this.recipientVotes); - - // need to advance 2 blocks to see the effect of a transfer on "getPastVotes" - const timepoint = await time.clock[mode](); - await mine(); - expect(await this.token.getPastVotes(this.holder, timepoint)).to.equal(this.holderVotes); - expect(await this.token.getPastVotes(this.recipient, timepoint)).to.equal(this.recipientVotes); - }); - }); - - // The following tests are a adaptation of https://github.com/compound-finance/compound-protocol/blob/master/tests/Governance/CompTest.js. - describe('Compound test suite', function () { - beforeEach(async function () { - await this.token.$_mint(this.holder, supply); - }); - - describe('balanceOf', function () { - it('grants to initial account', async function () { - expect(await this.token.balanceOf(this.holder)).to.equal(supply); - }); - }); - - describe('numCheckpoints', function () { - it('returns the number of checkpoints for a delegate', async function () { - await this.token.connect(this.holder).transfer(this.recipient, 100n); //give an account a few tokens for readability - expect(await this.token.numCheckpoints(this.other1)).to.equal(0n); - - const t1 = await this.token.connect(this.recipient).delegate(this.other1); - t1.timepoint = await time.clockFromReceipt[mode](t1); - expect(await this.token.numCheckpoints(this.other1)).to.equal(1n); - - const t2 = await this.token.connect(this.recipient).transfer(this.other2, 10); - t2.timepoint = await time.clockFromReceipt[mode](t2); - expect(await this.token.numCheckpoints(this.other1)).to.equal(2n); - - const t3 = await this.token.connect(this.recipient).transfer(this.other2, 10); - t3.timepoint = await time.clockFromReceipt[mode](t3); - expect(await this.token.numCheckpoints(this.other1)).to.equal(3n); - - const t4 = await this.token.connect(this.holder).transfer(this.recipient, 20); - t4.timepoint = await time.clockFromReceipt[mode](t4); - expect(await this.token.numCheckpoints(this.other1)).to.equal(4n); - - expect(await this.token.checkpoints(this.other1, 0n)).to.deep.equal([t1.timepoint, 100n]); - expect(await this.token.checkpoints(this.other1, 1n)).to.deep.equal([t2.timepoint, 90n]); - expect(await this.token.checkpoints(this.other1, 2n)).to.deep.equal([t3.timepoint, 80n]); - expect(await this.token.checkpoints(this.other1, 3n)).to.deep.equal([t4.timepoint, 100n]); - await mine(); - expect(await this.token.getPastVotes(this.other1, t1.timepoint)).to.equal(100n); - expect(await this.token.getPastVotes(this.other1, t2.timepoint)).to.equal(90n); - expect(await this.token.getPastVotes(this.other1, t3.timepoint)).to.equal(80n); - expect(await this.token.getPastVotes(this.other1, t4.timepoint)).to.equal(100n); - }); - - it('does not add more than one checkpoint in a block', async function () { - await this.token.connect(this.holder).transfer(this.recipient, 100n); - expect(await this.token.numCheckpoints(this.other1)).to.equal(0n); - - const [t1, t2, t3] = await batchInBlock([ - () => this.token.connect(this.recipient).delegate(this.other1, { gasLimit: 200000 }), - () => this.token.connect(this.recipient).transfer(this.other2, 10n, { gasLimit: 200000 }), - () => this.token.connect(this.recipient).transfer(this.other2, 10n, { gasLimit: 200000 }), - ]); - t1.timepoint = await time.clockFromReceipt[mode](t1); - t2.timepoint = await time.clockFromReceipt[mode](t2); - t3.timepoint = await time.clockFromReceipt[mode](t3); - - expect(await this.token.numCheckpoints(this.other1)).to.equal(1); - expect(await this.token.checkpoints(this.other1, 0n)).to.be.deep.equal([t1.timepoint, 80n]); - - const t4 = await this.token.connect(this.holder).transfer(this.recipient, 20n); - t4.timepoint = await time.clockFromReceipt[mode](t4); - - expect(await this.token.numCheckpoints(this.other1)).to.equal(2n); - expect(await this.token.checkpoints(this.other1, 1n)).to.be.deep.equal([t4.timepoint, 100n]); - }); - }); - - describe('getPastVotes', function () { - it('reverts if block number >= current block', async function () { - const clock = await this.token.clock(); - await expect(this.token.getPastVotes(this.other1, 50_000_000_000n)) - .to.be.revertedWithCustomError(this.token, 'ERC5805FutureLookup') - .withArgs(50_000_000_000n, clock); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.token.getPastVotes(this.other1, 0n)).to.equal(0n); - }); - - it('returns the latest block if >= last checkpoint block', async function () { - const tx = await this.token.connect(this.holder).delegate(this.other1); - const timepoint = await time.clockFromReceipt[mode](tx); - await mine(2); - - expect(await this.token.getPastVotes(this.other1, timepoint)).to.equal(supply); - expect(await this.token.getPastVotes(this.other1, timepoint + 1n)).to.equal(supply); - }); - - it('returns zero if < first checkpoint block', async function () { - await mine(); - const tx = await this.token.connect(this.holder).delegate(this.other1); - const timepoint = await time.clockFromReceipt[mode](tx); - await mine(2); - - expect(await this.token.getPastVotes(this.other1, timepoint - 1n)).to.equal(0n); - expect(await this.token.getPastVotes(this.other1, timepoint + 1n)).to.equal(supply); - }); - - it('generally returns the voting balance at the appropriate checkpoint', async function () { - const t1 = await this.token.connect(this.holder).delegate(this.other1); - await mine(2); - const t2 = await this.token.connect(this.holder).transfer(this.other2, 10); - await mine(2); - const t3 = await this.token.connect(this.holder).transfer(this.other2, 10); - await mine(2); - const t4 = await this.token.connect(this.other2).transfer(this.holder, 20); - await mine(2); - - t1.timepoint = await time.clockFromReceipt[mode](t1); - t2.timepoint = await time.clockFromReceipt[mode](t2); - t3.timepoint = await time.clockFromReceipt[mode](t3); - t4.timepoint = await time.clockFromReceipt[mode](t4); - - expect(await this.token.getPastVotes(this.other1, t1.timepoint - 1n)).to.equal(0n); - expect(await this.token.getPastVotes(this.other1, t1.timepoint)).to.equal(supply); - expect(await this.token.getPastVotes(this.other1, t1.timepoint + 1n)).to.equal(supply); - expect(await this.token.getPastVotes(this.other1, t2.timepoint)).to.equal(supply - 10n); - expect(await this.token.getPastVotes(this.other1, t2.timepoint + 1n)).to.equal(supply - 10n); - expect(await this.token.getPastVotes(this.other1, t3.timepoint)).to.equal(supply - 20n); - expect(await this.token.getPastVotes(this.other1, t3.timepoint + 1n)).to.equal(supply - 20n); - expect(await this.token.getPastVotes(this.other1, t4.timepoint)).to.equal(supply); - expect(await this.token.getPastVotes(this.other1, t4.timepoint + 1n)).to.equal(supply); - }); - }); - }); - - describe('getPastTotalSupply', function () { - beforeEach(async function () { - await this.token.connect(this.holder).delegate(this.holder); - }); - - it('reverts if block number >= current block', async function () { - const clock = await this.token.clock(); - await expect(this.token.getPastTotalSupply(50_000_000_000n)) - .to.be.revertedWithCustomError(this.token, 'ERC5805FutureLookup') - .withArgs(50_000_000_000n, clock); - }); - - it('returns 0 if there are no checkpoints', async function () { - expect(await this.token.getPastTotalSupply(0n)).to.equal(0n); - }); - - it('returns the latest block if >= last checkpoint block', async function () { - const tx = await this.token.$_mint(this.holder, supply); - const timepoint = await time.clockFromReceipt[mode](tx); - await mine(2); - - expect(await this.token.getPastTotalSupply(timepoint)).to.equal(supply); - expect(await this.token.getPastTotalSupply(timepoint + 1n)).to.equal(supply); - }); - - it('returns zero if < first checkpoint block', async function () { - await mine(); - const tx = await this.token.$_mint(this.holder, supply); - const timepoint = await time.clockFromReceipt[mode](tx); - await mine(2); - - expect(await this.token.getPastTotalSupply(timepoint - 1n)).to.equal(0n); - expect(await this.token.getPastTotalSupply(timepoint + 1n)).to.equal(supply); - }); - - it('generally returns the voting balance at the appropriate checkpoint', async function () { - const t1 = await this.token.$_mint(this.holder, supply); - await mine(2); - const t2 = await this.token.$_burn(this.holder, 10n); - await mine(2); - const t3 = await this.token.$_burn(this.holder, 10n); - await mine(2); - const t4 = await this.token.$_mint(this.holder, 20n); - await mine(2); - - t1.timepoint = await time.clockFromReceipt[mode](t1); - t2.timepoint = await time.clockFromReceipt[mode](t2); - t3.timepoint = await time.clockFromReceipt[mode](t3); - t4.timepoint = await time.clockFromReceipt[mode](t4); - - expect(await this.token.getPastTotalSupply(t1.timepoint - 1n)).to.equal(0n); - expect(await this.token.getPastTotalSupply(t1.timepoint)).to.equal(supply); - expect(await this.token.getPastTotalSupply(t1.timepoint + 1n)).to.equal(supply); - expect(await this.token.getPastTotalSupply(t2.timepoint)).to.equal(supply - 10n); - expect(await this.token.getPastTotalSupply(t2.timepoint + 1n)).to.equal(supply - 10n); - expect(await this.token.getPastTotalSupply(t3.timepoint)).to.equal(supply - 20n); - expect(await this.token.getPastTotalSupply(t3.timepoint + 1n)).to.equal(supply - 20n); - expect(await this.token.getPastTotalSupply(t4.timepoint)).to.equal(supply); - expect(await this.token.getPastTotalSupply(t4.timepoint + 1n)).to.equal(supply); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Wrapper.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Wrapper.test.js deleted file mode 100644 index 9e72e1a..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC20Wrapper.test.js +++ /dev/null @@ -1,203 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldBehaveLikeERC20 } = require('../ERC20.behavior'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const decimals = 9n; -const initialSupply = 100n; - -async function fixture() { - // this.accounts is used by shouldBehaveLikeERC20 - const accounts = await ethers.getSigners(); - const [holder, recipient, other] = accounts; - - const underlying = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, decimals]); - await underlying.$_mint(holder, initialSupply); - - const token = await ethers.deployContract('$ERC20Wrapper', [`Wrapped ${name}`, `W${symbol}`, underlying]); - - return { accounts, holder, recipient, other, underlying, token }; -} - -describe('ERC20Wrapper', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - afterEach('Underlying balance', async function () { - expect(await this.underlying.balanceOf(this.token)).to.equal(await this.token.totalSupply()); - }); - - it('has a name', async function () { - expect(await this.token.name()).to.equal(`Wrapped ${name}`); - }); - - it('has a symbol', async function () { - expect(await this.token.symbol()).to.equal(`W${symbol}`); - }); - - it('has the same decimals as the underlying token', async function () { - expect(await this.token.decimals()).to.equal(decimals); - }); - - it('decimals default back to 18 if token has no metadata', async function () { - const noDecimals = await ethers.deployContract('CallReceiverMock'); - const token = await ethers.deployContract('$ERC20Wrapper', [`Wrapped ${name}`, `W${symbol}`, noDecimals]); - expect(await token.decimals()).to.equal(18n); - }); - - it('has underlying', async function () { - expect(await this.token.underlying()).to.equal(this.underlying); - }); - - describe('deposit', function () { - it('executes with approval', async function () { - await this.underlying.connect(this.holder).approve(this.token, initialSupply); - - const tx = await this.token.connect(this.holder).depositFor(this.holder, initialSupply); - await expect(tx) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.holder, this.token, initialSupply) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.holder, initialSupply); - await expect(tx).to.changeTokenBalances( - this.underlying, - [this.holder, this.token], - [-initialSupply, initialSupply], - ); - await expect(tx).to.changeTokenBalance(this.token, this.holder, initialSupply); - }); - - it('reverts when missing approval', async function () { - await expect(this.token.connect(this.holder).depositFor(this.holder, initialSupply)) - .to.be.revertedWithCustomError(this.underlying, 'ERC20InsufficientAllowance') - .withArgs(this.token, 0, initialSupply); - }); - - it('reverts when inssuficient balance', async function () { - await this.underlying.connect(this.holder).approve(this.token, ethers.MaxUint256); - - await expect(this.token.connect(this.holder).depositFor(this.holder, ethers.MaxUint256)) - .to.be.revertedWithCustomError(this.underlying, 'ERC20InsufficientBalance') - .withArgs(this.holder, initialSupply, ethers.MaxUint256); - }); - - it('deposits to other account', async function () { - await this.underlying.connect(this.holder).approve(this.token, initialSupply); - - const tx = await this.token.connect(this.holder).depositFor(this.recipient, initialSupply); - await expect(tx) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.holder, this.token.target, initialSupply) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, initialSupply); - await expect(tx).to.changeTokenBalances( - this.underlying, - [this.holder, this.token], - [-initialSupply, initialSupply], - ); - await expect(tx).to.changeTokenBalances(this.token, [this.holder, this.recipient], [0, initialSupply]); - }); - - it('reverts minting to the wrapper contract', async function () { - await this.underlying.connect(this.holder).approve(this.token, ethers.MaxUint256); - - await expect(this.token.connect(this.holder).depositFor(this.token, ethers.MaxUint256)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver') - .withArgs(this.token); - }); - }); - - describe('withdraw', function () { - beforeEach(async function () { - await this.underlying.connect(this.holder).approve(this.token, initialSupply); - await this.token.connect(this.holder).depositFor(this.holder, initialSupply); - }); - - it('reverts when inssuficient balance', async function () { - await expect(this.token.connect(this.holder).withdrawTo(this.holder, ethers.MaxInt256)) - .to.be.revertedWithCustomError(this.token, 'ERC20InsufficientBalance') - .withArgs(this.holder, initialSupply, ethers.MaxInt256); - }); - - it('executes when operation is valid', async function () { - const value = 42n; - - const tx = await this.token.connect(this.holder).withdrawTo(this.holder, value); - await expect(tx) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token.target, this.holder, value) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, value); - await expect(tx).to.changeTokenBalances(this.underlying, [this.token, this.holder], [-value, value]); - await expect(tx).to.changeTokenBalance(this.token, this.holder, -value); - }); - - it('entire balance', async function () { - const tx = await this.token.connect(this.holder).withdrawTo(this.holder, initialSupply); - await expect(tx) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token.target, this.holder, initialSupply) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, initialSupply); - await expect(tx).to.changeTokenBalances( - this.underlying, - [this.token, this.holder], - [-initialSupply, initialSupply], - ); - await expect(tx).to.changeTokenBalance(this.token, this.holder, -initialSupply); - }); - - it('to other account', async function () { - const tx = await this.token.connect(this.holder).withdrawTo(this.recipient, initialSupply); - await expect(tx) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token, this.recipient, initialSupply) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, initialSupply); - await expect(tx).to.changeTokenBalances( - this.underlying, - [this.token, this.holder, this.recipient], - [-initialSupply, 0, initialSupply], - ); - await expect(tx).to.changeTokenBalance(this.token, this.holder, -initialSupply); - }); - - it('reverts withdrawing to the wrapper contract', async function () { - await expect(this.token.connect(this.holder).withdrawTo(this.token, initialSupply)) - .to.be.revertedWithCustomError(this.token, 'ERC20InvalidReceiver') - .withArgs(this.token); - }); - }); - - describe('recover', function () { - it('nothing to recover', async function () { - await this.underlying.connect(this.holder).approve(this.token, initialSupply); - await this.token.connect(this.holder).depositFor(this.holder, initialSupply); - - const tx = await this.token.$_recover(this.recipient); - await expect(tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, this.recipient, 0n); - await expect(tx).to.changeTokenBalance(this.token, this.recipient, 0); - }); - - it('something to recover', async function () { - await this.underlying.connect(this.holder).transfer(this.token, initialSupply); - - const tx = await this.token.$_recover(this.recipient); - await expect(tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, this.recipient, initialSupply); - await expect(tx).to.changeTokenBalance(this.token, this.recipient, initialSupply); - }); - }); - - describe('erc20 behaviour', function () { - beforeEach(async function () { - await this.underlying.connect(this.holder).approve(this.token, initialSupply); - await this.token.connect(this.holder).depositFor(this.holder, initialSupply); - }); - - shouldBehaveLikeERC20(initialSupply); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC4626.t.sol b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC4626.t.sol deleted file mode 100644 index 72b0dac..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC4626.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; - -import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; -import {ERC4626Mock} from "@openzeppelin/contracts/mocks/token/ERC4626Mock.sol"; -import {ERC4626OffsetMock} from "@openzeppelin/contracts/mocks/token/ERC4626OffsetMock.sol"; - -contract ERC4626VaultOffsetMock is ERC4626OffsetMock { - constructor( - ERC20 underlying_, - uint8 offset_ - ) ERC20("My Token Vault", "MTKNV") ERC4626(underlying_) ERC4626OffsetMock(offset_) {} -} - -contract ERC4626StdTest is ERC4626Test { - ERC20 private _underlying = new ERC20Mock(); - - function setUp() public override { - _underlying_ = address(_underlying); - _vault_ = address(new ERC4626Mock(_underlying_)); - _delta_ = 0; - _vaultMayBeEmpty = true; - _unlimitedAmount = true; - } - - /** - * @dev Check the case where calculated `decimals` value overflows the `uint8` type. - */ - function testFuzzDecimalsOverflow(uint8 offset) public { - /// @dev Remember that the `_underlying` exhibits a `decimals` value of 18. - offset = uint8(bound(uint256(offset), 238, uint256(type(uint8).max))); - ERC4626VaultOffsetMock erc4626VaultOffsetMock = new ERC4626VaultOffsetMock(_underlying, offset); - vm.expectRevert(); - erc4626VaultOffsetMock.decimals(); - } -} diff --git a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC4626.test.js b/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC4626.test.js deleted file mode 100644 index 71c7cba..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/extensions/ERC4626.test.js +++ /dev/null @@ -1,888 +0,0 @@ -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 { Enum } = require('../../../helpers/enums'); - -const name = 'My Token'; -const symbol = 'MTKN'; -const decimals = 18n; - -async function fixture() { - const [holder, recipient, spender, other, ...accounts] = await ethers.getSigners(); - return { holder, recipient, spender, other, accounts }; -} - -describe('ERC4626', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('inherit decimals if from asset', async function () { - for (const decimals of [0n, 9n, 12n, 18n, 36n]) { - const token = await ethers.deployContract('$ERC20DecimalsMock', ['', '', decimals]); - const vault = await ethers.deployContract('$ERC4626', ['', '', token]); - expect(await vault.decimals()).to.equal(decimals); - } - }); - - it('asset has not yet been created', async function () { - const vault = await ethers.deployContract('$ERC4626', ['', '', this.other.address]); - expect(await vault.decimals()).to.equal(decimals); - }); - - it('underlying excess decimals', async function () { - const token = await ethers.deployContract('$ERC20ExcessDecimalsMock'); - const vault = await ethers.deployContract('$ERC4626', ['', '', token]); - expect(await vault.decimals()).to.equal(decimals); - }); - - it('decimals overflow', async function () { - for (const offset of [243n, 250n, 255n]) { - const token = await ethers.deployContract('$ERC20DecimalsMock', ['', '', decimals]); - const vault = await ethers.deployContract('$ERC4626OffsetMock', ['', '', token, offset]); - await expect(vault.decimals()).to.be.revertedWithPanic(PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW); - } - }); - - describe('reentrancy', function () { - const reenterType = Enum('No', 'Before', 'After'); - - const value = 1_000_000_000_000_000_000n; - const reenterValue = 1_000_000_000n; - - beforeEach(async function () { - // Use offset 1 so the rate is not 1:1 and we can't possibly confuse assets and shares - const token = await ethers.deployContract('$ERC20Reentrant'); - const vault = await ethers.deployContract('$ERC4626OffsetMock', ['', '', token, 1n]); - // Funds and approval for tests - await token.$_mint(this.holder, value); - await token.$_mint(this.other, value); - await token.$_approve(this.holder, vault, ethers.MaxUint256); - await token.$_approve(this.other, vault, ethers.MaxUint256); - await token.$_approve(token, vault, ethers.MaxUint256); - - Object.assign(this, { token, vault }); - }); - - // During a `_deposit`, the vault does `transferFrom(depositor, vault, assets)` -> `_mint(receiver, shares)` - // such that a reentrancy BEFORE the transfer guarantees the price is kept the same. - // If the order of transfer -> mint is changed to mint -> transfer, the reentrancy could be triggered on an - // intermediate state in which the ratio of assets/shares has been decreased (more shares than assets). - it('correct share price is observed during reentrancy before deposit', async function () { - // mint token for deposit - await this.token.$_mint(this.token, reenterValue); - - // Schedules a reentrancy from the token contract - await this.token.scheduleReenter( - reenterType.Before, - this.vault, - this.vault.interface.encodeFunctionData('deposit', [reenterValue, this.holder.address]), - ); - - // Initial share price - const sharesForDeposit = await this.vault.previewDeposit(value); - const sharesForReenter = await this.vault.previewDeposit(reenterValue); - - await expect(this.vault.connect(this.holder).deposit(value, this.holder)) - // Deposit normally, reentering before the internal `_update` - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.holder, value, sharesForDeposit) - // Reentrant deposit event → uses the same price - .to.emit(this.vault, 'Deposit') - .withArgs(this.token, this.holder, reenterValue, sharesForReenter); - - // Assert prices is kept - expect(await this.vault.previewDeposit(value)).to.equal(sharesForDeposit); - }); - - // During a `_withdraw`, the vault does `_burn(owner, shares)` -> `transfer(receiver, assets)` - // such that a reentrancy AFTER the transfer guarantees the price is kept the same. - // If the order of burn -> transfer is changed to transfer -> burn, the reentrancy could be triggered on an - // intermediate state in which the ratio of shares/assets has been decreased (more assets than shares). - it('correct share price is observed during reentrancy after withdraw', async function () { - // Deposit into the vault: holder gets `value` share, token.address gets `reenterValue` shares - await this.vault.connect(this.holder).deposit(value, this.holder); - await this.vault.connect(this.other).deposit(reenterValue, this.token); - - // Schedules a reentrancy from the token contract - await this.token.scheduleReenter( - reenterType.After, - this.vault, - this.vault.interface.encodeFunctionData('withdraw', [reenterValue, this.holder.address, this.token.target]), - ); - - // Initial share price - const sharesForWithdraw = await this.vault.previewWithdraw(value); - const sharesForReenter = await this.vault.previewWithdraw(reenterValue); - - // Do withdraw normally, triggering the _afterTokenTransfer hook - await expect(this.vault.connect(this.holder).withdraw(value, this.holder, this.holder)) - // Main withdraw event - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.holder, this.holder, value, sharesForWithdraw) - // Reentrant withdraw event → uses the same price - .to.emit(this.vault, 'Withdraw') - .withArgs(this.token, this.holder, this.token, reenterValue, sharesForReenter); - - // Assert price is kept - expect(await this.vault.previewWithdraw(value)).to.equal(sharesForWithdraw); - }); - - // Donate newly minted tokens to the vault during the reentracy causes the share price to increase. - // Still, the deposit that trigger the reentracy is not affected and get the previewed price. - // Further deposits will get a different price (getting fewer shares for the same value of assets) - it('share price change during reentracy does not affect deposit', async function () { - // Schedules a reentrancy from the token contract that mess up the share price - await this.token.scheduleReenter( - reenterType.Before, - this.token, - this.token.interface.encodeFunctionData('$_mint', [this.vault.target, reenterValue]), - ); - - // Price before - const sharesBefore = await this.vault.previewDeposit(value); - - // Deposit, reentering before the internal `_update` - await expect(this.vault.connect(this.holder).deposit(value, this.holder)) - // Price is as previewed - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.holder, value, sharesBefore); - - // Price was modified during reentrancy - expect(await this.vault.previewDeposit(value)).to.lt(sharesBefore); - }); - - // Burn some tokens from the vault during the reentracy causes the share price to drop. - // Still, the withdraw that trigger the reentracy is not affected and get the previewed price. - // Further withdraw will get a different price (needing more shares for the same value of assets) - it('share price change during reentracy does not affect withdraw', async function () { - await this.vault.connect(this.holder).deposit(value, this.holder); - await this.vault.connect(this.other).deposit(value, this.other); - - // Schedules a reentrancy from the token contract that mess up the share price - await this.token.scheduleReenter( - reenterType.After, - this.token, - this.token.interface.encodeFunctionData('$_burn', [this.vault.target, reenterValue]), - ); - - // Price before - const sharesBefore = await this.vault.previewWithdraw(value); - - // Withdraw, triggering the _afterTokenTransfer hook - await expect(this.vault.connect(this.holder).withdraw(value, this.holder, this.holder)) - // Price is as previewed - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.holder, this.holder, value, sharesBefore); - - // Price was modified during reentrancy - expect(await this.vault.previewWithdraw(value)).to.gt(sharesBefore); - }); - }); - - describe('limits', function () { - beforeEach(async function () { - const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, decimals]); - const vault = await ethers.deployContract('$ERC4626LimitsMock', ['', '', token]); - - Object.assign(this, { token, vault }); - }); - - it('reverts on deposit() above max deposit', async function () { - const maxDeposit = await this.vault.maxDeposit(this.holder); - await expect(this.vault.connect(this.holder).deposit(maxDeposit + 1n, this.recipient)) - .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxDeposit') - .withArgs(this.recipient, maxDeposit + 1n, maxDeposit); - }); - - it('reverts on mint() above max mint', async function () { - const maxMint = await this.vault.maxMint(this.holder); - - await expect(this.vault.connect(this.holder).mint(maxMint + 1n, this.recipient)) - .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxMint') - .withArgs(this.recipient, maxMint + 1n, maxMint); - }); - - it('reverts on withdraw() above max withdraw', async function () { - const maxWithdraw = await this.vault.maxWithdraw(this.holder); - - await expect(this.vault.connect(this.holder).withdraw(maxWithdraw + 1n, this.recipient, this.holder)) - .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxWithdraw') - .withArgs(this.holder, maxWithdraw + 1n, maxWithdraw); - }); - - it('reverts on redeem() above max redeem', async function () { - const maxRedeem = await this.vault.maxRedeem(this.holder); - - await expect(this.vault.connect(this.holder).redeem(maxRedeem + 1n, this.recipient, this.holder)) - .to.be.revertedWithCustomError(this.vault, 'ERC4626ExceededMaxRedeem') - .withArgs(this.holder, maxRedeem + 1n, maxRedeem); - }); - }); - - for (const offset of [0n, 6n, 18n]) { - const parseToken = token => token * 10n ** decimals; - const parseShare = share => share * 10n ** (decimals + offset); - - const virtualAssets = 1n; - const virtualShares = 10n ** offset; - - describe(`offset: ${offset}`, function () { - beforeEach(async function () { - const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, decimals]); - const vault = await ethers.deployContract('$ERC4626OffsetMock', [name + ' Vault', symbol + 'V', token, offset]); - - await token.$_mint(this.holder, ethers.MaxUint256 / 2n); // 50% of maximum - await token.$_approve(this.holder, vault, ethers.MaxUint256); - await vault.$_approve(this.holder, this.spender, ethers.MaxUint256); - - Object.assign(this, { token, vault }); - }); - - it('metadata', async function () { - expect(await this.vault.name()).to.equal(name + ' Vault'); - expect(await this.vault.symbol()).to.equal(symbol + 'V'); - expect(await this.vault.decimals()).to.equal(decimals + offset); - expect(await this.vault.asset()).to.equal(this.token); - }); - - describe('empty vault: no assets & no shares', function () { - it('status', async function () { - expect(await this.vault.totalAssets()).to.equal(0n); - }); - - it('deposit', async function () { - expect(await this.vault.maxDeposit(this.holder)).to.equal(ethers.MaxUint256); - expect(await this.vault.previewDeposit(parseToken(1n))).to.equal(parseShare(1n)); - - const tx = this.vault.connect(this.holder).deposit(parseToken(1n), this.recipient); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.holder, this.vault], - [-parseToken(1n), parseToken(1n)], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.recipient, parseShare(1n)); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.vault, parseToken(1n)) - .to.emit(this.vault, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, parseShare(1n)) - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.recipient, parseToken(1n), parseShare(1n)); - }); - - it('mint', async function () { - expect(await this.vault.maxMint(this.holder)).to.equal(ethers.MaxUint256); - expect(await this.vault.previewMint(parseShare(1n))).to.equal(parseToken(1n)); - - const tx = this.vault.connect(this.holder).mint(parseShare(1n), this.recipient); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.holder, this.vault], - [-parseToken(1n), parseToken(1n)], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.recipient, parseShare(1n)); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.vault, parseToken(1n)) - .to.emit(this.vault, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, parseShare(1n)) - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.recipient, parseToken(1n), parseShare(1n)); - }); - - it('withdraw', async function () { - expect(await this.vault.maxWithdraw(this.holder)).to.equal(0n); - expect(await this.vault.previewWithdraw(0n)).to.equal(0n); - - const tx = this.vault.connect(this.holder).withdraw(0n, this.recipient, this.holder); - - await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); - await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.recipient, 0n) - .to.emit(this.vault, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, 0n) - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.recipient, this.holder, 0n, 0n); - }); - - it('redeem', async function () { - expect(await this.vault.maxRedeem(this.holder)).to.equal(0n); - expect(await this.vault.previewRedeem(0n)).to.equal(0n); - - const tx = this.vault.connect(this.holder).redeem(0n, this.recipient, this.holder); - - await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); - await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.recipient, 0n) - .to.emit(this.vault, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, 0n) - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.recipient, this.holder, 0n, 0n); - }); - }); - - describe('inflation attack: offset price by direct deposit of assets', function () { - beforeEach(async function () { - // Donate 1 token to the vault to offset the price - await this.token.$_mint(this.vault, parseToken(1n)); - }); - - it('status', async function () { - expect(await this.vault.totalSupply()).to.equal(0n); - expect(await this.vault.totalAssets()).to.equal(parseToken(1n)); - }); - - /** - * | offset | deposited assets | redeemable assets | - * |--------|----------------------|----------------------| - * | 0 | 1.000000000000000000 | 0. | - * | 6 | 1.000000000000000000 | 0.999999000000000000 | - * | 18 | 1.000000000000000000 | 0.999999999999999999 | - * - * Attack is possible, but made difficult by the offset. For the attack to be successful - * the attacker needs to frontrun a deposit 10**offset times bigger than what the victim - * was trying to deposit - */ - it('deposit', async function () { - const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; - const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - - const depositAssets = parseToken(1n); - const expectedShares = (depositAssets * effectiveShares) / effectiveAssets; - - expect(await this.vault.maxDeposit(this.holder)).to.equal(ethers.MaxUint256); - expect(await this.vault.previewDeposit(depositAssets)).to.equal(expectedShares); - - const tx = this.vault.connect(this.holder).deposit(depositAssets, this.recipient); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.holder, this.vault], - [-depositAssets, depositAssets], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.recipient, expectedShares); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.vault, depositAssets) - .to.emit(this.vault, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, expectedShares) - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.recipient, depositAssets, expectedShares); - }); - - /** - * | offset | deposited assets | redeemable assets | - * |--------|----------------------|----------------------| - * | 0 | 1000000000000000001. | 1000000000000000001. | - * | 6 | 1000000000000000001. | 1000000000000000001. | - * | 18 | 1000000000000000001. | 1000000000000000001. | - * - * Using mint protects against inflation attack, but makes minting shares very expensive. - * The ER20 allowance for the underlying asset is needed to protect the user from (too) - * large deposits. - */ - it('mint', async function () { - const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; - const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - - const mintShares = parseShare(1n); - const expectedAssets = (mintShares * effectiveAssets) / effectiveShares; - - expect(await this.vault.maxMint(this.holder)).to.equal(ethers.MaxUint256); - expect(await this.vault.previewMint(mintShares)).to.equal(expectedAssets); - - const tx = this.vault.connect(this.holder).mint(mintShares, this.recipient); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.holder, this.vault], - [-expectedAssets, expectedAssets], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.recipient, mintShares); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.vault, expectedAssets) - .to.emit(this.vault, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, mintShares) - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.recipient, expectedAssets, mintShares); - }); - - it('withdraw', async function () { - expect(await this.vault.maxWithdraw(this.holder)).to.equal(0n); - expect(await this.vault.previewWithdraw(0n)).to.equal(0n); - - const tx = this.vault.connect(this.holder).withdraw(0n, this.recipient, this.holder); - - await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); - await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.recipient, 0n) - .to.emit(this.vault, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, 0n) - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.recipient, this.holder, 0n, 0n); - }); - - it('redeem', async function () { - expect(await this.vault.maxRedeem(this.holder)).to.equal(0n); - expect(await this.vault.previewRedeem(0n)).to.equal(0n); - - const tx = this.vault.connect(this.holder).redeem(0n, this.recipient, this.holder); - - await expect(tx).to.changeTokenBalances(this.token, [this.vault, this.recipient], [0n, 0n]); - await expect(tx).to.changeTokenBalance(this.vault, this.holder, 0n); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.recipient, 0n) - .to.emit(this.vault, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, 0n) - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.recipient, this.holder, 0n, 0n); - }); - }); - - describe('full vault: assets & shares', function () { - beforeEach(async function () { - // Add 1 token of underlying asset and 100 shares to the vault - await this.token.$_mint(this.vault, parseToken(1n)); - await this.vault.$_mint(this.holder, parseShare(100n)); - }); - - it('status', async function () { - expect(await this.vault.totalSupply()).to.equal(parseShare(100n)); - expect(await this.vault.totalAssets()).to.equal(parseToken(1n)); - }); - - /** - * | offset | deposited assets | redeemable assets | - * |--------|--------------------- |----------------------| - * | 0 | 1.000000000000000000 | 0.999999999999999999 | - * | 6 | 1.000000000000000000 | 0.999999999999999999 | - * | 18 | 1.000000000000000000 | 0.999999999999999999 | - * - * Virtual shares & assets captures part of the value - */ - it('deposit', async function () { - const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; - const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - - const depositAssets = parseToken(1n); - const expectedShares = (depositAssets * effectiveShares) / effectiveAssets; - - expect(await this.vault.maxDeposit(this.holder)).to.equal(ethers.MaxUint256); - expect(await this.vault.previewDeposit(depositAssets)).to.equal(expectedShares); - - const tx = this.vault.connect(this.holder).deposit(depositAssets, this.recipient); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.holder, this.vault], - [-depositAssets, depositAssets], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.recipient, expectedShares); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.vault, depositAssets) - .to.emit(this.vault, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, expectedShares) - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.recipient, depositAssets, expectedShares); - }); - - /** - * | offset | deposited assets | redeemable assets | - * |--------|--------------------- |----------------------| - * | 0 | 0.010000000000000001 | 0.010000000000000000 | - * | 6 | 0.010000000000000001 | 0.010000000000000000 | - * | 18 | 0.010000000000000001 | 0.010000000000000000 | - * - * Virtual shares & assets captures part of the value - */ - it('mint', async function () { - const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; - const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - - const mintShares = parseShare(1n); - const expectedAssets = (mintShares * effectiveAssets) / effectiveShares + 1n; // add for the rounding - - expect(await this.vault.maxMint(this.holder)).to.equal(ethers.MaxUint256); - expect(await this.vault.previewMint(mintShares)).to.equal(expectedAssets); - - const tx = this.vault.connect(this.holder).mint(mintShares, this.recipient); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.holder, this.vault], - [-expectedAssets, expectedAssets], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.recipient, mintShares); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.vault, expectedAssets) - .to.emit(this.vault, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, mintShares) - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.recipient, expectedAssets, mintShares); - }); - - it('withdraw', async function () { - const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; - const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - - const withdrawAssets = parseToken(1n); - const expectedShares = (withdrawAssets * effectiveShares) / effectiveAssets + 1n; // add for the rounding - - expect(await this.vault.maxWithdraw(this.holder)).to.equal(withdrawAssets); - expect(await this.vault.previewWithdraw(withdrawAssets)).to.equal(expectedShares); - - const tx = this.vault.connect(this.holder).withdraw(withdrawAssets, this.recipient, this.holder); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.vault, this.recipient], - [-withdrawAssets, withdrawAssets], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.holder, -expectedShares); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.recipient, withdrawAssets) - .to.emit(this.vault, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, expectedShares) - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.recipient, this.holder, withdrawAssets, expectedShares); - }); - - it('withdraw with approval', async function () { - const assets = await this.vault.previewWithdraw(parseToken(1n)); - - await expect(this.vault.connect(this.other).withdraw(parseToken(1n), this.recipient, this.holder)) - .to.be.revertedWithCustomError(this.vault, 'ERC20InsufficientAllowance') - .withArgs(this.other, 0n, assets); - - await expect(this.vault.connect(this.spender).withdraw(parseToken(1n), this.recipient, this.holder)).to.not.be - .reverted; - }); - - it('redeem', async function () { - const effectiveAssets = (await this.vault.totalAssets()) + virtualAssets; - const effectiveShares = (await this.vault.totalSupply()) + virtualShares; - - const redeemShares = parseShare(100n); - const expectedAssets = (redeemShares * effectiveAssets) / effectiveShares; - - expect(await this.vault.maxRedeem(this.holder)).to.equal(redeemShares); - expect(await this.vault.previewRedeem(redeemShares)).to.equal(expectedAssets); - - const tx = this.vault.connect(this.holder).redeem(redeemShares, this.recipient, this.holder); - - await expect(tx).to.changeTokenBalances( - this.token, - [this.vault, this.recipient], - [-expectedAssets, expectedAssets], - ); - await expect(tx).to.changeTokenBalance(this.vault, this.holder, -redeemShares); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.recipient, expectedAssets) - .to.emit(this.vault, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, redeemShares) - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.recipient, this.holder, expectedAssets, redeemShares); - }); - - it('redeem with approval', async function () { - await expect(this.vault.connect(this.other).redeem(parseShare(100n), this.recipient, this.holder)) - .to.be.revertedWithCustomError(this.vault, 'ERC20InsufficientAllowance') - .withArgs(this.other, 0n, parseShare(100n)); - - await expect(this.vault.connect(this.spender).redeem(parseShare(100n), this.recipient, this.holder)).to.not.be - .reverted; - }); - }); - }); - } - - describe('ERC4626Fees', function () { - const feeBasisPoints = 500n; // 5% - const valueWithoutFees = 10_000n; - const fees = (valueWithoutFees * feeBasisPoints) / 10_000n; - const valueWithFees = valueWithoutFees + fees; - - describe('input fees', function () { - beforeEach(async function () { - const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 18n]); - const vault = await ethers.deployContract('$ERC4626FeesMock', [ - '', - '', - token, - feeBasisPoints, - this.other, - 0n, - ethers.ZeroAddress, - ]); - - await token.$_mint(this.holder, ethers.MaxUint256 / 2n); - await token.$_approve(this.holder, vault, ethers.MaxUint256 / 2n); - - Object.assign(this, { token, vault }); - }); - - it('deposit', async function () { - expect(await this.vault.previewDeposit(valueWithFees)).to.equal(valueWithoutFees); - this.tx = this.vault.connect(this.holder).deposit(valueWithFees, this.recipient); - }); - - it('mint', async function () { - expect(await this.vault.previewMint(valueWithoutFees)).to.equal(valueWithFees); - this.tx = this.vault.connect(this.holder).mint(valueWithoutFees, this.recipient); - }); - - afterEach(async function () { - await expect(this.tx).to.changeTokenBalances( - this.token, - [this.holder, this.vault, this.other], - [-valueWithFees, valueWithoutFees, fees], - ); - await expect(this.tx).to.changeTokenBalance(this.vault, this.recipient, valueWithoutFees); - await expect(this.tx) - // get total - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.vault, valueWithFees) - // redirect fees - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.other, fees) - // mint shares - .to.emit(this.vault, 'Transfer') - .withArgs(ethers.ZeroAddress, this.recipient, valueWithoutFees) - // deposit event - .to.emit(this.vault, 'Deposit') - .withArgs(this.holder, this.recipient, valueWithFees, valueWithoutFees); - }); - }); - - describe('output fees', function () { - beforeEach(async function () { - const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 18n]); - const vault = await ethers.deployContract('$ERC4626FeesMock', [ - '', - '', - token, - 0n, - ethers.ZeroAddress, - feeBasisPoints, - this.other, - ]); - - await token.$_mint(vault, ethers.MaxUint256 / 2n); - await vault.$_mint(this.holder, ethers.MaxUint256 / 2n); - - Object.assign(this, { token, vault }); - }); - - it('redeem', async function () { - expect(await this.vault.previewRedeem(valueWithFees)).to.equal(valueWithoutFees); - this.tx = this.vault.connect(this.holder).redeem(valueWithFees, this.recipient, this.holder); - }); - - it('withdraw', async function () { - expect(await this.vault.previewWithdraw(valueWithoutFees)).to.equal(valueWithFees); - this.tx = this.vault.connect(this.holder).withdraw(valueWithoutFees, this.recipient, this.holder); - }); - - afterEach(async function () { - await expect(this.tx).to.changeTokenBalances( - this.token, - [this.vault, this.recipient, this.other], - [-valueWithFees, valueWithoutFees, fees], - ); - await expect(this.tx).to.changeTokenBalance(this.vault, this.holder, -valueWithFees); - await expect(this.tx) - // withdraw principal - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.recipient, valueWithoutFees) - // redirect fees - .to.emit(this.token, 'Transfer') - .withArgs(this.vault, this.other, fees) - // mint shares - .to.emit(this.vault, 'Transfer') - .withArgs(this.holder, ethers.ZeroAddress, valueWithFees) - // withdraw event - .to.emit(this.vault, 'Withdraw') - .withArgs(this.holder, this.recipient, this.holder, valueWithoutFees, valueWithFees); - }); - }); - }); - - /// Scenario inspired by solmate ERC4626 tests: - /// https://github.com/transmissions11/solmate/blob/main/src/test/ERC4626.t.sol - it('multiple mint, deposit, redeem & withdrawal', async function () { - // test designed with both asset using similar decimals - const [alice, bruce] = this.accounts; - const token = await ethers.deployContract('$ERC20DecimalsMock', [name, symbol, 18n]); - const vault = await ethers.deployContract('$ERC4626', ['', '', token]); - - await token.$_mint(alice, 4000n); - await token.$_mint(bruce, 7001n); - await token.connect(alice).approve(vault, 4000n); - await token.connect(bruce).approve(vault, 7001n); - - // 1. Alice mints 2000 shares (costs 2000 tokens) - await expect(vault.connect(alice).mint(2000n, alice)) - .to.emit(token, 'Transfer') - .withArgs(alice, vault, 2000n) - .to.emit(vault, 'Transfer') - .withArgs(ethers.ZeroAddress, alice, 2000n); - - expect(await vault.previewDeposit(2000n)).to.equal(2000n); - expect(await vault.balanceOf(alice)).to.equal(2000n); - expect(await vault.balanceOf(bruce)).to.equal(0n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(2000n); - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(0n); - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(2000n); - expect(await vault.totalSupply()).to.equal(2000n); - expect(await vault.totalAssets()).to.equal(2000n); - - // 2. Bruce deposits 4000 tokens (mints 4000 shares) - await expect(vault.connect(bruce).mint(4000n, bruce)) - .to.emit(token, 'Transfer') - .withArgs(bruce, vault, 4000n) - .to.emit(vault, 'Transfer') - .withArgs(ethers.ZeroAddress, bruce, 4000n); - - expect(await vault.previewDeposit(4000n)).to.equal(4000n); - expect(await vault.balanceOf(alice)).to.equal(2000n); - expect(await vault.balanceOf(bruce)).to.equal(4000n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(2000n); - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(4000n); - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(6000n); - expect(await vault.totalSupply()).to.equal(6000n); - expect(await vault.totalAssets()).to.equal(6000n); - - // 3. Vault mutates by +3000 tokens (simulated yield returned from strategy) - await token.$_mint(vault, 3000n); - - expect(await vault.balanceOf(alice)).to.equal(2000n); - expect(await vault.balanceOf(bruce)).to.equal(4000n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(2999n); // used to be 3000, but virtual assets/shares captures part of the yield - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(5999n); // used to be 6000, but virtual assets/shares captures part of the yield - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(6000n); - expect(await vault.totalSupply()).to.equal(6000n); - expect(await vault.totalAssets()).to.equal(9000n); - - // 4. Alice deposits 2000 tokens (mints 1333 shares) - await expect(vault.connect(alice).deposit(2000n, alice)) - .to.emit(token, 'Transfer') - .withArgs(alice, vault, 2000n) - .to.emit(vault, 'Transfer') - .withArgs(ethers.ZeroAddress, alice, 1333n); - - expect(await vault.balanceOf(alice)).to.equal(3333n); - expect(await vault.balanceOf(bruce)).to.equal(4000n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(4999n); - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(6000n); - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(7333n); - expect(await vault.totalSupply()).to.equal(7333n); - expect(await vault.totalAssets()).to.equal(11000n); - - // 5. Bruce mints 2000 shares (costs 3001 assets) - // NOTE: Bruce's assets spent got rounded towards infinity - // NOTE: Alices's vault assets got rounded towards infinity - await expect(vault.connect(bruce).mint(2000n, bruce)) - .to.emit(token, 'Transfer') - .withArgs(bruce, vault, 3000n) - .to.emit(vault, 'Transfer') - .withArgs(ethers.ZeroAddress, bruce, 2000n); - - expect(await vault.balanceOf(alice)).to.equal(3333n); - expect(await vault.balanceOf(bruce)).to.equal(6000n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(4999n); // used to be 5000 - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(9000n); - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(9333n); - expect(await vault.totalSupply()).to.equal(9333n); - expect(await vault.totalAssets()).to.equal(14000n); // used to be 14001 - - // 6. Vault mutates by +3000 tokens - // NOTE: Vault holds 17001 tokens, but sum of assetsOf() is 17000. - await token.$_mint(vault, 3000n); - - expect(await vault.balanceOf(alice)).to.equal(3333n); - expect(await vault.balanceOf(bruce)).to.equal(6000n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(6070n); // used to be 6071 - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(10928n); // used to be 10929 - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(9333n); - expect(await vault.totalSupply()).to.equal(9333n); - expect(await vault.totalAssets()).to.equal(17000n); // used to be 17001 - - // 7. Alice redeem 1333 shares (2428 assets) - await expect(vault.connect(alice).redeem(1333n, alice, alice)) - .to.emit(vault, 'Transfer') - .withArgs(alice, ethers.ZeroAddress, 1333n) - .to.emit(token, 'Transfer') - .withArgs(vault, alice, 2427n); // used to be 2428 - - expect(await vault.balanceOf(alice)).to.equal(2000n); - expect(await vault.balanceOf(bruce)).to.equal(6000n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(3643n); - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(10929n); - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(8000n); - expect(await vault.totalSupply()).to.equal(8000n); - expect(await vault.totalAssets()).to.equal(14573n); - - // 8. Bruce withdraws 2929 assets (1608 shares) - await expect(vault.connect(bruce).withdraw(2929n, bruce, bruce)) - .to.emit(vault, 'Transfer') - .withArgs(bruce, ethers.ZeroAddress, 1608n) - .to.emit(token, 'Transfer') - .withArgs(vault, bruce, 2929n); - - expect(await vault.balanceOf(alice)).to.equal(2000n); - expect(await vault.balanceOf(bruce)).to.equal(4392n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(3643n); - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(8000n); - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(6392n); - expect(await vault.totalSupply()).to.equal(6392n); - expect(await vault.totalAssets()).to.equal(11644n); - - // 9. Alice withdraws 3643 assets (2000 shares) - // NOTE: Bruce's assets have been rounded back towards infinity - await expect(vault.connect(alice).withdraw(3643n, alice, alice)) - .to.emit(vault, 'Transfer') - .withArgs(alice, ethers.ZeroAddress, 2000n) - .to.emit(token, 'Transfer') - .withArgs(vault, alice, 3643n); - - expect(await vault.balanceOf(alice)).to.equal(0n); - expect(await vault.balanceOf(bruce)).to.equal(4392n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(0n); - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(8000n); // used to be 8001 - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(4392n); - expect(await vault.totalSupply()).to.equal(4392n); - expect(await vault.totalAssets()).to.equal(8001n); - - // 10. Bruce redeem 4392 shares (8001 tokens) - await expect(vault.connect(bruce).redeem(4392n, bruce, bruce)) - .to.emit(vault, 'Transfer') - .withArgs(bruce, ethers.ZeroAddress, 4392n) - .to.emit(token, 'Transfer') - .withArgs(vault, bruce, 8000n); // used to be 8001 - - expect(await vault.balanceOf(alice)).to.equal(0n); - expect(await vault.balanceOf(bruce)).to.equal(0n); - expect(await vault.convertToAssets(await vault.balanceOf(alice))).to.equal(0n); - expect(await vault.convertToAssets(await vault.balanceOf(bruce))).to.equal(0n); - expect(await vault.convertToShares(await token.balanceOf(vault))).to.equal(0n); - expect(await vault.totalSupply()).to.equal(0n); - expect(await vault.totalAssets()).to.equal(1n); // used to be 0 - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC20/utils/SafeERC20.test.js b/lib_openzeppelin_contracts/test/token/ERC20/utils/SafeERC20.test.js deleted file mode 100644 index 60bcc55..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC20/utils/SafeERC20.test.js +++ /dev/null @@ -1,427 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const name = 'ERC20Mock'; -const symbol = 'ERC20Mock'; -const value = 100n; -const data = '0x12345678'; - -async function fixture() { - const [hasNoCode, owner, receiver, spender, other] = await ethers.getSigners(); - - const mock = await ethers.deployContract('$SafeERC20'); - const erc20ReturnFalseMock = await ethers.deployContract('$ERC20ReturnFalseMock', [name, symbol]); - const erc20ReturnTrueMock = await ethers.deployContract('$ERC20', [name, symbol]); // default implementation returns true - const erc20NoReturnMock = await ethers.deployContract('$ERC20NoReturnMock', [name, symbol]); - const erc20ForceApproveMock = await ethers.deployContract('$ERC20ForceApproveMock', [name, symbol]); - const erc1363Mock = await ethers.deployContract('$ERC1363', [name, symbol]); - const erc1363ReturnFalseOnErc20Mock = await ethers.deployContract('$ERC1363ReturnFalseOnERC20Mock', [name, symbol]); - const erc1363ReturnFalseMock = await ethers.deployContract('$ERC1363ReturnFalseMock', [name, symbol]); - const erc1363NoReturnMock = await ethers.deployContract('$ERC1363NoReturnMock', [name, symbol]); - const erc1363ForceApproveMock = await ethers.deployContract('$ERC1363ForceApproveMock', [name, symbol]); - const erc1363Receiver = await ethers.deployContract('$ERC1363ReceiverMock'); - const erc1363Spender = await ethers.deployContract('$ERC1363SpenderMock'); - - return { - hasNoCode, - owner, - receiver, - spender, - other, - mock, - erc20ReturnFalseMock, - erc20ReturnTrueMock, - erc20NoReturnMock, - erc20ForceApproveMock, - erc1363Mock, - erc1363ReturnFalseOnErc20Mock, - erc1363ReturnFalseMock, - erc1363NoReturnMock, - erc1363ForceApproveMock, - erc1363Receiver, - erc1363Spender, - }; -} - -describe('SafeERC20', function () { - before(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('with address that has no contract code', function () { - beforeEach(async function () { - this.token = this.hasNoCode; - }); - - it('reverts on transfer', async function () { - await expect(this.mock.$safeTransfer(this.token, this.receiver, 0n)) - .to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode') - .withArgs(this.token); - }); - - it('reverts on transferFrom', async function () { - await expect(this.mock.$safeTransferFrom(this.token, this.mock, this.receiver, 0n)) - .to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode') - .withArgs(this.token); - }); - - it('reverts on increaseAllowance', async function () { - // Call to 'token.allowance' does not return any data, resulting in a decoding error (revert without reason) - await expect(this.mock.$safeIncreaseAllowance(this.token, this.spender, 0n)).to.be.revertedWithoutReason(); - }); - - it('reverts on decreaseAllowance', async function () { - // Call to 'token.allowance' does not return any data, resulting in a decoding error (revert without reason) - await expect(this.mock.$safeDecreaseAllowance(this.token, this.spender, 0n)).to.be.revertedWithoutReason(); - }); - - it('reverts on forceApprove', async function () { - await expect(this.mock.$forceApprove(this.token, this.spender, 0n)) - .to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode') - .withArgs(this.token); - }); - }); - - describe('with token that returns false on all calls', function () { - beforeEach(async function () { - this.token = this.erc20ReturnFalseMock; - }); - - it('reverts on transfer', async function () { - await expect(this.mock.$safeTransfer(this.token, this.receiver, 0n)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - - it('reverts on transferFrom', async function () { - await expect(this.mock.$safeTransferFrom(this.token, this.mock, this.receiver, 0n)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - - it('reverts on increaseAllowance', async function () { - await expect(this.mock.$safeIncreaseAllowance(this.token, this.spender, 0n)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - - it('reverts on decreaseAllowance', async function () { - await expect(this.mock.$safeDecreaseAllowance(this.token, this.spender, 0n)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - - it('reverts on forceApprove', async function () { - await expect(this.mock.$forceApprove(this.token, this.spender, 0n)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - }); - - describe('with token that returns true on all calls', function () { - beforeEach(async function () { - this.token = this.erc20ReturnTrueMock; - }); - - shouldOnlyRevertOnErrors(); - }); - - describe('with token that returns no boolean values', function () { - beforeEach(async function () { - this.token = this.erc20NoReturnMock; - }); - - shouldOnlyRevertOnErrors(); - }); - - describe('with usdt approval behaviour', function () { - beforeEach(async function () { - this.token = this.erc20ForceApproveMock; - }); - - describe('with initial approval', function () { - beforeEach(async function () { - await this.token.$_approve(this.mock, this.spender, 100n); - }); - - it('safeIncreaseAllowance works', async function () { - await this.mock.$safeIncreaseAllowance(this.token, this.spender, 10n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(110n); - }); - - it('safeDecreaseAllowance works', async function () { - await this.mock.$safeDecreaseAllowance(this.token, this.spender, 10n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(90n); - }); - - it('forceApprove works', async function () { - await this.mock.$forceApprove(this.token, this.spender, 200n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(200n); - }); - }); - }); - - describe('with standard ERC1363', function () { - beforeEach(async function () { - this.token = this.erc1363Mock; - }); - - shouldOnlyRevertOnErrors(); - - describe('transferAndCall', function () { - it('cannot transferAndCall to an EOA directly', async function () { - await this.token.$_mint(this.owner, 100n); - - await expect(this.token.connect(this.owner).transferAndCall(this.receiver, value, ethers.Typed.bytes(data))) - .to.be.revertedWithCustomError(this.token, 'ERC1363InvalidReceiver') - .withArgs(this.receiver); - }); - - it('can transferAndCall to an EOA using helper', async function () { - await this.token.$_mint(this.mock, value); - - await expect(this.mock.$transferAndCallRelaxed(this.token, this.receiver, value, data)) - .to.emit(this.token, 'Transfer') - .withArgs(this.mock, this.receiver, value); - }); - - it('can transferAndCall to an ERC1363Receiver using helper', async function () { - await this.token.$_mint(this.mock, value); - - await expect(this.mock.$transferAndCallRelaxed(this.token, this.erc1363Receiver, value, data)) - .to.emit(this.token, 'Transfer') - .withArgs(this.mock, this.erc1363Receiver, value) - .to.emit(this.erc1363Receiver, 'Received') - .withArgs(this.mock, this.mock, value, data); - }); - }); - - describe('transferFromAndCall', function () { - it('can transferFromAndCall to an EOA using helper', async function () { - await this.token.$_mint(this.owner, value); - await this.token.$_approve(this.owner, this.mock, ethers.MaxUint256); - - await expect(this.mock.$transferFromAndCallRelaxed(this.token, this.owner, this.receiver, value, data)) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, this.receiver, value); - }); - - it('can transferFromAndCall to an ERC1363Receiver using helper', async function () { - await this.token.$_mint(this.owner, value); - await this.token.$_approve(this.owner, this.mock, ethers.MaxUint256); - - await expect(this.mock.$transferFromAndCallRelaxed(this.token, this.owner, this.erc1363Receiver, value, data)) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, this.erc1363Receiver, value) - .to.emit(this.erc1363Receiver, 'Received') - .withArgs(this.mock, this.owner, value, data); - }); - }); - - describe('approveAndCall', function () { - it('can approveAndCall to an EOA using helper', async function () { - await expect(this.mock.$approveAndCallRelaxed(this.token, this.receiver, value, data)) - .to.emit(this.token, 'Approval') - .withArgs(this.mock, this.receiver, value); - }); - - it('can approveAndCall to an ERC1363Spender using helper', async function () { - await expect(this.mock.$approveAndCallRelaxed(this.token, this.erc1363Spender, value, data)) - .to.emit(this.token, 'Approval') - .withArgs(this.mock, this.erc1363Spender, value) - .to.emit(this.erc1363Spender, 'Approved') - .withArgs(this.mock, value, data); - }); - }); - }); - - describe('with ERC1363 that returns false on all ERC20 calls', function () { - beforeEach(async function () { - this.token = this.erc1363ReturnFalseOnErc20Mock; - }); - - it('reverts on transferAndCallRelaxed', async function () { - await expect(this.mock.$transferAndCallRelaxed(this.token, this.erc1363Receiver, 0n, data)) - .to.be.revertedWithCustomError(this.token, 'ERC1363TransferFailed') - .withArgs(this.erc1363Receiver, 0n); - }); - - it('reverts on transferFromAndCallRelaxed', async function () { - await expect(this.mock.$transferFromAndCallRelaxed(this.token, this.mock, this.erc1363Receiver, 0n, data)) - .to.be.revertedWithCustomError(this.token, 'ERC1363TransferFromFailed') - .withArgs(this.mock, this.erc1363Receiver, 0n); - }); - - it('reverts on approveAndCallRelaxed', async function () { - await expect(this.mock.$approveAndCallRelaxed(this.token, this.erc1363Spender, 0n, data)) - .to.be.revertedWithCustomError(this.token, 'ERC1363ApproveFailed') - .withArgs(this.erc1363Spender, 0n); - }); - }); - - describe('with ERC1363 that returns false on all ERC1363 calls', function () { - beforeEach(async function () { - this.token = this.erc1363ReturnFalseMock; - }); - - it('reverts on transferAndCallRelaxed', async function () { - await expect(this.mock.$transferAndCallRelaxed(this.token, this.erc1363Receiver, 0n, data)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - - it('reverts on transferFromAndCallRelaxed', async function () { - await expect(this.mock.$transferFromAndCallRelaxed(this.token, this.mock, this.erc1363Receiver, 0n, data)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - - it('reverts on approveAndCallRelaxed', async function () { - await expect(this.mock.$approveAndCallRelaxed(this.token, this.erc1363Spender, 0n, data)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedOperation') - .withArgs(this.token); - }); - }); - - describe('with ERC1363 that returns no boolean values', function () { - beforeEach(async function () { - this.token = this.erc1363NoReturnMock; - }); - - it('reverts on transferAndCallRelaxed', async function () { - await expect( - this.mock.$transferAndCallRelaxed(this.token, this.erc1363Receiver, 0n, data), - ).to.be.revertedWithoutReason(); - }); - - it('reverts on transferFromAndCallRelaxed', async function () { - await expect( - this.mock.$transferFromAndCallRelaxed(this.token, this.mock, this.erc1363Receiver, 0n, data), - ).to.be.revertedWithoutReason(); - }); - - it('reverts on approveAndCallRelaxed', async function () { - await expect( - this.mock.$approveAndCallRelaxed(this.token, this.erc1363Spender, 0n, data), - ).to.be.revertedWithoutReason(); - }); - }); - - describe('with ERC1363 with usdt approval behaviour', function () { - beforeEach(async function () { - this.token = this.erc1363ForceApproveMock; - }); - - describe('without initial approval', function () { - it('approveAndCallRelaxed works when recipient is an EOA', async function () { - await this.mock.$approveAndCallRelaxed(this.token, this.spender, 10n, data); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(10n); - }); - - it('approveAndCallRelaxed works when recipient is a contract', async function () { - await this.mock.$approveAndCallRelaxed(this.token, this.erc1363Spender, 10n, data); - expect(await this.token.allowance(this.mock, this.erc1363Spender)).to.equal(10n); - }); - }); - - describe('with initial approval', function () { - it('approveAndCallRelaxed works when recipient is an EOA', async function () { - await this.token.$_approve(this.mock, this.spender, 100n); - - await this.mock.$approveAndCallRelaxed(this.token, this.spender, 10n, data); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(10n); - }); - - it('approveAndCallRelaxed reverts when recipient is a contract', async function () { - await this.token.$_approve(this.mock, this.erc1363Spender, 100n); - await expect(this.mock.$approveAndCallRelaxed(this.token, this.erc1363Spender, 10n, data)).to.be.revertedWith( - 'USDT approval failure', - ); - }); - }); - }); -}); - -function shouldOnlyRevertOnErrors() { - describe('transfers', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, 100n); - await this.token.$_mint(this.mock, 100n); - await this.token.$_approve(this.owner, this.mock, ethers.MaxUint256); - }); - - it("doesn't revert on transfer", async function () { - await expect(this.mock.$safeTransfer(this.token, this.receiver, 10n)) - .to.emit(this.token, 'Transfer') - .withArgs(this.mock, this.receiver, 10n); - }); - - it("doesn't revert on transferFrom", async function () { - await expect(this.mock.$safeTransferFrom(this.token, this.owner, this.receiver, 10n)) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, this.receiver, 10n); - }); - }); - - describe('approvals', function () { - describe('with zero allowance', function () { - beforeEach(async function () { - await this.token.$_approve(this.mock, this.spender, 0n); - }); - - it("doesn't revert when force approving a non-zero allowance", async function () { - await this.mock.$forceApprove(this.token, this.spender, 100n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(100n); - }); - - it("doesn't revert when force approving a zero allowance", async function () { - await this.mock.$forceApprove(this.token, this.spender, 0n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(0n); - }); - - it("doesn't revert when increasing the allowance", async function () { - await this.mock.$safeIncreaseAllowance(this.token, this.spender, 10n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(10n); - }); - - it('reverts when decreasing the allowance', async function () { - await expect(this.mock.$safeDecreaseAllowance(this.token, this.spender, 10n)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedDecreaseAllowance') - .withArgs(this.spender, 0n, 10n); - }); - }); - - describe('with non-zero allowance', function () { - beforeEach(async function () { - await this.token.$_approve(this.mock, this.spender, 100n); - }); - - it("doesn't revert when force approving a non-zero allowance", async function () { - await this.mock.$forceApprove(this.token, this.spender, 20n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(20n); - }); - - it("doesn't revert when force approving a zero allowance", async function () { - await this.mock.$forceApprove(this.token, this.spender, 0n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(0n); - }); - - it("doesn't revert when increasing the allowance", async function () { - await this.mock.$safeIncreaseAllowance(this.token, this.spender, 10n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(110n); - }); - - it("doesn't revert when decreasing the allowance to a positive value", async function () { - await this.mock.$safeDecreaseAllowance(this.token, this.spender, 50n); - expect(await this.token.allowance(this.mock, this.spender)).to.equal(50n); - }); - - it('reverts when decreasing the allowance to a negative value', async function () { - await expect(this.mock.$safeDecreaseAllowance(this.token, this.spender, 200n)) - .to.be.revertedWithCustomError(this.mock, 'SafeERC20FailedDecreaseAllowance') - .withArgs(this.spender, 100n, 200n); - }); - }); - }); -} diff --git a/lib_openzeppelin_contracts/test/token/ERC721/ERC721.behavior.js b/lib_openzeppelin_contracts/test/token/ERC721/ERC721.behavior.js deleted file mode 100644 index b9dd80d..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/ERC721.behavior.js +++ /dev/null @@ -1,972 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); -const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); - -const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); -const { RevertType } = require('../../helpers/enums'); - -const firstTokenId = 5042n; -const secondTokenId = 79217n; -const nonExistentTokenId = 13n; -const fourthTokenId = 4n; -const baseURI = 'https://api.example.com/v1/'; - -const RECEIVER_MAGIC_VALUE = '0x150b7a02'; - -function shouldBehaveLikeERC721() { - beforeEach(async function () { - const [owner, newOwner, approved, operator, other] = this.accounts; - Object.assign(this, { owner, newOwner, approved, operator, other }); - }); - - shouldSupportInterfaces(['ERC721']); - - describe('with minted tokens', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, firstTokenId); - await this.token.$_mint(this.owner, secondTokenId); - this.to = this.other; - }); - - describe('balanceOf', function () { - describe('when the given address owns some tokens', function () { - it('returns the amount of tokens owned by the given address', async function () { - expect(await this.token.balanceOf(this.owner)).to.equal(2n); - }); - }); - - describe('when the given address does not own any tokens', function () { - it('returns 0', async function () { - expect(await this.token.balanceOf(this.other)).to.equal(0n); - }); - }); - - describe('when querying the zero address', function () { - it('throws', async function () { - await expect(this.token.balanceOf(ethers.ZeroAddress)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidOwner') - .withArgs(ethers.ZeroAddress); - }); - }); - }); - - describe('ownerOf', function () { - describe('when the given token ID was tracked by this token', function () { - const tokenId = firstTokenId; - - it('returns the owner of the given token ID', async function () { - expect(await this.token.ownerOf(tokenId)).to.equal(this.owner); - }); - }); - - describe('when the given token ID was not tracked by this token', function () { - const tokenId = nonExistentTokenId; - - it('reverts', async function () { - await expect(this.token.ownerOf(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - }); - }); - }); - - describe('transfers', function () { - const tokenId = firstTokenId; - const data = '0x42'; - - beforeEach(async function () { - await this.token.connect(this.owner).approve(this.approved, tokenId); - await this.token.connect(this.owner).setApprovalForAll(this.operator, true); - }); - - const transferWasSuccessful = () => { - it('transfers the ownership of the given token ID to the given address', async function () { - await this.tx(); - expect(await this.token.ownerOf(tokenId)).to.equal(this.to); - }); - - it('emits a Transfer event', async function () { - await expect(this.tx()).to.emit(this.token, 'Transfer').withArgs(this.owner, this.to, tokenId); - }); - - it('clears the approval for the token ID with no event', async function () { - await expect(this.tx()).to.not.emit(this.token, 'Approval'); - - expect(await this.token.getApproved(tokenId)).to.equal(ethers.ZeroAddress); - }); - - it('adjusts owners balances', async function () { - const balanceBefore = await this.token.balanceOf(this.owner); - await this.tx(); - expect(await this.token.balanceOf(this.owner)).to.equal(balanceBefore - 1n); - }); - - it('adjusts owners tokens by index', async function () { - if (!this.token.tokenOfOwnerByIndex) return; - - await this.tx(); - expect(await this.token.tokenOfOwnerByIndex(this.to, 0n)).to.equal(tokenId); - expect(await this.token.tokenOfOwnerByIndex(this.owner, 0n)).to.not.equal(tokenId); - }); - }; - - const shouldTransferTokensByUsers = function (fragment, opts = {}) { - describe('when called by the owner', function () { - beforeEach(async function () { - this.tx = () => - this.token.connect(this.owner)[fragment](this.owner, this.to, tokenId, ...(opts.extra ?? [])); - }); - transferWasSuccessful(); - }); - - describe('when called by the approved individual', function () { - beforeEach(async function () { - this.tx = () => - this.token.connect(this.approved)[fragment](this.owner, this.to, tokenId, ...(opts.extra ?? [])); - }); - transferWasSuccessful(); - }); - - describe('when called by the operator', function () { - beforeEach(async function () { - this.tx = () => - this.token.connect(this.operator)[fragment](this.owner, this.to, tokenId, ...(opts.extra ?? [])); - }); - transferWasSuccessful(); - }); - - describe('when called by the owner without an approved user', function () { - beforeEach(async function () { - await this.token.connect(this.owner).approve(ethers.ZeroAddress, tokenId); - this.tx = () => - this.token.connect(this.operator)[fragment](this.owner, this.to, tokenId, ...(opts.extra ?? [])); - }); - transferWasSuccessful(); - }); - - describe('when sent to the owner', function () { - beforeEach(async function () { - this.tx = () => - this.token.connect(this.owner)[fragment](this.owner, this.owner, tokenId, ...(opts.extra ?? [])); - }); - - it('keeps ownership of the token', async function () { - await this.tx(); - expect(await this.token.ownerOf(tokenId)).to.equal(this.owner); - }); - - it('clears the approval for the token ID', async function () { - await this.tx(); - expect(await this.token.getApproved(tokenId)).to.equal(ethers.ZeroAddress); - }); - - it('emits only a transfer event', async function () { - await expect(this.tx()).to.emit(this.token, 'Transfer').withArgs(this.owner, this.owner, tokenId); - }); - - it('keeps the owner balance', async function () { - const balanceBefore = await this.token.balanceOf(this.owner); - await this.tx(); - expect(await this.token.balanceOf(this.owner)).to.equal(balanceBefore); - }); - - it('keeps same tokens by index', async function () { - if (!this.token.tokenOfOwnerByIndex) return; - - expect(await Promise.all([0n, 1n].map(i => this.token.tokenOfOwnerByIndex(this.owner, i)))).to.have.members( - [firstTokenId, secondTokenId], - ); - }); - }); - - describe('when the address of the previous owner is incorrect', function () { - it('reverts', async function () { - await expect( - this.token.connect(this.owner)[fragment](this.other, this.other, tokenId, ...(opts.extra ?? [])), - ) - .to.be.revertedWithCustomError(this.token, 'ERC721IncorrectOwner') - .withArgs(this.other, tokenId, this.owner); - }); - }); - - describe('when the sender is not authorized for the token id', function () { - if (opts.unrestricted) { - it('does not revert', async function () { - await this.token.connect(this.other)[fragment](this.owner, this.other, tokenId, ...(opts.extra ?? [])); - }); - } else { - it('reverts', async function () { - await expect( - this.token.connect(this.other)[fragment](this.owner, this.other, tokenId, ...(opts.extra ?? [])), - ) - .to.be.revertedWithCustomError(this.token, 'ERC721InsufficientApproval') - .withArgs(this.other, tokenId); - }); - } - }); - - describe('when the given token ID does not exist', function () { - it('reverts', async function () { - await expect( - this.token - .connect(this.owner) - [fragment](this.owner, this.other, nonExistentTokenId, ...(opts.extra ?? [])), - ) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(nonExistentTokenId); - }); - }); - - describe('when the address to transfer the token to is the zero address', function () { - it('reverts', async function () { - await expect( - this.token.connect(this.owner)[fragment](this.owner, ethers.ZeroAddress, tokenId, ...(opts.extra ?? [])), - ) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - }); - }; - - const shouldTransferSafely = function (fragment, data, opts = {}) { - // sanity - it('function exists', async function () { - expect(this.token.interface.hasFunction(fragment)).to.be.true; - }); - - describe('to a user account', function () { - shouldTransferTokensByUsers(fragment, opts); - }); - - describe('to a valid receiver contract', function () { - beforeEach(async function () { - this.to = await ethers.deployContract('ERC721ReceiverMock', [RECEIVER_MAGIC_VALUE, RevertType.None]); - }); - - shouldTransferTokensByUsers(fragment, opts); - - it('calls onERC721Received', async function () { - await expect(this.token.connect(this.owner)[fragment](this.owner, this.to, tokenId, ...(opts.extra ?? []))) - .to.emit(this.to, 'Received') - .withArgs(this.owner, this.owner, tokenId, data, anyValue); - }); - - it('calls onERC721Received from approved', async function () { - await expect( - this.token.connect(this.approved)[fragment](this.owner, this.to, tokenId, ...(opts.extra ?? [])), - ) - .to.emit(this.to, 'Received') - .withArgs(this.approved, this.owner, tokenId, data, anyValue); - }); - - describe('with an invalid token id', function () { - it('reverts', async function () { - await expect( - this.token - .connect(this.approved) - [fragment](this.owner, this.to, nonExistentTokenId, ...(opts.extra ?? [])), - ) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(nonExistentTokenId); - }); - }); - }); - }; - - for (const { fnName, opts } of [ - { fnName: 'transferFrom', opts: {} }, - { fnName: '$_transfer', opts: { unrestricted: true } }, - ]) { - describe(`via ${fnName}`, function () { - shouldTransferTokensByUsers(fnName, opts); - }); - } - - for (const { fnName, opts } of [ - { fnName: 'safeTransferFrom', opts: {} }, - { fnName: '$_safeTransfer', opts: { unrestricted: true } }, - ]) { - describe(`via ${fnName}`, function () { - describe('with data', function () { - shouldTransferSafely(fnName, data, { ...opts, extra: [ethers.Typed.bytes(data)] }); - }); - - describe('without data', function () { - shouldTransferSafely(fnName, '0x', opts); - }); - - describe('to a receiver contract returning unexpected value', function () { - it('reverts', async function () { - const invalidReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - '0xdeadbeef', - RevertType.None, - ]); - - await expect(this.token.connect(this.owner)[fnName](this.owner, invalidReceiver, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(invalidReceiver); - }); - }); - - describe('to a receiver contract that reverts with message', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.RevertWithMessage, - ]); - - await expect( - this.token.connect(this.owner)[fnName](this.owner, revertingReceiver, tokenId), - ).to.be.revertedWith('ERC721ReceiverMock: reverting'); - }); - }); - - describe('to a receiver contract that reverts without message', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.RevertWithoutMessage, - ]); - - await expect(this.token.connect(this.owner)[fnName](this.owner, revertingReceiver, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(revertingReceiver); - }); - }); - - describe('to a receiver contract that reverts with custom error', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.RevertWithCustomError, - ]); - - await expect(this.token.connect(this.owner)[fnName](this.owner, revertingReceiver, tokenId)) - .to.be.revertedWithCustomError(revertingReceiver, 'CustomError') - .withArgs(RECEIVER_MAGIC_VALUE); - }); - }); - - describe('to a receiver contract that panics', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.Panic, - ]); - - await expect( - this.token.connect(this.owner)[fnName](this.owner, revertingReceiver, tokenId), - ).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO); - }); - }); - - describe('to a contract that does not implement the required function', function () { - it('reverts', async function () { - const nonReceiver = await ethers.deployContract('CallReceiverMock'); - - await expect(this.token.connect(this.owner)[fnName](this.owner, nonReceiver, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(nonReceiver); - }); - }); - }); - } - }); - - describe('safe mint', function () { - const tokenId = fourthTokenId; - const data = '0x42'; - - describe('via safeMint', function () { - // regular minting is tested in ERC721Mintable.test.js and others - it('calls onERC721Received — with data', async function () { - const receiver = await ethers.deployContract('ERC721ReceiverMock', [RECEIVER_MAGIC_VALUE, RevertType.None]); - - await expect(await this.token.$_safeMint(receiver, tokenId, ethers.Typed.bytes(data))) - .to.emit(receiver, 'Received') - .withArgs(anyValue, ethers.ZeroAddress, tokenId, data, anyValue); - }); - - it('calls onERC721Received — without data', async function () { - const receiver = await ethers.deployContract('ERC721ReceiverMock', [RECEIVER_MAGIC_VALUE, RevertType.None]); - - await expect(await this.token.$_safeMint(receiver, tokenId)) - .to.emit(receiver, 'Received') - .withArgs(anyValue, ethers.ZeroAddress, tokenId, '0x', anyValue); - }); - - describe('to a receiver contract returning unexpected value', function () { - it('reverts', async function () { - const invalidReceiver = await ethers.deployContract('ERC721ReceiverMock', ['0xdeadbeef', RevertType.None]); - - await expect(this.token.$_safeMint(invalidReceiver, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(invalidReceiver); - }); - }); - - describe('to a receiver contract that reverts with message', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.RevertWithMessage, - ]); - - await expect(this.token.$_safeMint(revertingReceiver, tokenId)).to.be.revertedWith( - 'ERC721ReceiverMock: reverting', - ); - }); - }); - - describe('to a receiver contract that reverts without message', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.RevertWithoutMessage, - ]); - - await expect(this.token.$_safeMint(revertingReceiver, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(revertingReceiver); - }); - }); - - describe('to a receiver contract that reverts with custom error', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.RevertWithCustomError, - ]); - - await expect(this.token.$_safeMint(revertingReceiver, tokenId)) - .to.be.revertedWithCustomError(revertingReceiver, 'CustomError') - .withArgs(RECEIVER_MAGIC_VALUE); - }); - }); - - describe('to a receiver contract that panics', function () { - it('reverts', async function () { - const revertingReceiver = await ethers.deployContract('ERC721ReceiverMock', [ - RECEIVER_MAGIC_VALUE, - RevertType.Panic, - ]); - - await expect(this.token.$_safeMint(revertingReceiver, tokenId)).to.be.revertedWithPanic( - PANIC_CODES.DIVISION_BY_ZERO, - ); - }); - }); - - describe('to a contract that does not implement the required function', function () { - it('reverts', async function () { - const nonReceiver = await ethers.deployContract('CallReceiverMock'); - - await expect(this.token.$_safeMint(nonReceiver, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(nonReceiver); - }); - }); - }); - }); - - describe('approve', function () { - const tokenId = firstTokenId; - - const itClearsApproval = function () { - it('clears approval for the token', async function () { - expect(await this.token.getApproved(tokenId)).to.equal(ethers.ZeroAddress); - }); - }; - - const itApproves = function () { - it('sets the approval for the target address', async function () { - expect(await this.token.getApproved(tokenId)).to.equal(this.approved ?? this.approved); - }); - }; - - const itEmitsApprovalEvent = function () { - it('emits an approval event', async function () { - await expect(this.tx) - .to.emit(this.token, 'Approval') - .withArgs(this.owner, this.approved ?? this.approved, tokenId); - }); - }; - - describe('when clearing approval', function () { - describe('when there was no prior approval', function () { - beforeEach(async function () { - this.approved = ethers.ZeroAddress; - this.tx = await this.token.connect(this.owner).approve(this.approved, tokenId); - }); - - itClearsApproval(); - itEmitsApprovalEvent(); - }); - - describe('when there was a prior approval', function () { - beforeEach(async function () { - await this.token.connect(this.owner).approve(this.other, tokenId); - this.approved = ethers.ZeroAddress; - this.tx = await this.token.connect(this.owner).approve(this.approved, tokenId); - }); - - itClearsApproval(); - itEmitsApprovalEvent(); - }); - }); - - describe('when approving a non-zero address', function () { - describe('when there was no prior approval', function () { - beforeEach(async function () { - this.tx = await this.token.connect(this.owner).approve(this.approved, tokenId); - }); - - itApproves(); - itEmitsApprovalEvent(); - }); - - describe('when there was a prior approval to the same address', function () { - beforeEach(async function () { - await this.token.connect(this.owner).approve(this.approved, tokenId); - this.tx = await this.token.connect(this.owner).approve(this.approved, tokenId); - }); - - itApproves(); - itEmitsApprovalEvent(); - }); - - describe('when there was a prior approval to a different address', function () { - beforeEach(async function () { - await this.token.connect(this.owner).approve(this.other, tokenId); - this.tx = await this.token.connect(this.owner).approve(this.approved, tokenId); - }); - - itApproves(); - itEmitsApprovalEvent(); - }); - }); - - describe('when the sender does not own the given token ID', function () { - it('reverts', async function () { - await expect(this.token.connect(this.other).approve(this.approved, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidApprover') - .withArgs(this.other); - }); - }); - - describe('when the sender is approved for the given token ID', function () { - it('reverts', async function () { - await this.token.connect(this.owner).approve(this.approved, tokenId); - - await expect(this.token.connect(this.approved).approve(this.other, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidApprover') - .withArgs(this.approved); - }); - }); - - describe('when the sender is an operator', function () { - beforeEach(async function () { - await this.token.connect(this.owner).setApprovalForAll(this.operator, true); - - this.tx = await this.token.connect(this.operator).approve(this.approved, tokenId); - }); - - itApproves(); - itEmitsApprovalEvent(); - }); - - describe('when the given token ID does not exist', function () { - it('reverts', async function () { - await expect(this.token.connect(this.operator).approve(this.approved, nonExistentTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(nonExistentTokenId); - }); - }); - }); - - describe('setApprovalForAll', function () { - describe('when the operator willing to approve is not the owner', function () { - describe('when there is no operator approval set by the sender', function () { - it('approves the operator', async function () { - await this.token.connect(this.owner).setApprovalForAll(this.operator, true); - - expect(await this.token.isApprovedForAll(this.owner, this.operator)).to.be.true; - }); - - it('emits an approval event', async function () { - await expect(this.token.connect(this.owner).setApprovalForAll(this.operator, true)) - .to.emit(this.token, 'ApprovalForAll') - .withArgs(this.owner, this.operator, true); - }); - }); - - describe('when the operator was set as not approved', function () { - beforeEach(async function () { - await this.token.connect(this.owner).setApprovalForAll(this.operator, false); - }); - - it('approves the operator', async function () { - await this.token.connect(this.owner).setApprovalForAll(this.operator, true); - - expect(await this.token.isApprovedForAll(this.owner, this.operator)).to.be.true; - }); - - it('emits an approval event', async function () { - await expect(this.token.connect(this.owner).setApprovalForAll(this.operator, true)) - .to.emit(this.token, 'ApprovalForAll') - .withArgs(this.owner, this.operator, true); - }); - - it('can unset the operator approval', async function () { - await this.token.connect(this.owner).setApprovalForAll(this.operator, false); - - expect(await this.token.isApprovedForAll(this.owner, this.operator)).to.be.false; - }); - }); - - describe('when the operator was already approved', function () { - beforeEach(async function () { - await this.token.connect(this.owner).setApprovalForAll(this.operator, true); - }); - - it('keeps the approval to the given address', async function () { - await this.token.connect(this.owner).setApprovalForAll(this.operator, true); - - expect(await this.token.isApprovedForAll(this.owner, this.operator)).to.be.true; - }); - - it('emits an approval event', async function () { - await expect(this.token.connect(this.owner).setApprovalForAll(this.operator, true)) - .to.emit(this.token, 'ApprovalForAll') - .withArgs(this.owner, this.operator, true); - }); - }); - }); - - describe('when the operator is address zero', function () { - it('reverts', async function () { - await expect(this.token.connect(this.owner).setApprovalForAll(ethers.ZeroAddress, true)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidOperator') - .withArgs(ethers.ZeroAddress); - }); - }); - }); - - describe('getApproved', function () { - describe('when token is not minted', function () { - it('reverts', async function () { - await expect(this.token.getApproved(nonExistentTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(nonExistentTokenId); - }); - }); - - describe('when token has been minted ', function () { - it('should return the zero address', async function () { - expect(await this.token.getApproved(firstTokenId)).to.equal(ethers.ZeroAddress); - }); - - describe('when account has been approved', function () { - beforeEach(async function () { - await this.token.connect(this.owner).approve(this.approved, firstTokenId); - }); - - it('returns approved account', async function () { - expect(await this.token.getApproved(firstTokenId)).to.equal(this.approved); - }); - }); - }); - }); - }); - - describe('_mint(address, uint256)', function () { - it('reverts with a null destination address', async function () { - await expect(this.token.$_mint(ethers.ZeroAddress, firstTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - - describe('with minted token', function () { - beforeEach(async function () { - this.tx = await this.token.$_mint(this.owner, firstTokenId); - }); - - it('emits a Transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(ethers.ZeroAddress, this.owner, firstTokenId); - }); - - it('creates the token', async function () { - expect(await this.token.balanceOf(this.owner)).to.equal(1n); - expect(await this.token.ownerOf(firstTokenId)).to.equal(this.owner); - }); - - it('reverts when adding a token id that already exists', async function () { - await expect(this.token.$_mint(this.owner, firstTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidSender') - .withArgs(ethers.ZeroAddress); - }); - }); - }); - - describe('_burn', function () { - it('reverts when burning a non-existent token id', async function () { - await expect(this.token.$_burn(nonExistentTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(nonExistentTokenId); - }); - - describe('with minted tokens', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, firstTokenId); - await this.token.$_mint(this.owner, secondTokenId); - }); - - describe('with burnt token', function () { - beforeEach(async function () { - this.tx = await this.token.$_burn(firstTokenId); - }); - - it('emits a Transfer event', async function () { - await expect(this.tx).to.emit(this.token, 'Transfer').withArgs(this.owner, ethers.ZeroAddress, firstTokenId); - }); - - it('deletes the token', async function () { - expect(await this.token.balanceOf(this.owner)).to.equal(1n); - await expect(this.token.ownerOf(firstTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(firstTokenId); - }); - - it('reverts when burning a token id that has been deleted', async function () { - await expect(this.token.$_burn(firstTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(firstTokenId); - }); - }); - }); - }); -} - -function shouldBehaveLikeERC721Enumerable() { - beforeEach(async function () { - const [owner, newOwner, approved, operator, other] = this.accounts; - Object.assign(this, { owner, newOwner, approved, operator, other }); - }); - - shouldSupportInterfaces(['ERC721Enumerable']); - - describe('with minted tokens', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, firstTokenId); - await this.token.$_mint(this.owner, secondTokenId); - this.to = this.other; - }); - - describe('totalSupply', function () { - it('returns total token supply', async function () { - expect(await this.token.totalSupply()).to.equal(2n); - }); - }); - - describe('tokenOfOwnerByIndex', function () { - describe('when the given index is lower than the amount of tokens owned by the given address', function () { - it('returns the token ID placed at the given index', async function () { - expect(await this.token.tokenOfOwnerByIndex(this.owner, 0n)).to.equal(firstTokenId); - }); - }); - - describe('when the index is greater than or equal to the total tokens owned by the given address', function () { - it('reverts', async function () { - await expect(this.token.tokenOfOwnerByIndex(this.owner, 2n)) - .to.be.revertedWithCustomError(this.token, 'ERC721OutOfBoundsIndex') - .withArgs(this.owner, 2n); - }); - }); - - describe('when the given address does not own any token', function () { - it('reverts', async function () { - await expect(this.token.tokenOfOwnerByIndex(this.other, 0n)) - .to.be.revertedWithCustomError(this.token, 'ERC721OutOfBoundsIndex') - .withArgs(this.other, 0n); - }); - }); - - describe('after transferring all tokens to another user', function () { - beforeEach(async function () { - await this.token.connect(this.owner).transferFrom(this.owner, this.other, firstTokenId); - await this.token.connect(this.owner).transferFrom(this.owner, this.other, secondTokenId); - }); - - it('returns correct token IDs for target', async function () { - expect(await this.token.balanceOf(this.other)).to.equal(2n); - - expect(await Promise.all([0n, 1n].map(i => this.token.tokenOfOwnerByIndex(this.other, i)))).to.have.members([ - firstTokenId, - secondTokenId, - ]); - }); - - it('returns empty collection for original owner', async function () { - expect(await this.token.balanceOf(this.owner)).to.equal(0n); - await expect(this.token.tokenOfOwnerByIndex(this.owner, 0n)) - .to.be.revertedWithCustomError(this.token, 'ERC721OutOfBoundsIndex') - .withArgs(this.owner, 0n); - }); - }); - }); - - describe('tokenByIndex', function () { - it('returns all tokens', async function () { - expect(await Promise.all([0n, 1n].map(i => this.token.tokenByIndex(i)))).to.have.members([ - firstTokenId, - secondTokenId, - ]); - }); - - it('reverts if index is greater than supply', async function () { - await expect(this.token.tokenByIndex(2n)) - .to.be.revertedWithCustomError(this.token, 'ERC721OutOfBoundsIndex') - .withArgs(ethers.ZeroAddress, 2n); - }); - - for (const tokenId of [firstTokenId, secondTokenId]) { - it(`returns all tokens after burning token ${tokenId} and minting new tokens`, async function () { - const newTokenId = 300n; - const anotherNewTokenId = 400n; - - await this.token.$_burn(tokenId); - await this.token.$_mint(this.newOwner, newTokenId); - await this.token.$_mint(this.newOwner, anotherNewTokenId); - - expect(await this.token.totalSupply()).to.equal(3n); - - expect(await Promise.all([0n, 1n, 2n].map(i => this.token.tokenByIndex(i)))) - .to.have.members([firstTokenId, secondTokenId, newTokenId, anotherNewTokenId].filter(x => x !== tokenId)) - .to.not.include(tokenId); - }); - } - }); - }); - - describe('_mint(address, uint256)', function () { - it('reverts with a null destination address', async function () { - await expect(this.token.$_mint(ethers.ZeroAddress, firstTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - - describe('with minted token', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, firstTokenId); - }); - - it('adjusts owner tokens by index', async function () { - expect(await this.token.tokenOfOwnerByIndex(this.owner, 0n)).to.equal(firstTokenId); - }); - - it('adjusts all tokens list', async function () { - expect(await this.token.tokenByIndex(0n)).to.equal(firstTokenId); - }); - }); - }); - - describe('_burn', function () { - it('reverts when burning a non-existent token id', async function () { - await expect(this.token.$_burn(firstTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(firstTokenId); - }); - - describe('with minted tokens', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, firstTokenId); - await this.token.$_mint(this.owner, secondTokenId); - }); - - describe('with burnt token', function () { - beforeEach(async function () { - await this.token.$_burn(firstTokenId); - }); - - it('removes that token from the token list of the owner', async function () { - expect(await this.token.tokenOfOwnerByIndex(this.owner, 0n)).to.equal(secondTokenId); - }); - - it('adjusts all tokens list', async function () { - expect(await this.token.tokenByIndex(0n)).to.equal(secondTokenId); - }); - - it('burns all tokens', async function () { - await this.token.$_burn(secondTokenId); - expect(await this.token.totalSupply()).to.equal(0n); - - await expect(this.token.tokenByIndex(0n)) - .to.be.revertedWithCustomError(this.token, 'ERC721OutOfBoundsIndex') - .withArgs(ethers.ZeroAddress, 0n); - }); - }); - }); - }); -} - -function shouldBehaveLikeERC721Metadata(name, symbol) { - shouldSupportInterfaces(['ERC721Metadata']); - - describe('metadata', function () { - it('has a name', async function () { - expect(await this.token.name()).to.equal(name); - }); - - it('has a symbol', async function () { - expect(await this.token.symbol()).to.equal(symbol); - }); - - describe('token URI', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, firstTokenId); - }); - - it('return empty string by default', async function () { - expect(await this.token.tokenURI(firstTokenId)).to.equal(''); - }); - - it('reverts when queried for non existent token id', async function () { - await expect(this.token.tokenURI(nonExistentTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(nonExistentTokenId); - }); - - describe('base URI', function () { - beforeEach(function () { - if (!this.token.interface.hasFunction('setBaseURI')) { - this.skip(); - } - }); - - it('base URI can be set', async function () { - await this.token.setBaseURI(baseURI); - expect(await this.token.baseURI()).to.equal(baseURI); - }); - - it('base URI is added as a prefix to the token URI', async function () { - await this.token.setBaseURI(baseURI); - expect(await this.token.tokenURI(firstTokenId)).to.equal(baseURI + firstTokenId.toString()); - }); - - it('token URI can be changed by changing the base URI', async function () { - await this.token.setBaseURI(baseURI); - const newBaseURI = 'https://api.example.com/v2/'; - await this.token.setBaseURI(newBaseURI); - expect(await this.token.tokenURI(firstTokenId)).to.equal(newBaseURI + firstTokenId.toString()); - }); - }); - }); - }); -} - -module.exports = { - shouldBehaveLikeERC721, - shouldBehaveLikeERC721Enumerable, - shouldBehaveLikeERC721Metadata, -}; diff --git a/lib_openzeppelin_contracts/test/token/ERC721/ERC721.test.js b/lib_openzeppelin_contracts/test/token/ERC721/ERC721.test.js deleted file mode 100644 index 1454cb0..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/ERC721.test.js +++ /dev/null @@ -1,23 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldBehaveLikeERC721, shouldBehaveLikeERC721Metadata } = require('./ERC721.behavior'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; - -async function fixture() { - return { - accounts: await ethers.getSigners(), - token: await ethers.deployContract('$ERC721', [name, symbol]), - }; -} - -describe('ERC721', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeERC721(); - shouldBehaveLikeERC721Metadata(name, symbol); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/ERC721Enumerable.test.js b/lib_openzeppelin_contracts/test/token/ERC721/ERC721Enumerable.test.js deleted file mode 100644 index a3bdea7..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/ERC721Enumerable.test.js +++ /dev/null @@ -1,28 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { - shouldBehaveLikeERC721, - shouldBehaveLikeERC721Metadata, - shouldBehaveLikeERC721Enumerable, -} = require('./ERC721.behavior'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; - -async function fixture() { - return { - accounts: await ethers.getSigners(), - token: await ethers.deployContract('$ERC721Enumerable', [name, symbol]), - }; -} - -describe('ERC721', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeERC721(); - shouldBehaveLikeERC721Metadata(name, symbol); - shouldBehaveLikeERC721Enumerable(); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Burnable.test.js b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Burnable.test.js deleted file mode 100644 index d6f0b80..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Burnable.test.js +++ /dev/null @@ -1,77 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; -const tokenId = 1n; -const otherTokenId = 2n; -const unknownTokenId = 3n; - -async function fixture() { - const [owner, approved, another] = await ethers.getSigners(); - const token = await ethers.deployContract('$ERC721Burnable', [name, symbol]); - return { owner, approved, another, token }; -} - -describe('ERC721Burnable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('like a burnable ERC721', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, tokenId); - await this.token.$_mint(this.owner, otherTokenId); - }); - - describe('burn', function () { - describe('when successful', function () { - it('emits a burn event, burns the given token ID and adjusts the balance of the owner', async function () { - const balanceBefore = await this.token.balanceOf(this.owner); - - await expect(this.token.connect(this.owner).burn(tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, ethers.ZeroAddress, tokenId); - - await expect(this.token.ownerOf(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - - expect(await this.token.balanceOf(this.owner)).to.equal(balanceBefore - 1n); - }); - }); - - describe('when there is a previous approval burned', function () { - beforeEach(async function () { - await this.token.connect(this.owner).approve(this.approved, tokenId); - await this.token.connect(this.owner).burn(tokenId); - }); - - describe('getApproved', function () { - it('reverts', async function () { - await expect(this.token.getApproved(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - }); - }); - }); - - describe('when there is no previous approval burned', function () { - it('reverts', async function () { - await expect(this.token.connect(this.another).burn(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InsufficientApproval') - .withArgs(this.another, tokenId); - }); - }); - - describe('when the given token ID was not tracked by this contract', function () { - it('reverts', async function () { - await expect(this.token.connect(this.owner).burn(unknownTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(unknownTokenId); - }); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Consecutive.t.sol b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Consecutive.t.sol deleted file mode 100644 index 6b909ad..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Consecutive.t.sol +++ /dev/null @@ -1,181 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -// solhint-disable func-name-mixedcase - -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {ERC721Consecutive} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Consecutive.sol"; -import {Test, StdUtils} from "@forge-std/Test.sol"; - -function toSingleton(address account) pure returns (address[] memory) { - address[] memory accounts = new address[](1); - accounts[0] = account; - return accounts; -} - -contract ERC721ConsecutiveTarget is StdUtils, ERC721Consecutive { - uint96 private immutable _offset; - uint256 public totalMinted = 0; - - constructor(address[] memory receivers, uint256[] memory batches, uint256 startingId) ERC721("", "") { - _offset = uint96(startingId); - for (uint256 i = 0; i < batches.length; i++) { - address receiver = receivers[i % receivers.length]; - uint96 batchSize = uint96(bound(batches[i], 0, _maxBatchSize())); - _mintConsecutive(receiver, batchSize); - totalMinted += batchSize; - } - } - - function burn(uint256 tokenId) public { - _burn(tokenId); - } - - function _firstConsecutiveId() internal view virtual override returns (uint96) { - return _offset; - } -} - -contract ERC721ConsecutiveTest is Test { - function test_balance(address receiver, uint256[] calldata batches, uint96 startingId) public { - vm.assume(receiver != address(0)); - - uint256 startingTokenId = bound(startingId, 0, 5000); - - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); - - assertEq(token.balanceOf(receiver), token.totalMinted()); - } - - function test_ownership( - address receiver, - uint256[] calldata batches, - uint256[2] calldata unboundedTokenId, - uint96 startingId - ) public { - vm.assume(receiver != address(0)); - - uint256 startingTokenId = bound(startingId, 0, 5000); - - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); - - if (token.totalMinted() > 0) { - uint256 validTokenId = bound( - unboundedTokenId[0], - startingTokenId, - startingTokenId + token.totalMinted() - 1 - ); - assertEq(token.ownerOf(validTokenId), receiver); - } - - uint256 invalidTokenId = bound( - unboundedTokenId[1], - startingTokenId + token.totalMinted(), - startingTokenId + token.totalMinted() + 1 - ); - vm.expectRevert(); - token.ownerOf(invalidTokenId); - } - - function test_burn( - address receiver, - uint256[] calldata batches, - uint256 unboundedTokenId, - uint96 startingId - ) public { - vm.assume(receiver != address(0)); - - uint256 startingTokenId = bound(startingId, 0, 5000); - - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); - - // only test if we minted at least one token - uint256 supply = token.totalMinted(); - vm.assume(supply > 0); - - // burn a token in [0; supply[ - uint256 tokenId = bound(unboundedTokenId, startingTokenId, startingTokenId + supply - 1); - token.burn(tokenId); - - // balance should have decreased - assertEq(token.balanceOf(receiver), supply - 1); - - // token should be burnt - vm.expectRevert(); - token.ownerOf(tokenId); - } - - function test_transfer( - address[2] calldata accounts, - uint256[2] calldata unboundedBatches, - uint256[2] calldata unboundedTokenId, - uint96 startingId - ) public { - vm.assume(accounts[0] != address(0)); - vm.assume(accounts[1] != address(0)); - vm.assume(accounts[0] != accounts[1]); - - uint256 startingTokenId = bound(startingId, 1, 5000); - - address[] memory receivers = new address[](2); - receivers[0] = accounts[0]; - receivers[1] = accounts[1]; - - // We assume _maxBatchSize is 5000 (the default). This test will break otherwise. - uint256[] memory batches = new uint256[](2); - batches[0] = bound(unboundedBatches[0], startingTokenId, 5000); - batches[1] = bound(unboundedBatches[1], startingTokenId, 5000); - - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(receivers, batches, startingTokenId); - - uint256 tokenId0 = bound(unboundedTokenId[0], startingTokenId, batches[0]); - uint256 tokenId1 = bound(unboundedTokenId[1], startingTokenId, batches[1]) + batches[0]; - - assertEq(token.ownerOf(tokenId0), accounts[0]); - assertEq(token.ownerOf(tokenId1), accounts[1]); - assertEq(token.balanceOf(accounts[0]), batches[0]); - assertEq(token.balanceOf(accounts[1]), batches[1]); - - vm.prank(accounts[0]); - token.transferFrom(accounts[0], accounts[1], tokenId0); - - assertEq(token.ownerOf(tokenId0), accounts[1]); - assertEq(token.ownerOf(tokenId1), accounts[1]); - assertEq(token.balanceOf(accounts[0]), batches[0] - 1); - assertEq(token.balanceOf(accounts[1]), batches[1] + 1); - - vm.prank(accounts[1]); - token.transferFrom(accounts[1], accounts[0], tokenId1); - - assertEq(token.ownerOf(tokenId0), accounts[1]); - assertEq(token.ownerOf(tokenId1), accounts[0]); - assertEq(token.balanceOf(accounts[0]), batches[0]); - assertEq(token.balanceOf(accounts[1]), batches[1]); - } - - function test_start_consecutive_id( - address receiver, - uint256[2] calldata unboundedBatches, - uint256[2] calldata unboundedTokenId, - uint96 startingId - ) public { - vm.assume(receiver != address(0)); - - uint256 startingTokenId = bound(startingId, 1, 5000); - - // We assume _maxBatchSize is 5000 (the default). This test will break otherwise. - uint256[] memory batches = new uint256[](2); - batches[0] = bound(unboundedBatches[0], startingTokenId, 5000); - batches[1] = bound(unboundedBatches[1], startingTokenId, 5000); - - ERC721ConsecutiveTarget token = new ERC721ConsecutiveTarget(toSingleton(receiver), batches, startingTokenId); - - uint256 tokenId0 = bound(unboundedTokenId[0], startingTokenId, batches[0]); - uint256 tokenId1 = bound(unboundedTokenId[1], startingTokenId, batches[1]); - - assertEq(token.ownerOf(tokenId0), receiver); - assertEq(token.ownerOf(tokenId1), receiver); - assertEq(token.balanceOf(receiver), batches[0] + batches[1]); - } -} diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Consecutive.test.js b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Consecutive.test.js deleted file mode 100644 index d2eda94..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Consecutive.test.js +++ /dev/null @@ -1,236 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { sum } = require('../../../helpers/math'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; - -describe('ERC721Consecutive', function () { - for (const offset of [0n, 1n, 42n]) { - describe(`with offset ${offset}`, function () { - async function fixture() { - const accounts = await ethers.getSigners(); - const [alice, bruce, chris, receiver] = accounts; - - const batches = [ - { receiver: alice, amount: 0n }, - { receiver: alice, amount: 1n }, - { receiver: alice, amount: 2n }, - { receiver: bruce, amount: 5n }, - { receiver: chris, amount: 0n }, - { receiver: alice, amount: 7n }, - ]; - const delegates = [alice, chris]; - - const token = await ethers.deployContract('$ERC721ConsecutiveMock', [ - name, - symbol, - offset, - delegates, - batches.map(({ receiver }) => receiver), - batches.map(({ amount }) => amount), - ]); - - return { accounts, alice, bruce, chris, receiver, batches, delegates, token }; - } - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('minting during construction', function () { - it('events are emitted at construction', async function () { - let first = offset; - for (const batch of this.batches) { - if (batch.amount > 0) { - await expect(this.token.deploymentTransaction()) - .to.emit(this.token, 'ConsecutiveTransfer') - .withArgs( - first /* fromTokenId */, - first + batch.amount - 1n /* toTokenId */, - ethers.ZeroAddress /* fromAddress */, - batch.receiver /* toAddress */, - ); - } else { - // ".to.not.emit" only looks at event name, and doesn't check the parameters - } - first += batch.amount; - } - }); - - it('ownership is set', async function () { - const owners = [ - ...Array(Number(offset)).fill(ethers.ZeroAddress), - ...this.batches.flatMap(({ receiver, amount }) => Array(Number(amount)).fill(receiver.address)), - ]; - - for (const tokenId in owners) { - if (owners[tokenId] != ethers.ZeroAddress) { - expect(await this.token.ownerOf(tokenId)).to.equal(owners[tokenId]); - } - } - }); - - it('balance & voting power are set', async function () { - for (const account of this.accounts) { - const balance = - sum(...this.batches.filter(({ receiver }) => receiver === account).map(({ amount }) => amount)) ?? 0n; - - expect(await this.token.balanceOf(account)).to.equal(balance); - - // If not delegated at construction, check before + do delegation - if (!this.delegates.includes(account)) { - expect(await this.token.getVotes(account)).to.equal(0n); - - await this.token.connect(account).delegate(account); - } - - // At this point all accounts should have delegated - expect(await this.token.getVotes(account)).to.equal(balance); - } - }); - - it('reverts on consecutive minting to the zero address', async function () { - await expect( - ethers.deployContract('$ERC721ConsecutiveMock', [ - name, - symbol, - offset, - this.delegates, - [ethers.ZeroAddress], - [10], - ]), - ) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidReceiver') - .withArgs(ethers.ZeroAddress); - }); - }); - - describe('minting after construction', function () { - it('consecutive minting is not possible after construction', async function () { - await expect(this.token.$_mintConsecutive(this.alice, 10)).to.be.revertedWithCustomError( - this.token, - 'ERC721ForbiddenBatchMint', - ); - }); - - it('simple minting is possible after construction', async function () { - const tokenId = sum(...this.batches.map(b => b.amount)) + offset; - - await expect(this.token.ownerOf(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - - await expect(this.token.$_mint(this.alice, tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.alice, tokenId); - }); - - it('cannot mint a token that has been batched minted', async function () { - const tokenId = sum(...this.batches.map(b => b.amount)) + offset - 1n; - - expect(await this.token.ownerOf(tokenId)).to.not.equal(ethers.ZeroAddress); - - await expect(this.token.$_mint(this.alice, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721InvalidSender') - .withArgs(ethers.ZeroAddress); - }); - }); - - describe('ERC721 behavior', function () { - const tokenId = offset + 1n; - - it('core takes over ownership on transfer', async function () { - await this.token.connect(this.alice).transferFrom(this.alice, this.receiver, tokenId); - - expect(await this.token.ownerOf(tokenId)).to.equal(this.receiver); - }); - - it('tokens can be burned and re-minted #1', async function () { - await expect(this.token.connect(this.alice).$_burn(tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(this.alice, ethers.ZeroAddress, tokenId); - - await expect(this.token.ownerOf(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - - await expect(this.token.$_mint(this.bruce, tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.bruce, tokenId); - - expect(await this.token.ownerOf(tokenId)).to.equal(this.bruce); - }); - - it('tokens can be burned and re-minted #2', async function () { - const tokenId = sum(...this.batches.map(({ amount }) => amount)) + offset; - - await expect(this.token.ownerOf(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - - // mint - await expect(this.token.$_mint(this.alice, tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.alice, tokenId); - - expect(await this.token.ownerOf(tokenId)).to.equal(this.alice); - - // burn - await expect(await this.token.$_burn(tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(this.alice, ethers.ZeroAddress, tokenId); - - await expect(this.token.ownerOf(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - - // re-mint - await expect(this.token.$_mint(this.bruce, tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.bruce, tokenId); - - expect(await this.token.ownerOf(tokenId)).to.equal(this.bruce); - }); - }); - }); - } - - describe('invalid use', function () { - const receiver = ethers.Wallet.createRandom(); - - it('cannot mint a batch larger than 5000', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveMock'); - - await expect(ethers.deployContract('$ERC721ConsecutiveMock', [name, symbol, 0, [], [receiver], [5001n]])) - .to.be.revertedWithCustomError({ interface }, 'ERC721ExceededMaxBatchMint') - .withArgs(5001n, 5000n); - }); - - it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); - - await expect( - ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); - }); - - it('cannot use single minting during construction', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveNoConstructorMintMock'); - - await expect( - ethers.deployContract('$ERC721ConsecutiveNoConstructorMintMock', [name, symbol]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721ForbiddenMint'); - }); - - it('consecutive mint not compatible with enumerability', async function () { - const { interface } = await ethers.getContractFactory('$ERC721ConsecutiveEnumerableMock'); - - await expect( - ethers.deployContract('$ERC721ConsecutiveEnumerableMock', [name, symbol, [receiver], [100n]]), - ).to.be.revertedWithCustomError({ interface }, 'ERC721EnumerableForbiddenBatchMint'); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Pausable.test.js b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Pausable.test.js deleted file mode 100644 index acf731a..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Pausable.test.js +++ /dev/null @@ -1,81 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; -const tokenId = 1n; -const otherTokenId = 2n; -const data = ethers.Typed.bytes('0x42'); - -async function fixture() { - const [owner, receiver, operator] = await ethers.getSigners(); - const token = await ethers.deployContract('$ERC721Pausable', [name, symbol]); - return { owner, receiver, operator, token }; -} - -describe('ERC721Pausable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('when token is paused', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, tokenId); - await this.token.$_pause(); - }); - - it('reverts when trying to transferFrom', async function () { - await expect( - this.token.connect(this.owner).transferFrom(this.owner, this.receiver, tokenId), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to safeTransferFrom', async function () { - await expect( - this.token.connect(this.owner).safeTransferFrom(this.owner, this.receiver, tokenId), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to safeTransferFrom with data', async function () { - await expect( - this.token.connect(this.owner).safeTransferFrom(this.owner, this.receiver, tokenId, data), - ).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - it('reverts when trying to mint', async function () { - await expect(this.token.$_mint(this.receiver, otherTokenId)).to.be.revertedWithCustomError( - this.token, - 'EnforcedPause', - ); - }); - - it('reverts when trying to burn', async function () { - await expect(this.token.$_burn(tokenId)).to.be.revertedWithCustomError(this.token, 'EnforcedPause'); - }); - - describe('getApproved', function () { - it('returns approved address', async function () { - expect(await this.token.getApproved(tokenId)).to.equal(ethers.ZeroAddress); - }); - }); - - describe('balanceOf', function () { - it('returns the amount of tokens owned by the given address', async function () { - expect(await this.token.balanceOf(this.owner)).to.equal(1n); - }); - }); - - describe('ownerOf', function () { - it('returns the amount of tokens owned by the given address', async function () { - expect(await this.token.ownerOf(tokenId)).to.equal(this.owner); - }); - }); - - describe('isApprovedForAll', function () { - it('returns the approval of the operator', async function () { - expect(await this.token.isApprovedForAll(this.owner, this.operator)).to.be.false; - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Royalty.test.js b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Royalty.test.js deleted file mode 100644 index e11954a..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Royalty.test.js +++ /dev/null @@ -1,57 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldBehaveLikeERC2981 } = require('../../common/ERC2981.behavior'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; - -const tokenId1 = 1n; -const tokenId2 = 2n; -const royalty = 200n; -const salePrice = 1000n; - -async function fixture() { - const [account1, account2, recipient] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC721Royalty', [name, symbol]); - await token.$_mint(account1, tokenId1); - await token.$_mint(account1, tokenId2); - - return { account1, account2, recipient, token }; -} - -describe('ERC721Royalty', function () { - beforeEach(async function () { - Object.assign( - this, - await loadFixture(fixture), - { tokenId1, tokenId2, royalty, salePrice }, // set for behavior tests - ); - }); - - describe('token specific functions', function () { - beforeEach(async function () { - await this.token.$_setTokenRoyalty(tokenId1, this.recipient, royalty); - }); - - it('royalty information are kept during burn and re-mint', async function () { - await this.token.$_burn(tokenId1); - - expect(await this.token.royaltyInfo(tokenId1, salePrice)).to.deep.equal([ - this.recipient.address, - (salePrice * royalty) / 10000n, - ]); - - await this.token.$_mint(this.account2, tokenId1); - - expect(await this.token.royaltyInfo(tokenId1, salePrice)).to.deep.equal([ - this.recipient.address, - (salePrice * royalty) / 10000n, - ]); - }); - }); - - shouldBehaveLikeERC2981(); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721URIStorage.test.js b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721URIStorage.test.js deleted file mode 100644 index 830c13a..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721URIStorage.test.js +++ /dev/null @@ -1,121 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldSupportInterfaces } = require('../../../utils/introspection/SupportsInterface.behavior'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; -const baseURI = 'https://api.example.com/v1/'; -const otherBaseURI = 'https://api.example.com/v2/'; -const sampleUri = 'mock://mytoken'; -const tokenId = 1n; -const nonExistentTokenId = 2n; - -async function fixture() { - const [owner] = await ethers.getSigners(); - const token = await ethers.deployContract('$ERC721URIStorageMock', [name, symbol]); - return { owner, token }; -} - -describe('ERC721URIStorage', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldSupportInterfaces(['0x49064906']); - - describe('token URI', function () { - beforeEach(async function () { - await this.token.$_mint(this.owner, tokenId); - }); - - it('it is empty by default', async function () { - expect(await this.token.tokenURI(tokenId)).to.equal(''); - }); - - it('reverts when queried for non existent token id', async function () { - await expect(this.token.tokenURI(nonExistentTokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(nonExistentTokenId); - }); - - it('can be set for a token id', async function () { - await this.token.$_setTokenURI(tokenId, sampleUri); - expect(await this.token.tokenURI(tokenId)).to.equal(sampleUri); - }); - - it('setting the uri emits an event', async function () { - await expect(this.token.$_setTokenURI(tokenId, sampleUri)) - .to.emit(this.token, 'MetadataUpdate') - .withArgs(tokenId); - }); - - it('setting the uri for non existent token id is allowed', async function () { - await expect(await this.token.$_setTokenURI(nonExistentTokenId, sampleUri)) - .to.emit(this.token, 'MetadataUpdate') - .withArgs(nonExistentTokenId); - - // value will be accessible after mint - await this.token.$_mint(this.owner, nonExistentTokenId); - expect(await this.token.tokenURI(nonExistentTokenId)).to.equal(sampleUri); - }); - - it('base URI can be set', async function () { - await this.token.setBaseURI(baseURI); - expect(await this.token.$_baseURI()).to.equal(baseURI); - }); - - it('base URI is added as a prefix to the token URI', async function () { - await this.token.setBaseURI(baseURI); - await this.token.$_setTokenURI(tokenId, sampleUri); - - expect(await this.token.tokenURI(tokenId)).to.equal(baseURI + sampleUri); - }); - - it('token URI can be changed by changing the base URI', async function () { - await this.token.setBaseURI(baseURI); - await this.token.$_setTokenURI(tokenId, sampleUri); - - await this.token.setBaseURI(otherBaseURI); - expect(await this.token.tokenURI(tokenId)).to.equal(otherBaseURI + sampleUri); - }); - - it('tokenId is appended to base URI for tokens with no URI', async function () { - await this.token.setBaseURI(baseURI); - - expect(await this.token.tokenURI(tokenId)).to.equal(baseURI + tokenId); - }); - - it('tokens without URI can be burnt ', async function () { - await this.token.$_burn(tokenId); - - await expect(this.token.tokenURI(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - }); - - it('tokens with URI can be burnt ', async function () { - await this.token.$_setTokenURI(tokenId, sampleUri); - - await this.token.$_burn(tokenId); - - await expect(this.token.tokenURI(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - }); - - it('tokens URI is kept if token is burnt and reminted ', async function () { - await this.token.$_setTokenURI(tokenId, sampleUri); - - await this.token.$_burn(tokenId); - - await expect(this.token.tokenURI(tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721NonexistentToken') - .withArgs(tokenId); - - await this.token.$_mint(this.owner, tokenId); - expect(await this.token.tokenURI(tokenId)).to.equal(sampleUri); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Votes.test.js b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Votes.test.js deleted file mode 100644 index dcae1b8..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Votes.test.js +++ /dev/null @@ -1,194 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture, mine } = require('@nomicfoundation/hardhat-network-helpers'); - -const time = require('../../../helpers/time'); - -const { shouldBehaveLikeVotes } = require('../../../governance/utils/Votes.behavior'); - -const TOKENS = [ - { Token: '$ERC721Votes', mode: 'blocknumber' }, - // no timestamp mode for ERC721Votes yet -]; - -const name = 'My Vote'; -const symbol = 'MTKN'; -const version = '1'; -const tokens = [ethers.parseEther('10000000'), 10n, 20n, 30n]; - -describe('ERC721Votes', function () { - for (const { Token, mode } of TOKENS) { - const fixture = async () => { - // accounts is required by shouldBehaveLikeVotes - const accounts = await ethers.getSigners(); - const [holder, recipient, other1, other2] = accounts; - - const token = await ethers.deployContract(Token, [name, symbol, name, version]); - - return { accounts, holder, recipient, other1, other2, token }; - }; - - describe(`vote with ${mode}`, function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - this.votes = this.token; - }); - - // includes ERC6372 behavior check - shouldBehaveLikeVotes(tokens, { mode, fungible: false }); - - describe('balanceOf', function () { - beforeEach(async function () { - await this.votes.$_mint(this.holder, tokens[0]); - await this.votes.$_mint(this.holder, tokens[1]); - await this.votes.$_mint(this.holder, tokens[2]); - await this.votes.$_mint(this.holder, tokens[3]); - }); - - it('grants to initial account', async function () { - expect(await this.votes.balanceOf(this.holder)).to.equal(4n); - }); - }); - - describe('transfers', function () { - beforeEach(async function () { - await this.votes.$_mint(this.holder, tokens[0]); - }); - - it('no delegation', async function () { - await expect(this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0])) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, tokens[0]) - .to.not.emit(this.token, 'DelegateVotesChanged'); - - this.holderVotes = 0n; - this.recipientVotes = 0n; - }); - - it('sender delegation', async function () { - await this.votes.connect(this.holder).delegate(this.holder); - - const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, tokens[0]) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.holder, 1n, 0n); - - const { logs } = await tx.wait(); - const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged'); - for (const event of logs.filter(event => event.fragment.name == 'Transfer')) { - expect(event.index).to.lt(index); - } - - this.holderVotes = 0n; - this.recipientVotes = 0n; - }); - - it('receiver delegation', async function () { - await this.votes.connect(this.recipient).delegate(this.recipient); - - const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, tokens[0]) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.recipient, 0n, 1n); - - const { logs } = await tx.wait(); - const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged'); - for (const event of logs.filter(event => event.fragment.name == 'Transfer')) { - expect(event.index).to.lt(index); - } - - this.holderVotes = 0n; - this.recipientVotes = 1n; - }); - - it('full delegation', async function () { - await this.votes.connect(this.holder).delegate(this.holder); - await this.votes.connect(this.recipient).delegate(this.recipient); - - const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]); - await expect(tx) - .to.emit(this.token, 'Transfer') - .withArgs(this.holder, this.recipient, tokens[0]) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.holder, 1n, 0n) - .to.emit(this.token, 'DelegateVotesChanged') - .withArgs(this.recipient, 0n, 1n); - - const { logs } = await tx.wait(); - const { index } = logs.find(event => event.fragment.name == 'DelegateVotesChanged'); - for (const event of logs.filter(event => event.fragment.name == 'Transfer')) { - expect(event.index).to.lt(index); - } - - this.holderVotes = 0; - this.recipientVotes = 1n; - }); - - it('returns the same total supply on transfers', async function () { - await this.votes.connect(this.holder).delegate(this.holder); - - const tx = await this.votes.connect(this.holder).transferFrom(this.holder, this.recipient, tokens[0]); - const timepoint = await time.clockFromReceipt[mode](tx); - - await mine(2); - - expect(await this.votes.getPastTotalSupply(timepoint - 1n)).to.equal(1n); - expect(await this.votes.getPastTotalSupply(timepoint + 1n)).to.equal(1n); - - this.holderVotes = 0n; - this.recipientVotes = 0n; - }); - - it('generally returns the voting balance at the appropriate checkpoint', async function () { - await this.votes.$_mint(this.holder, tokens[1]); - await this.votes.$_mint(this.holder, tokens[2]); - await this.votes.$_mint(this.holder, tokens[3]); - - const total = await this.votes.balanceOf(this.holder); - - const t1 = await this.votes.connect(this.holder).delegate(this.other1); - await mine(2); - const t2 = await this.votes.connect(this.holder).transferFrom(this.holder, this.other2, tokens[0]); - await mine(2); - const t3 = await this.votes.connect(this.holder).transferFrom(this.holder, this.other2, tokens[2]); - await mine(2); - const t4 = await this.votes.connect(this.other2).transferFrom(this.other2, this.holder, tokens[2]); - await mine(2); - - t1.timepoint = await time.clockFromReceipt[mode](t1); - t2.timepoint = await time.clockFromReceipt[mode](t2); - t3.timepoint = await time.clockFromReceipt[mode](t3); - t4.timepoint = await time.clockFromReceipt[mode](t4); - - expect(await this.votes.getPastVotes(this.other1, t1.timepoint - 1n)).to.equal(0n); - expect(await this.votes.getPastVotes(this.other1, t1.timepoint)).to.equal(total); - expect(await this.votes.getPastVotes(this.other1, t1.timepoint + 1n)).to.equal(total); - expect(await this.votes.getPastVotes(this.other1, t2.timepoint)).to.equal(3n); - expect(await this.votes.getPastVotes(this.other1, t2.timepoint + 1n)).to.equal(3n); - expect(await this.votes.getPastVotes(this.other1, t3.timepoint)).to.equal(2n); - expect(await this.votes.getPastVotes(this.other1, t3.timepoint + 1n)).to.equal(2n); - expect(await this.votes.getPastVotes(this.other1, t4.timepoint)).to.equal('3'); - expect(await this.votes.getPastVotes(this.other1, t4.timepoint + 1n)).to.equal(3n); - - this.holderVotes = 0n; - this.recipientVotes = 0n; - }); - - afterEach(async function () { - expect(await this.votes.getVotes(this.holder)).to.equal(this.holderVotes); - expect(await this.votes.getVotes(this.recipient)).to.equal(this.recipientVotes); - - // need to advance 2 blocks to see the effect of a transfer on "getPastVotes" - const timepoint = await time.clock[mode](); - await mine(); - expect(await this.votes.getPastVotes(this.holder, timepoint)).to.equal(this.holderVotes); - expect(await this.votes.getPastVotes(this.recipient, timepoint)).to.equal(this.recipientVotes); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Wrapper.test.js b/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Wrapper.test.js deleted file mode 100644 index eeead4c..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/extensions/ERC721Wrapper.test.js +++ /dev/null @@ -1,201 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldBehaveLikeERC721 } = require('../ERC721.behavior'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; -const tokenId = 1n; -const otherTokenId = 2n; - -async function fixture() { - const accounts = await ethers.getSigners(); - const [owner, approved, other] = accounts; - - const underlying = await ethers.deployContract('$ERC721', [name, symbol]); - await underlying.$_safeMint(owner, tokenId); - await underlying.$_safeMint(owner, otherTokenId); - const token = await ethers.deployContract('$ERC721Wrapper', [`Wrapped ${name}`, `W${symbol}`, underlying]); - - return { accounts, owner, approved, other, underlying, token }; -} - -describe('ERC721Wrapper', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('has a name', async function () { - expect(await this.token.name()).to.equal(`Wrapped ${name}`); - }); - - it('has a symbol', async function () { - expect(await this.token.symbol()).to.equal(`W${symbol}`); - }); - - it('has underlying', async function () { - expect(await this.token.underlying()).to.equal(this.underlying); - }); - - describe('depositFor', function () { - it('works with token approval', async function () { - await this.underlying.connect(this.owner).approve(this.token, tokenId); - - await expect(this.token.connect(this.owner).depositFor(this.owner, [tokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.owner, this.token, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.owner, tokenId); - }); - - it('works with approval for all', async function () { - await this.underlying.connect(this.owner).setApprovalForAll(this.token, true); - - await expect(this.token.connect(this.owner).depositFor(this.owner, [tokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.owner, this.token, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.owner, tokenId); - }); - - it('works sending to another account', async function () { - await this.underlying.connect(this.owner).approve(this.token, tokenId); - - await expect(this.token.connect(this.owner).depositFor(this.other, [tokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.owner, this.token, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.other, tokenId); - }); - - it('works with multiple tokens', async function () { - await this.underlying.connect(this.owner).approve(this.token, tokenId); - await this.underlying.connect(this.owner).approve(this.token, otherTokenId); - - await expect(this.token.connect(this.owner).depositFor(this.owner, [tokenId, otherTokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.owner, this.token, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.owner, tokenId) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.owner, this.token, otherTokenId) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.owner, otherTokenId); - }); - - it('reverts with missing approval', async function () { - await expect(this.token.connect(this.owner).depositFor(this.owner, [tokenId])) - .to.be.revertedWithCustomError(this.token, 'ERC721InsufficientApproval') - .withArgs(this.token, tokenId); - }); - }); - - describe('withdrawTo', function () { - beforeEach(async function () { - await this.underlying.connect(this.owner).approve(this.token, tokenId); - await this.token.connect(this.owner).depositFor(this.owner, [tokenId]); - }); - - it('works for an owner', async function () { - await expect(this.token.connect(this.owner).withdrawTo(this.owner, [tokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token, this.owner, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, ethers.ZeroAddress, tokenId); - }); - - it('works for an approved', async function () { - await this.token.connect(this.owner).approve(this.approved, tokenId); - - await expect(this.token.connect(this.approved).withdrawTo(this.owner, [tokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token, this.owner, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, ethers.ZeroAddress, tokenId); - }); - - it('works for an approved for all', async function () { - await this.token.connect(this.owner).setApprovalForAll(this.approved, true); - - await expect(this.token.connect(this.approved).withdrawTo(this.owner, [tokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token, this.owner, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, ethers.ZeroAddress, tokenId); - }); - - it("doesn't work for a non-owner nor approved", async function () { - await expect(this.token.connect(this.other).withdrawTo(this.owner, [tokenId])) - .to.be.revertedWithCustomError(this.token, 'ERC721InsufficientApproval') - .withArgs(this.other, tokenId); - }); - - it('works with multiple tokens', async function () { - await this.underlying.connect(this.owner).approve(this.token, otherTokenId); - await this.token.connect(this.owner).depositFor(this.owner, [otherTokenId]); - - await expect(this.token.connect(this.owner).withdrawTo(this.owner, [tokenId, otherTokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token, this.owner, tokenId) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token, this.owner, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, ethers.ZeroAddress, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, ethers.ZeroAddress, tokenId); - }); - - it('works to another account', async function () { - await expect(this.token.connect(this.owner).withdrawTo(this.other, [tokenId])) - .to.emit(this.underlying, 'Transfer') - .withArgs(this.token, this.other, tokenId) - .to.emit(this.token, 'Transfer') - .withArgs(this.owner, ethers.ZeroAddress, tokenId); - }); - }); - - describe('onERC721Received', function () { - it('only allows calls from underlying', async function () { - await expect( - this.token.connect(this.other).onERC721Received( - this.owner, - this.token, - tokenId, - this.other.address, // Correct data - ), - ) - .to.be.revertedWithCustomError(this.token, 'ERC721UnsupportedToken') - .withArgs(this.other); - }); - - it('mints a token to from', async function () { - await expect(this.underlying.connect(this.owner).safeTransferFrom(this.owner, this.token, tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.owner, tokenId); - }); - }); - - describe('_recover', function () { - it('works if there is something to recover', async function () { - // Should use `transferFrom` to avoid `onERC721Received` minting - await this.underlying.connect(this.owner).transferFrom(this.owner, this.token, tokenId); - - await expect(this.token.$_recover(this.other, tokenId)) - .to.emit(this.token, 'Transfer') - .withArgs(ethers.ZeroAddress, this.other, tokenId); - }); - - it('reverts if there is nothing to recover', async function () { - const holder = await this.underlying.ownerOf(tokenId); - - await expect(this.token.$_recover(holder, tokenId)) - .to.be.revertedWithCustomError(this.token, 'ERC721IncorrectOwner') - .withArgs(this.token, tokenId, holder); - }); - }); - - describe('ERC712 behavior', function () { - shouldBehaveLikeERC721(); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/utils/ERC721Holder.test.js b/lib_openzeppelin_contracts/test/token/ERC721/utils/ERC721Holder.test.js deleted file mode 100644 index 31dd2fd..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/utils/ERC721Holder.test.js +++ /dev/null @@ -1,20 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -const name = 'Non Fungible Token'; -const symbol = 'NFT'; -const tokenId = 1n; - -describe('ERC721Holder', function () { - it('receives an ERC721 token', async function () { - const [owner] = await ethers.getSigners(); - - const token = await ethers.deployContract('$ERC721', [name, symbol]); - await token.$_mint(owner, tokenId); - - const receiver = await ethers.deployContract('$ERC721Holder'); - await token.connect(owner).safeTransferFrom(owner, receiver, tokenId); - - expect(await token.ownerOf(tokenId)).to.equal(receiver); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/ERC721/utils/ERC721Utils.test.js b/lib_openzeppelin_contracts/test/token/ERC721/utils/ERC721Utils.test.js deleted file mode 100644 index 2327d1a..0000000 --- a/lib_openzeppelin_contracts/test/token/ERC721/utils/ERC721Utils.test.js +++ /dev/null @@ -1,94 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { RevertType } = require('../../../helpers/enums'); -const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); - -const tokenId = 1n; - -const RECEIVER_MAGIC_VALUE = '0x150b7a02'; - -const deployReceiver = (revertType, returnValue = RECEIVER_MAGIC_VALUE) => - ethers.deployContract('$ERC721ReceiverMock', [returnValue, revertType]); - -const fixture = async () => { - const [eoa, operator, owner] = await ethers.getSigners(); - const utils = await ethers.deployContract('$ERC721Utils'); - - const receivers = { - correct: await deployReceiver(RevertType.None), - invalid: await deployReceiver(RevertType.None, '0xdeadbeef'), - message: await deployReceiver(RevertType.RevertWithMessage), - empty: await deployReceiver(RevertType.RevertWithoutMessage), - customError: await deployReceiver(RevertType.RevertWithCustomError), - panic: await deployReceiver(RevertType.Panic), - nonReceiver: await ethers.deployContract('CallReceiverMock'), - eoa, - }; - - return { operator, owner, utils, receivers }; -}; - -describe('ERC721Utils', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('onERC721Received', function () { - it('succeeds when called by an EOA', async function () { - await expect(this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.eoa, tokenId, '0x')).to - .not.be.reverted; - }); - - it('succeeds when data is passed', async function () { - const data = '0x12345678'; - await expect(this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.correct, tokenId, data)) - .to.not.be.reverted; - }); - - it('succeeds when data is empty', async function () { - await expect(this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.correct, tokenId, '0x')) - .to.not.be.reverted; - }); - - it('reverts when receiver returns invalid value', async function () { - await expect(this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.invalid, tokenId, '0x')) - .to.be.revertedWithCustomError(this.utils, 'ERC721InvalidReceiver') - .withArgs(this.receivers.invalid); - }); - - it('reverts when receiver reverts with message', async function () { - await expect( - this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.message, tokenId, '0x'), - ).to.be.revertedWith('ERC721ReceiverMock: reverting'); - }); - - it('reverts when receiver reverts without message', async function () { - await expect(this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.empty, tokenId, '0x')) - .to.be.revertedWithCustomError(this.utils, 'ERC721InvalidReceiver') - .withArgs(this.receivers.empty); - }); - - it('reverts when receiver reverts with custom error', async function () { - await expect( - this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.customError, tokenId, '0x'), - ) - .to.be.revertedWithCustomError(this.receivers.customError, 'CustomError') - .withArgs(RECEIVER_MAGIC_VALUE); - }); - - it('reverts when receiver panics', async function () { - await expect( - this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.panic, tokenId, '0x'), - ).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO); - }); - - it('reverts when receiver does not implement onERC721Received', async function () { - await expect( - this.utils.$checkOnERC721Received(this.operator, this.owner, this.receivers.nonReceiver, tokenId, '0x'), - ) - .to.be.revertedWithCustomError(this.utils, 'ERC721InvalidReceiver') - .withArgs(this.receivers.nonReceiver); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/token/common/ERC2981.behavior.js b/lib_openzeppelin_contracts/test/token/common/ERC2981.behavior.js deleted file mode 100644 index ae6abcc..0000000 --- a/lib_openzeppelin_contracts/test/token/common/ERC2981.behavior.js +++ /dev/null @@ -1,152 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior'); - -function shouldBehaveLikeERC2981() { - const royaltyFraction = 10n; - - shouldSupportInterfaces(['ERC2981']); - - describe('default royalty', function () { - beforeEach(async function () { - await this.token.$_setDefaultRoyalty(this.account1, royaltyFraction); - }); - - it('checks royalty is set', async function () { - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.deep.equal([ - this.account1.address, - (this.salePrice * royaltyFraction) / 10_000n, - ]); - }); - - it('updates royalty amount', async function () { - const newFraction = 25n; - - await this.token.$_setDefaultRoyalty(this.account1, newFraction); - - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.deep.equal([ - this.account1.address, - (this.salePrice * newFraction) / 10_000n, - ]); - }); - - it('holds same royalty value for different tokens', async function () { - const newFraction = 20n; - - await this.token.$_setDefaultRoyalty(this.account1, newFraction); - - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.deep.equal( - await this.token.royaltyInfo(this.tokenId2, this.salePrice), - ); - }); - - it('Remove royalty information', async function () { - const newValue = 0n; - await this.token.$_deleteDefaultRoyalty(); - - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.deep.equal([ethers.ZeroAddress, newValue]); - - expect(await this.token.royaltyInfo(this.tokenId2, this.salePrice)).to.deep.equal([ethers.ZeroAddress, newValue]); - }); - - it('reverts if invalid parameters', async function () { - const royaltyDenominator = await this.token.$_feeDenominator(); - - await expect(this.token.$_setDefaultRoyalty(ethers.ZeroAddress, royaltyFraction)) - .to.be.revertedWithCustomError(this.token, 'ERC2981InvalidDefaultRoyaltyReceiver') - .withArgs(ethers.ZeroAddress); - - const anotherRoyaltyFraction = 11000n; - - await expect(this.token.$_setDefaultRoyalty(this.account1, anotherRoyaltyFraction)) - .to.be.revertedWithCustomError(this.token, 'ERC2981InvalidDefaultRoyalty') - .withArgs(anotherRoyaltyFraction, royaltyDenominator); - }); - }); - - describe('token based royalty', function () { - beforeEach(async function () { - await this.token.$_setTokenRoyalty(this.tokenId1, this.account1, royaltyFraction); - }); - - it('updates royalty amount', async function () { - const newFraction = 25n; - - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.deep.equal([ - this.account1.address, - (this.salePrice * royaltyFraction) / 10_000n, - ]); - - await this.token.$_setTokenRoyalty(this.tokenId1, this.account1, newFraction); - - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.deep.equal([ - this.account1.address, - (this.salePrice * newFraction) / 10_000n, - ]); - }); - - it('holds different values for different tokens', async function () { - const newFraction = 20n; - - await this.token.$_setTokenRoyalty(this.tokenId2, this.account1, newFraction); - - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.not.deep.equal( - await this.token.royaltyInfo(this.tokenId2, this.salePrice), - ); - }); - - it('reverts if invalid parameters', async function () { - const royaltyDenominator = await this.token.$_feeDenominator(); - - await expect(this.token.$_setTokenRoyalty(this.tokenId1, ethers.ZeroAddress, royaltyFraction)) - .to.be.revertedWithCustomError(this.token, 'ERC2981InvalidTokenRoyaltyReceiver') - .withArgs(this.tokenId1, ethers.ZeroAddress); - - const anotherRoyaltyFraction = 11000n; - - await expect(this.token.$_setTokenRoyalty(this.tokenId1, this.account1, anotherRoyaltyFraction)) - .to.be.revertedWithCustomError(this.token, 'ERC2981InvalidTokenRoyalty') - .withArgs(this.tokenId1, anotherRoyaltyFraction, royaltyDenominator); - }); - - it('can reset token after setting royalty', async function () { - const newFraction = 30n; - - await this.token.$_setTokenRoyalty(this.tokenId1, this.account2, newFraction); - - // Tokens must have own information - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.deep.equal([ - this.account2.address, - (this.salePrice * newFraction) / 10_000n, - ]); - - await this.token.$_setTokenRoyalty(this.tokenId2, this.account1, 0n); - - // Token must not share default information - expect(await this.token.royaltyInfo(this.tokenId2, this.salePrice)).to.deep.equal([this.account1.address, 0n]); - }); - - it('can hold default and token royalty information', async function () { - const newFraction = 30n; - - await this.token.$_setTokenRoyalty(this.tokenId2, this.account2, newFraction); - - // Tokens must not have same values - expect(await this.token.royaltyInfo(this.tokenId1, this.salePrice)).to.not.deep.equal([ - this.account2.address, - (this.salePrice * newFraction) / 10_000n, - ]); - - // Updated token must have new values - expect(await this.token.royaltyInfo(this.tokenId2, this.salePrice)).to.deep.equal([ - this.account2.address, - (this.salePrice * newFraction) / 10_000n, - ]); - }); - }); -} - -module.exports = { - shouldBehaveLikeERC2981, -}; diff --git a/lib_openzeppelin_contracts/test/utils/Address.test.js b/lib_openzeppelin_contracts/test/utils/Address.test.js deleted file mode 100644 index 2177539..0000000 --- a/lib_openzeppelin_contracts/test/utils/Address.test.js +++ /dev/null @@ -1,280 +0,0 @@ -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 coder = ethers.AbiCoder.defaultAbiCoder(); - -async function fixture() { - const [recipient, other] = await ethers.getSigners(); - - const mock = await ethers.deployContract('$Address'); - const target = await ethers.deployContract('CallReceiverMock'); - const targetEther = await ethers.deployContract('EtherReceiverMock'); - - return { recipient, other, mock, target, targetEther }; -} - -describe('Address', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('sendValue', function () { - describe('when sender contract has no funds', function () { - it('sends 0 wei', async function () { - await expect(this.mock.$sendValue(this.other, 0n)).to.changeEtherBalance(this.recipient, 0n); - }); - - it('reverts when sending non-zero amounts', async function () { - await expect(this.mock.$sendValue(this.other, 1n)) - .to.be.revertedWithCustomError(this.mock, 'InsufficientBalance') - .withArgs(0n, 1n); - }); - }); - - describe('when sender contract has funds', function () { - const funds = ethers.parseEther('1'); - - beforeEach(async function () { - await this.other.sendTransaction({ to: this.mock, value: funds }); - }); - - describe('with EOA recipient', function () { - it('sends 0 wei', async function () { - await expect(this.mock.$sendValue(this.recipient, 0n)).to.changeEtherBalance(this.recipient, 0n); - }); - - it('sends non-zero amounts', async function () { - await expect(this.mock.$sendValue(this.recipient, funds - 1n)).to.changeEtherBalance( - this.recipient, - funds - 1n, - ); - }); - - it('sends the whole balance', async function () { - await expect(this.mock.$sendValue(this.recipient, funds)).to.changeEtherBalance(this.recipient, funds); - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - }); - - it('reverts when sending more than the balance', async function () { - await expect(this.mock.$sendValue(this.recipient, funds + 1n)) - .to.be.revertedWithCustomError(this.mock, 'InsufficientBalance') - .withArgs(funds, funds + 1n); - }); - }); - - describe('with contract recipient', function () { - it('sends funds', async function () { - await this.targetEther.setAcceptEther(true); - await expect(this.mock.$sendValue(this.targetEther, funds)).to.changeEtherBalance(this.targetEther, funds); - }); - - it('reverts on recipient revert', async function () { - await this.targetEther.setAcceptEther(false); - await expect(this.mock.$sendValue(this.targetEther, funds)).to.be.revertedWithCustomError( - this.mock, - 'FailedCall', - ); - }); - }); - }); - }); - - describe('functionCall', function () { - describe('with valid contract receiver', function () { - it('calls the requested function', async function () { - const call = this.target.interface.encodeFunctionData('mockFunction'); - - await expect(this.mock.$functionCall(this.target, call)) - .to.emit(this.target, 'MockFunctionCalled') - .to.emit(this.mock, 'return$functionCall') - .withArgs(coder.encode(['string'], ['0x1234'])); - }); - - it('calls the requested empty return function', async function () { - const call = this.target.interface.encodeFunctionData('mockFunctionEmptyReturn'); - - await expect(this.mock.$functionCall(this.target, call)).to.emit(this.target, 'MockFunctionCalled'); - }); - - it('reverts when the called function reverts with no reason', async function () { - const call = this.target.interface.encodeFunctionData('mockFunctionRevertsNoReason'); - - await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - }); - - it('reverts when the called function reverts, bubbling up the revert reason', async function () { - const call = this.target.interface.encodeFunctionData('mockFunctionRevertsReason'); - - await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWith('CallReceiverMock: reverting'); - }); - - it('reverts when the called function runs out of gas', async function () { - const call = this.target.interface.encodeFunctionData('mockFunctionOutOfGas'); - - await expect(this.mock.$functionCall(this.target, call, { gasLimit: 120_000n })).to.be.revertedWithCustomError( - this.mock, - 'FailedCall', - ); - }); - - it('reverts when the called function throws', async function () { - const call = this.target.interface.encodeFunctionData('mockFunctionThrows'); - - await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithPanic(PANIC_CODES.ASSERTION_ERROR); - }); - - it('reverts when function does not exist', async function () { - const interface = new ethers.Interface(['function mockFunctionDoesNotExist()']); - const call = interface.encodeFunctionData('mockFunctionDoesNotExist'); - - await expect(this.mock.$functionCall(this.target, call)).to.be.revertedWithCustomError(this.mock, 'FailedCall'); - }); - }); - - describe('with non-contract receiver', function () { - it('reverts when address is not a contract', async function () { - const call = this.target.interface.encodeFunctionData('mockFunction'); - - await expect(this.mock.$functionCall(this.recipient, call)) - .to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode') - .withArgs(this.recipient); - }); - }); - }); - - describe('functionCallWithValue', function () { - describe('with zero value', function () { - it('calls the requested function', async function () { - const call = this.target.interface.encodeFunctionData('mockFunction'); - - await expect(this.mock.$functionCallWithValue(this.target, call, 0n)) - .to.emit(this.target, 'MockFunctionCalled') - .to.emit(this.mock, 'return$functionCallWithValue') - .withArgs(coder.encode(['string'], ['0x1234'])); - }); - }); - - describe('with non-zero value', function () { - const value = ethers.parseEther('1.2'); - - it('reverts if insufficient sender balance', async function () { - const call = this.target.interface.encodeFunctionData('mockFunction'); - - await expect(this.mock.$functionCallWithValue(this.target, call, value)) - .to.be.revertedWithCustomError(this.mock, 'InsufficientBalance') - .withArgs(0n, value); - }); - - it('calls the requested function with existing value', async function () { - await this.other.sendTransaction({ to: this.mock, value }); - - const call = this.target.interface.encodeFunctionData('mockFunction'); - const tx = await this.mock.$functionCallWithValue(this.target, call, value); - - await expect(tx).to.changeEtherBalance(this.target, value); - - await expect(tx) - .to.emit(this.target, 'MockFunctionCalled') - .to.emit(this.mock, 'return$functionCallWithValue') - .withArgs(coder.encode(['string'], ['0x1234'])); - }); - - it('calls the requested function with transaction funds', async function () { - expect(await ethers.provider.getBalance(this.mock)).to.equal(0n); - - const call = this.target.interface.encodeFunctionData('mockFunction'); - const tx = await this.mock.connect(this.other).$functionCallWithValue(this.target, call, value, { value }); - - await expect(tx).to.changeEtherBalance(this.target, value); - await expect(tx) - .to.emit(this.target, 'MockFunctionCalled') - .to.emit(this.mock, 'return$functionCallWithValue') - .withArgs(coder.encode(['string'], ['0x1234'])); - }); - - it('reverts when calling non-payable functions', async function () { - await this.other.sendTransaction({ to: this.mock, value }); - - const call = this.target.interface.encodeFunctionData('mockFunctionNonPayable'); - - await expect(this.mock.$functionCallWithValue(this.target, call, value)).to.be.revertedWithCustomError( - this.mock, - 'FailedCall', - ); - }); - }); - }); - - describe('functionStaticCall', function () { - it('calls the requested function', async function () { - const call = this.target.interface.encodeFunctionData('mockStaticFunction'); - - expect(await this.mock.$functionStaticCall(this.target, call)).to.equal(coder.encode(['string'], ['0x1234'])); - }); - - it('reverts on a non-static function', async function () { - const call = this.target.interface.encodeFunctionData('mockFunction'); - - await expect(this.mock.$functionStaticCall(this.target, call)).to.be.revertedWithCustomError( - this.mock, - 'FailedCall', - ); - }); - - it('bubbles up revert reason', async function () { - const call = this.target.interface.encodeFunctionData('mockFunctionRevertsReason'); - - await expect(this.mock.$functionStaticCall(this.target, call)).to.be.revertedWith('CallReceiverMock: reverting'); - }); - - it('reverts when address is not a contract', async function () { - const call = this.target.interface.encodeFunctionData('mockFunction'); - - await expect(this.mock.$functionStaticCall(this.recipient, call)) - .to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode') - .withArgs(this.recipient); - }); - }); - - describe('functionDelegateCall', function () { - it('delegate calls the requested function', async function () { - const slot = ethers.hexlify(ethers.randomBytes(32)); - const value = ethers.hexlify(ethers.randomBytes(32)); - - const call = this.target.interface.encodeFunctionData('mockFunctionWritesStorage', [slot, value]); - - expect(await ethers.provider.getStorage(this.mock, slot)).to.equal(ethers.ZeroHash); - - await expect(await this.mock.$functionDelegateCall(this.target, call)) - .to.emit(this.mock, 'return$functionDelegateCall') - .withArgs(coder.encode(['string'], ['0x1234'])); - - expect(await ethers.provider.getStorage(this.mock, slot)).to.equal(value); - }); - - it('bubbles up revert reason', async function () { - const call = this.target.interface.encodeFunctionData('mockFunctionRevertsReason'); - - await expect(this.mock.$functionDelegateCall(this.target, call)).to.be.revertedWith( - 'CallReceiverMock: reverting', - ); - }); - - it('reverts when address is not a contract', async function () { - const call = this.target.interface.encodeFunctionData('mockFunction'); - - await expect(this.mock.$functionDelegateCall(this.recipient, call)) - .to.be.revertedWithCustomError(this.mock, 'AddressEmptyCode') - .withArgs(this.recipient); - }); - }); - - describe('verifyCallResult', function () { - it('returns returndata on success', async function () { - const returndata = '0x123abc'; - expect(await this.mock.$verifyCallResult(true, returndata)).to.equal(returndata); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/Arrays.t.sol b/lib_openzeppelin_contracts/test/utils/Arrays.t.sol deleted file mode 100644 index 9001727..0000000 --- a/lib_openzeppelin_contracts/test/utils/Arrays.t.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; -import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; - -contract ArraysTest is Test { - function testSort(uint256[] memory values) public { - Arrays.sort(values); - for (uint256 i = 1; i < values.length; ++i) { - assertLe(values[i - 1], values[i]); - } - } -} diff --git a/lib_openzeppelin_contracts/test/utils/Arrays.test.js b/lib_openzeppelin_contracts/test/utils/Arrays.test.js deleted file mode 100644 index bcb3858..0000000 --- a/lib_openzeppelin_contracts/test/utils/Arrays.test.js +++ /dev/null @@ -1,223 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { generators } = require('../helpers/random'); -const { capitalize } = require('../../scripts/helpers'); -const { TYPES } = require('../../scripts/generate/templates/Arrays.opts'); - -// See https://en.cppreference.com/w/cpp/algorithm/lower_bound -const lowerBound = (array, value) => { - const i = array.findIndex(element => value <= element); - return i == -1 ? array.length : i; -}; - -// See https://en.cppreference.com/w/cpp/algorithm/upper_bound -const upperBound = (array, value) => { - const i = array.findIndex(element => value < element); - return i == -1 ? array.length : i; -}; - -const bigintSign = x => (x > 0n ? 1 : x < 0n ? -1 : 0); -const comparator = (a, b) => bigintSign(ethers.toBigInt(a) - ethers.toBigInt(b)); -const hasDuplicates = array => array.some((v, i) => array.indexOf(v) != i); - -describe('Arrays', function () { - const fixture = async () => { - return { mock: await ethers.deployContract('$Arrays') }; - }; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('search', function () { - for (const [title, { array, tests }] of Object.entries({ - 'Even number of elements': { - array: [11n, 12n, 13n, 14n, 15n, 16n, 17n, 18n, 19n, 20n], - tests: { - 'basic case': 16n, - 'first element': 11n, - 'last element': 20n, - 'searched value is over the upper boundary': 32n, - 'searched value is under the lower boundary': 2n, - }, - }, - 'Odd number of elements': { - array: [11n, 12n, 13n, 14n, 15n, 16n, 17n, 18n, 19n, 20n, 21n], - tests: { - 'basic case': 16n, - 'first element': 11n, - 'last element': 21n, - 'searched value is over the upper boundary': 32n, - 'searched value is under the lower boundary': 2n, - }, - }, - 'Array with gap': { - array: [11n, 12n, 13n, 14n, 15n, 20n, 21n, 22n, 23n, 24n], - tests: { - 'search value in gap': 17n, - }, - }, - 'Array with duplicated elements': { - array: [0n, 10n, 10n, 10n, 10n, 10n, 10n, 10n, 20n], - tests: { - 'search value is duplicated': 10n, - }, - }, - 'Array with duplicated first element': { - array: [10n, 10n, 10n, 10n, 10n, 10n, 10n, 20n], - tests: { - 'search value is duplicated first element': 10n, - }, - }, - 'Array with duplicated last element': { - array: [0n, 10n, 10n, 10n, 10n, 10n, 10n, 10n], - tests: { - 'search value is duplicated last element': 10n, - }, - }, - 'Empty array': { - array: [], - tests: { - 'always returns 0 for empty array': 10n, - }, - }, - })) { - describe(title, function () { - const fixture = async () => { - return { instance: await ethers.deployContract('Uint256ArraysMock', [array]) }; - }; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - for (const [name, input] of Object.entries(tests)) { - describe(name, function () { - it('[deprecated] findUpperBound', async function () { - // findUpperBound does not support duplicated - if (hasDuplicates(array)) { - expect(await this.instance.findUpperBound(input)).to.equal(upperBound(array, input) - 1); - } else { - expect(await this.instance.findUpperBound(input)).to.equal(lowerBound(array, input)); - } - }); - - it('lowerBound', async function () { - expect(await this.instance.lowerBound(input)).to.equal(lowerBound(array, input)); - expect(await this.instance.lowerBoundMemory(array, input)).to.equal(lowerBound(array, input)); - }); - - it('upperBound', async function () { - expect(await this.instance.upperBound(input)).to.equal(upperBound(array, input)); - expect(await this.instance.upperBoundMemory(array, input)).to.equal(upperBound(array, input)); - }); - }); - } - }); - } - }); - - for (const type of TYPES) { - const elements = Array.from({ length: 10 }, generators[type]); - - describe(type, function () { - const fixture = async () => { - return { instance: await ethers.deployContract(`${capitalize(type)}ArraysMock`, [elements]) }; - }; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('sort', function () { - for (const length of [0, 1, 2, 8, 32, 128]) { - describe(`${type}[] of length ${length}`, function () { - beforeEach(async function () { - this.array = Array.from({ length }, generators[type]); - }); - - afterEach(async function () { - const expected = Array.from(this.array).sort(comparator); - const reversed = Array.from(expected).reverse(); - expect(await this.instance.sort(this.array)).to.deep.equal(expected); - expect(await this.instance.sortReverse(this.array)).to.deep.equal(reversed); - }); - - it('sort array', async function () { - // nothing to do here, beforeEach and afterEach already take care of everything. - }); - - if (length > 1) { - it('sort array for identical elements', async function () { - // duplicate the first value to all elements - this.array.fill(this.array.at(0)); - }); - - it('sort already sorted array', async function () { - // pre-sort the elements - this.array.sort(comparator); - }); - - it('sort reversed array', async function () { - // pre-sort in reverse order - this.array.sort(comparator).reverse(); - }); - - it('sort almost sorted array', async function () { - // pre-sort + rotate (move the last element to the front) for an almost sorted effect - this.array.sort(comparator); - this.array.unshift(this.array.pop()); - }); - } - }); - } - }); - - describe('unsafeAccess', function () { - describe('storage', function () { - for (const i in elements) { - it(`unsafeAccess within bounds #${i}`, async function () { - expect(await this.instance.unsafeAccess(i)).to.equal(elements[i]); - }); - } - - it('unsafeAccess outside bounds', async function () { - await expect(this.instance.unsafeAccess(elements.length)).to.not.be.rejected; - }); - - it('unsafeSetLength changes the length or the array', async function () { - const newLength = generators.uint256(); - - expect(await this.instance.length()).to.equal(elements.length); - await expect(this.instance.unsafeSetLength(newLength)).to.not.be.rejected; - expect(await this.instance.length()).to.equal(newLength); - }); - }); - - describe('memory', function () { - const fragment = `$unsafeMemoryAccess(${type}[] arr, uint256 pos)`; - - for (const i in elements) { - it(`unsafeMemoryAccess within bounds #${i}`, async function () { - expect(await this.mock[fragment](elements, i)).to.equal(elements[i]); - }); - } - - it('unsafeMemoryAccess outside bounds', async function () { - await expect(this.mock[fragment](elements, elements.length)).to.not.be.rejected; - }); - - it('unsafeMemoryAccess loop around', async function () { - for (let i = 251n; i < 256n; ++i) { - expect(await this.mock[fragment](elements, 2n ** i - 1n)).to.equal(BigInt(elements.length)); - expect(await this.mock[fragment](elements, 2n ** i + 0n)).to.equal(elements[0]); - expect(await this.mock[fragment](elements, 2n ** i + 1n)).to.equal(elements[1]); - } - }); - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/utils/Base64.t.sol b/lib_openzeppelin_contracts/test/utils/Base64.t.sol deleted file mode 100644 index d5752ae..0000000 --- a/lib_openzeppelin_contracts/test/utils/Base64.t.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; -import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; - -contract Base64Test is Test { - function testEncode(bytes memory input) external { - assertEq(Base64.encode(input), vm.toBase64(input)); - } - - function testEncodeURL(bytes memory input) external { - assertEq(Base64.encodeURL(input), _removePadding(vm.toBase64URL(input))); - } - - function _removePadding(string memory inputStr) internal pure returns (string memory) { - bytes memory input = bytes(inputStr); - bytes memory output; - - for (uint256 i = 0; i < input.length; ++i) { - if (input[input.length - i - 1] != 0x3d) { - output = new bytes(input.length - i); - break; - } - } - - for (uint256 i = 0; i < output.length; ++i) { - output[i] = input[i]; - } - - return string(output); - } -} diff --git a/lib_openzeppelin_contracts/test/utils/Base64.test.js b/lib_openzeppelin_contracts/test/utils/Base64.test.js deleted file mode 100644 index 5c42746..0000000 --- a/lib_openzeppelin_contracts/test/utils/Base64.test.js +++ /dev/null @@ -1,59 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -// Replace "+/" with "-_" in the char table, and remove the padding -// see https://datatracker.ietf.org/doc/html/rfc4648#section-5 -const base64toBase64Url = str => str.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', ''); - -async function fixture() { - const mock = await ethers.deployContract('$Base64'); - return { mock }; -} - -describe('Strings', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('base64', function () { - for (const { title, input, expected } of [ - { title: 'converts to base64 encoded string with double padding', input: 'test', expected: 'dGVzdA==' }, - { title: 'converts to base64 encoded string with single padding', input: 'test1', expected: 'dGVzdDE=' }, - { title: 'converts to base64 encoded string without padding', input: 'test12', expected: 'dGVzdDEy' }, - { title: 'converts to base64 encoded string (/ case)', input: 'où', expected: 'b/k=' }, - { title: 'converts to base64 encoded string (+ case)', input: 'zs~1t8', expected: 'enN+MXQ4' }, - { title: 'empty bytes', input: '', expected: '' }, - ]) - it(title, async function () { - const buffer = Buffer.from(input, 'ascii'); - expect(await this.mock.$encode(buffer)).to.equal(ethers.encodeBase64(buffer)); - expect(await this.mock.$encode(buffer)).to.equal(expected); - }); - }); - - describe('base64url', function () { - for (const { title, input, expected } of [ - { title: 'converts to base64url encoded string with double padding', input: 'test', expected: 'dGVzdA' }, - { title: 'converts to base64url encoded string with single padding', input: 'test1', expected: 'dGVzdDE' }, - { title: 'converts to base64url encoded string without padding', input: 'test12', expected: 'dGVzdDEy' }, - { title: 'converts to base64url encoded string (_ case)', input: 'où', expected: 'b_k' }, - { title: 'converts to base64url encoded string (- case)', input: 'zs~1t8', expected: 'enN-MXQ4' }, - { title: 'empty bytes', input: '', expected: '' }, - ]) - it(title, async function () { - const buffer = Buffer.from(input, 'ascii'); - expect(await this.mock.$encodeURL(buffer)).to.equal(base64toBase64Url(ethers.encodeBase64(buffer))); - expect(await this.mock.$encodeURL(buffer)).to.equal(expected); - }); - }); - - it('Encode reads beyond the input buffer into dirty memory', async function () { - const mock = await ethers.deployContract('Base64Dirty'); - const buffer32 = ethers.id('example'); - const buffer31 = buffer32.slice(0, -2); - - expect(await mock.encode(buffer31)).to.equal(ethers.encodeBase64(buffer31)); - expect(await mock.encode(buffer32)).to.equal(ethers.encodeBase64(buffer32)); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/Context.behavior.js b/lib_openzeppelin_contracts/test/utils/Context.behavior.js deleted file mode 100644 index adb140f..0000000 --- a/lib_openzeppelin_contracts/test/utils/Context.behavior.js +++ /dev/null @@ -1,48 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - return { contextHelper: await ethers.deployContract('ContextMockCaller', []) }; -} -function shouldBehaveLikeRegularContext() { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('msgSender', function () { - it('returns the transaction sender when called from an EOA', async function () { - await expect(this.context.connect(this.sender).msgSender()).to.emit(this.context, 'Sender').withArgs(this.sender); - }); - - it('returns the transaction sender when called from another contract', async function () { - await expect(this.contextHelper.connect(this.sender).callSender(this.context)) - .to.emit(this.context, 'Sender') - .withArgs(this.contextHelper); - }); - }); - - describe('msgData', function () { - const args = [42n, 'OpenZeppelin']; - - it('returns the transaction data when called from an EOA', async function () { - const callData = this.context.interface.encodeFunctionData('msgData', args); - - await expect(this.context.msgData(...args)) - .to.emit(this.context, 'Data') - .withArgs(callData, ...args); - }); - - it('returns the transaction sender when from another contract', async function () { - const callData = this.context.interface.encodeFunctionData('msgData', args); - - await expect(this.contextHelper.callData(this.context, ...args)) - .to.emit(this.context, 'Data') - .withArgs(callData, ...args); - }); - }); -} - -module.exports = { - shouldBehaveLikeRegularContext, -}; diff --git a/lib_openzeppelin_contracts/test/utils/Context.test.js b/lib_openzeppelin_contracts/test/utils/Context.test.js deleted file mode 100644 index b766729..0000000 --- a/lib_openzeppelin_contracts/test/utils/Context.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldBehaveLikeRegularContext } = require('./Context.behavior'); - -async function fixture() { - const [sender] = await ethers.getSigners(); - const context = await ethers.deployContract('ContextMock', []); - return { sender, context }; -} - -describe('Context', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldBehaveLikeRegularContext(); -}); diff --git a/lib_openzeppelin_contracts/test/utils/Create2.t.sol b/lib_openzeppelin_contracts/test/utils/Create2.t.sol deleted file mode 100644 index c6c0185..0000000 --- a/lib_openzeppelin_contracts/test/utils/Create2.t.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; - -contract Create2Test is Test { - function testComputeAddressSpillage(bytes32 salt, bytes32 bytecodeHash, address deployer) public { - address predicted = Create2.computeAddress(salt, bytecodeHash, deployer); - bytes32 spillage; - /// @solidity memory-safe-assembly - assembly { - spillage := and(predicted, 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) - } - assertEq(spillage, bytes32(0)); - } -} diff --git a/lib_openzeppelin_contracts/test/utils/Create2.test.js b/lib_openzeppelin_contracts/test/utils/Create2.test.js deleted file mode 100644 index aba4dee..0000000 --- a/lib_openzeppelin_contracts/test/utils/Create2.test.js +++ /dev/null @@ -1,134 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [deployer, other] = await ethers.getSigners(); - - const factory = await ethers.deployContract('$Create2'); - - // Bytecode for deploying a contract that includes a constructor. - // 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])])); - - // 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([])])); - - return { deployer, other, factory, constructorByteCode, constructorLessBytecode }; -} - -describe('Create2', function () { - const salt = 'salt message'; - const saltHex = ethers.id(salt); - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('computeAddress', function () { - it('computes the correct contract address', async function () { - const onChainComputed = await this.factory.$computeAddress(saltHex, ethers.keccak256(this.constructorByteCode)); - const offChainComputed = ethers.getCreate2Address( - this.factory.target, - saltHex, - ethers.keccak256(this.constructorByteCode), - ); - expect(onChainComputed).to.equal(offChainComputed); - }); - - it('computes the correct contract address with deployer', async function () { - const onChainComputed = await this.factory.$computeAddress( - saltHex, - ethers.keccak256(this.constructorByteCode), - ethers.Typed.address(this.deployer), - ); - const offChainComputed = ethers.getCreate2Address( - this.deployer.address, - saltHex, - ethers.keccak256(this.constructorByteCode), - ); - expect(onChainComputed).to.equal(offChainComputed); - }); - }); - - describe('deploy', function () { - it('deploys a contract without constructor', async function () { - const offChainComputed = ethers.getCreate2Address( - this.factory.target, - saltHex, - ethers.keccak256(this.constructorLessBytecode), - ); - - await expect(this.factory.$deploy(0n, saltHex, this.constructorLessBytecode)) - .to.emit(this.factory, 'return$deploy') - .withArgs(offChainComputed); - - expect(this.constructorLessBytecode).to.include((await ethers.provider.getCode(offChainComputed)).slice(2)); - }); - - it('deploys a contract with constructor arguments', async function () { - const offChainComputed = ethers.getCreate2Address( - this.factory.target, - saltHex, - ethers.keccak256(this.constructorByteCode), - ); - - await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)) - .to.emit(this.factory, 'return$deploy') - .withArgs(offChainComputed); - - const instance = await ethers.getContractAt('VestingWallet', offChainComputed); - - expect(await instance.owner()).to.equal(this.other); - }); - - it('deploys a contract with funds deposited in the factory', async function () { - const value = 10n; - - await this.deployer.sendTransaction({ to: this.factory, value }); - - const offChainComputed = ethers.getCreate2Address( - this.factory.target, - saltHex, - ethers.keccak256(this.constructorByteCode), - ); - - expect(await ethers.provider.getBalance(this.factory)).to.equal(value); - expect(await ethers.provider.getBalance(offChainComputed)).to.equal(0n); - - await expect(this.factory.$deploy(value, saltHex, this.constructorByteCode)) - .to.emit(this.factory, 'return$deploy') - .withArgs(offChainComputed); - - expect(await ethers.provider.getBalance(this.factory)).to.equal(0n); - expect(await ethers.provider.getBalance(offChainComputed)).to.equal(value); - }); - - it('fails deploying a contract in an existent address', async function () { - await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.emit(this.factory, 'return$deploy'); - - await expect(this.factory.$deploy(0n, saltHex, this.constructorByteCode)).to.be.revertedWithCustomError( - this.factory, - 'FailedDeployment', - ); - }); - - it('fails deploying a contract if the bytecode length is zero', async function () { - await expect(this.factory.$deploy(0n, saltHex, '0x')).to.be.revertedWithCustomError( - this.factory, - 'Create2EmptyBytecode', - ); - }); - - it('fails deploying a contract if factory contract does not have sufficient balance', async function () { - await expect(this.factory.$deploy(1n, saltHex, this.constructorByteCode)) - .to.be.revertedWithCustomError(this.factory, 'InsufficientBalance') - .withArgs(0n, 1n); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/Multicall.test.js b/lib_openzeppelin_contracts/test/utils/Multicall.test.js deleted file mode 100644 index 9c84e44..0000000 --- a/lib_openzeppelin_contracts/test/utils/Multicall.test.js +++ /dev/null @@ -1,72 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [holder, alice, bruce] = await ethers.getSigners(); - - const amount = 12_000n; - const helper = await ethers.deployContract('MulticallHelper'); - const mock = await ethers.deployContract('$ERC20MulticallMock', ['name', 'symbol']); - await mock.$_mint(holder, amount); - - return { holder, alice, bruce, amount, mock, helper }; -} - -describe('Multicall', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('batches function calls', async function () { - expect(await this.mock.balanceOf(this.alice)).to.equal(0n); - expect(await this.mock.balanceOf(this.bruce)).to.equal(0n); - - await expect( - this.mock.multicall([ - this.mock.interface.encodeFunctionData('transfer', [this.alice.address, this.amount / 2n]), - this.mock.interface.encodeFunctionData('transfer', [this.bruce.address, this.amount / 3n]), - ]), - ) - .to.emit(this.mock, 'Transfer') - .withArgs(this.holder, this.alice, this.amount / 2n) - .to.emit(this.mock, 'Transfer') - .withArgs(this.holder, this.bruce, this.amount / 3n); - - expect(await this.mock.balanceOf(this.alice)).to.equal(this.amount / 2n); - expect(await this.mock.balanceOf(this.bruce)).to.equal(this.amount / 3n); - }); - - it('returns an array with the result of each call', async function () { - await this.mock.transfer(this.helper, this.amount); - expect(await this.mock.balanceOf(this.helper)).to.equal(this.amount); - - await this.helper.checkReturnValues(this.mock, [this.alice, this.bruce], [this.amount / 2n, this.amount / 3n]); - }); - - it('reverts previous calls', async function () { - expect(await this.mock.balanceOf(this.alice)).to.equal(0n); - - await expect( - this.mock.multicall([ - this.mock.interface.encodeFunctionData('transfer', [this.alice.address, this.amount]), - this.mock.interface.encodeFunctionData('transfer', [this.bruce.address, this.amount]), - ]), - ) - .to.be.revertedWithCustomError(this.mock, 'ERC20InsufficientBalance') - .withArgs(this.holder, 0, this.amount); - - expect(await this.mock.balanceOf(this.alice)).to.equal(0n); - }); - - it('bubbles up revert reasons', async function () { - await expect( - this.mock.multicall([ - this.mock.interface.encodeFunctionData('transfer', [this.alice.address, this.amount]), - this.mock.interface.encodeFunctionData('transfer', [this.bruce.address, this.amount]), - ]), - ) - .to.be.revertedWithCustomError(this.mock, 'ERC20InsufficientBalance') - .withArgs(this.holder, 0, this.amount); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/Nonces.test.js b/lib_openzeppelin_contracts/test/utils/Nonces.test.js deleted file mode 100644 index 2cb4798..0000000 --- a/lib_openzeppelin_contracts/test/utils/Nonces.test.js +++ /dev/null @@ -1,75 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [sender, other] = await ethers.getSigners(); - - const mock = await ethers.deployContract('$Nonces'); - - return { sender, other, mock }; -} - -describe('Nonces', function () { - beforeEach(async 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); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/Packing.t.sol b/lib_openzeppelin_contracts/test/utils/Packing.t.sol deleted file mode 100644 index 892807f..0000000 --- a/lib_openzeppelin_contracts/test/utils/Packing.t.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; -import {Packing} from "@openzeppelin/contracts/utils/Packing.sol"; - -contract PackingTest is Test { - using Packing for *; - - // Pack a pair of arbitrary uint128, and check that split recovers the correct values - function testUint128x2(uint128 first, uint128 second) external { - Packing.Uint128x2 packed = Packing.pack(first, second); - assertEq(packed.first(), first); - assertEq(packed.second(), second); - - (uint128 recoveredFirst, uint128 recoveredSecond) = packed.split(); - assertEq(recoveredFirst, first); - assertEq(recoveredSecond, second); - } - - // split an arbitrary bytes32 into a pair of uint128, and check that repack matches the input - function testUint128x2(bytes32 input) external { - (uint128 first, uint128 second) = input.asUint128x2().split(); - assertEq(Packing.pack(first, second).asBytes32(), input); - } -} diff --git a/lib_openzeppelin_contracts/test/utils/Packing.test.js b/lib_openzeppelin_contracts/test/utils/Packing.test.js deleted file mode 100644 index 8fb9095..0000000 --- a/lib_openzeppelin_contracts/test/utils/Packing.test.js +++ /dev/null @@ -1,27 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { generators } = require('../helpers/random'); - -async function fixture() { - return { mock: await ethers.deployContract('$Packing') }; -} - -describe('Packing', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('Uint128x2', async function () { - const first = generators.uint256() % 2n ** 128n; - const second = generators.uint256() % 2n ** 128n; - const packed = ethers.hexlify(ethers.toBeArray((first << 128n) | second)); - - expect(await this.mock.$asUint128x2(packed)).to.equal(packed); - expect(await this.mock.$asBytes32(packed)).to.equal(packed); - expect(await this.mock.$pack(first, second)).to.equal(packed); - expect(await this.mock.$split(packed)).to.deep.equal([first, second]); - expect(await this.mock.$first(packed)).to.equal(first); - expect(await this.mock.$second(packed)).to.equal(second); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/Panic.test.js b/lib_openzeppelin_contracts/test/utils/Panic.test.js deleted file mode 100644 index 49673c7..0000000 --- a/lib_openzeppelin_contracts/test/utils/Panic.test.js +++ /dev/null @@ -1,37 +0,0 @@ -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() { - return { mock: await ethers.deployContract('$Panic') }; -} - -describe('Panic', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - for (const [name, code] of Object.entries({ - GENERIC: 0x0, - ASSERT: PANIC_CODES.ASSERTION_ERROR, - UNDER_OVERFLOW: PANIC_CODES.ARITHMETIC_OVERFLOW, - DIVISION_BY_ZERO: PANIC_CODES.DIVISION_BY_ZERO, - ENUM_CONVERSION_ERROR: PANIC_CODES.ENUM_CONVERSION_OUT_OF_BOUNDS, - STORAGE_ENCODING_ERROR: PANIC_CODES.INCORRECTLY_ENCODED_STORAGE_BYTE_ARRAY, - EMPTY_ARRAY_POP: PANIC_CODES.POP_ON_EMPTY_ARRAY, - ARRAY_OUT_OF_BOUNDS: PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS, - RESOURCE_ERROR: PANIC_CODES.TOO_MUCH_MEMORY_ALLOCATED, - INVALID_INTERNAL_FUNCTION: PANIC_CODES.ZERO_INITIALIZED_VARIABLE, - })) { - describe(`${name} (${ethers.toBeHex(code)})`, function () { - it('exposes panic code as constant', async function () { - expect(await this.mock.getFunction(`$${name}`)()).to.equal(code); - }); - - it('reverts with panic when called', async function () { - await expect(this.mock.$panic(code)).to.be.revertedWithPanic(code); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/utils/Pausable.test.js b/lib_openzeppelin_contracts/test/utils/Pausable.test.js deleted file mode 100644 index 67d74a0..0000000 --- a/lib_openzeppelin_contracts/test/utils/Pausable.test.js +++ /dev/null @@ -1,90 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const [pauser] = await ethers.getSigners(); - - const mock = await ethers.deployContract('PausableMock'); - - return { pauser, mock }; -} - -describe('Pausable', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('when unpaused', function () { - beforeEach(async function () { - expect(await this.mock.paused()).to.be.false; - }); - - it('can perform normal process in non-pause', async function () { - expect(await this.mock.count()).to.equal(0n); - - await this.mock.normalProcess(); - expect(await this.mock.count()).to.equal(1n); - }); - - it('cannot take drastic measure in non-pause', async function () { - await expect(this.mock.drasticMeasure()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause'); - - expect(await this.mock.drasticMeasureTaken()).to.be.false; - }); - - describe('when paused', function () { - beforeEach(async function () { - this.tx = await this.mock.pause(); - }); - - it('emits a Paused event', async function () { - await expect(this.tx).to.emit(this.mock, 'Paused').withArgs(this.pauser); - }); - - it('cannot perform normal process in pause', async function () { - await expect(this.mock.normalProcess()).to.be.revertedWithCustomError(this.mock, 'EnforcedPause'); - }); - - it('can take a drastic measure in a pause', async function () { - await this.mock.drasticMeasure(); - expect(await this.mock.drasticMeasureTaken()).to.be.true; - }); - - it('reverts when re-pausing', async function () { - await expect(this.mock.pause()).to.be.revertedWithCustomError(this.mock, 'EnforcedPause'); - }); - - describe('unpausing', function () { - it('is unpausable by the pauser', async function () { - await this.mock.unpause(); - expect(await this.mock.paused()).to.be.false; - }); - - describe('when unpaused', function () { - beforeEach(async function () { - this.tx = await this.mock.unpause(); - }); - - it('emits an Unpaused event', async function () { - await expect(this.tx).to.emit(this.mock, 'Unpaused').withArgs(this.pauser); - }); - - it('should resume allowing normal process', async function () { - expect(await this.mock.count()).to.equal(0n); - await this.mock.normalProcess(); - expect(await this.mock.count()).to.equal(1n); - }); - - it('should prevent drastic measure', async function () { - await expect(this.mock.drasticMeasure()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause'); - }); - - it('reverts when re-unpausing', async function () { - await expect(this.mock.unpause()).to.be.revertedWithCustomError(this.mock, 'ExpectedPause'); - }); - }); - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/ReentrancyGuard.test.js b/lib_openzeppelin_contracts/test/utils/ReentrancyGuard.test.js deleted file mode 100644 index c441856..0000000 --- a/lib_openzeppelin_contracts/test/utils/ReentrancyGuard.test.js +++ /dev/null @@ -1,50 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -for (const variant of ['', 'Transient']) { - describe(`Reentrancy${variant}Guard`, function () { - async function fixture() { - const name = `Reentrancy${variant}Mock`; - const mock = await ethers.deployContract(name); - return { name, mock }; - } - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('nonReentrant function can be called', async function () { - expect(await this.mock.counter()).to.equal(0n); - await this.mock.callback(); - expect(await this.mock.counter()).to.equal(1n); - }); - - it('does not allow remote callback', async function () { - const attacker = await ethers.deployContract('ReentrancyAttack'); - await expect(this.mock.countAndCall(attacker)).to.be.revertedWith('ReentrancyAttack: failed call'); - }); - - it('_reentrancyGuardEntered should be true when guarded', async function () { - await this.mock.guardedCheckEntered(); - }); - - it('_reentrancyGuardEntered should be false when unguarded', async function () { - await this.mock.unguardedCheckNotEntered(); - }); - - // The following are more side-effects than intended behavior: - // I put them here as documentation, and to monitor any changes - // in the side-effects. - it('does not allow local recursion', async function () { - await expect(this.mock.countLocalRecursive(10n)).to.be.revertedWithCustomError( - this.mock, - 'ReentrancyGuardReentrantCall', - ); - }); - - it('does not allow indirect local recursion', async function () { - await expect(this.mock.countThisRecursive(10n)).to.be.revertedWith(`${this.name}: failed call`); - }); - }); -} diff --git a/lib_openzeppelin_contracts/test/utils/ShortStrings.t.sol b/lib_openzeppelin_contracts/test/utils/ShortStrings.t.sol deleted file mode 100644 index a18fad3..0000000 --- a/lib_openzeppelin_contracts/test/utils/ShortStrings.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; - -import {ShortStrings, ShortString} from "@openzeppelin/contracts/utils/ShortStrings.sol"; - -contract ShortStringsTest is Test { - string _fallback; - - function testRoundtripShort(string memory input) external { - vm.assume(_isShort(input)); - ShortString short = ShortStrings.toShortString(input); - string memory output = ShortStrings.toString(short); - assertEq(input, output); - } - - function testRoundtripWithFallback(string memory input, string memory fallbackInitial) external { - _fallback = fallbackInitial; // Make sure that the initial value has no effect - ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback); - string memory output = ShortStrings.toStringWithFallback(short, _fallback); - assertEq(input, output); - } - - function testRevertLong(string memory input) external { - vm.assume(!_isShort(input)); - vm.expectRevert(abi.encodeWithSelector(ShortStrings.StringTooLong.selector, input)); - this.toShortString(input); - } - - function testLengthShort(string memory input) external { - vm.assume(_isShort(input)); - uint256 inputLength = bytes(input).length; - ShortString short = ShortStrings.toShortString(input); - uint256 shortLength = ShortStrings.byteLength(short); - assertEq(inputLength, shortLength); - } - - function testLengthWithFallback(string memory input, string memory fallbackInitial) external { - _fallback = fallbackInitial; - uint256 inputLength = bytes(input).length; - ShortString short = ShortStrings.toShortStringWithFallback(input, _fallback); - uint256 shortLength = ShortStrings.byteLengthWithFallback(short, _fallback); - assertEq(inputLength, shortLength); - } - - function toShortString(string memory input) external pure returns (ShortString) { - return ShortStrings.toShortString(input); - } - - function _isShort(string memory input) internal pure returns (bool) { - return bytes(input).length < 32; - } -} diff --git a/lib_openzeppelin_contracts/test/utils/ShortStrings.test.js b/lib_openzeppelin_contracts/test/utils/ShortStrings.test.js deleted file mode 100644 index cb1a06a..0000000 --- a/lib_openzeppelin_contracts/test/utils/ShortStrings.test.js +++ /dev/null @@ -1,64 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const FALLBACK_SENTINEL = ethers.zeroPadValue('0xFF', 32); - -const length = sstr => parseInt(sstr.slice(64), 16); -const decode = sstr => ethers.toUtf8String(sstr).slice(0, length(sstr)); -const encode = str => - str.length < 32 - ? ethers.concat([ - ethers.encodeBytes32String(str).slice(0, -2), - ethers.zeroPadValue(ethers.toBeArray(str.length), 1), - ]) - : FALLBACK_SENTINEL; - -async function fixture() { - const mock = await ethers.deployContract('$ShortStrings'); - return { mock }; -} - -describe('ShortStrings', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - for (const str of [0, 1, 16, 31, 32, 64, 1024].map(length => 'a'.repeat(length))) { - describe(`with string length ${str.length}`, function () { - it('encode / decode', async function () { - if (str.length < 32) { - const encoded = await this.mock.$toShortString(str); - expect(encoded).to.equal(encode(str)); - expect(decode(encoded)).to.equal(str); - - expect(await this.mock.$byteLength(encoded)).to.equal(str.length); - expect(await this.mock.$toString(encoded)).to.equal(str); - } else { - await expect(this.mock.$toShortString(str)) - .to.be.revertedWithCustomError(this.mock, 'StringTooLong') - .withArgs(str); - } - }); - - it('set / get with fallback', async function () { - const short = await this.mock - .$toShortStringWithFallback(str, 0) - .then(tx => tx.wait()) - .then(receipt => receipt.logs.find(ev => ev.fragment.name == 'return$toShortStringWithFallback').args[0]); - - expect(short).to.equal(encode(str)); - - const promise = this.mock.$toString(short); - if (str.length < 32) { - expect(await promise).to.equal(str); - } else { - await expect(promise).to.be.revertedWithCustomError(this.mock, 'InvalidShortString'); - } - - expect(await this.mock.$byteLengthWithFallback(short, 0)).to.equal(str.length); - expect(await this.mock.$toStringWithFallback(short, 0)).to.equal(str); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/utils/SlotDerivation.t.sol b/lib_openzeppelin_contracts/test/utils/SlotDerivation.t.sol deleted file mode 100644 index 20b3341..0000000 --- a/lib_openzeppelin_contracts/test/utils/SlotDerivation.t.sol +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-License-Identifier: MIT -// This file was procedurally generated from scripts/generate/templates/SlotDerivation.t.js. - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; - -import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; - -contract SlotDerivationTest is Test { - using SlotDerivation for bytes32; - - bytes[] private _array; - - function testDeriveArray(uint256 length, uint256 offset) public { - length = bound(length, 1, type(uint256).max); - offset = bound(offset, 0, length - 1); - - bytes32 baseSlot; - assembly { - baseSlot := _array.slot - sstore(baseSlot, length) // store length so solidity access does not revert - } - - bytes storage derived = _array[offset]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveArray().offset(offset), derivedSlot); - } - - mapping(address => bytes) private _addressMapping; - - function testDeriveMappingAddress(address key) public { - bytes32 baseSlot; - assembly { - baseSlot := _addressMapping.slot - } - - bytes storage derived = _addressMapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(bool => bytes) private _boolMapping; - - function testDeriveMappingBoolean(bool key) public { - bytes32 baseSlot; - assembly { - baseSlot := _boolMapping.slot - } - - bytes storage derived = _boolMapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(bytes32 => bytes) private _bytes32Mapping; - - function testDeriveMappingBytes32(bytes32 key) public { - bytes32 baseSlot; - assembly { - baseSlot := _bytes32Mapping.slot - } - - bytes storage derived = _bytes32Mapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(bytes4 => bytes) private _bytes4Mapping; - - function testDeriveMappingBytes4(bytes4 key) public { - bytes32 baseSlot; - assembly { - baseSlot := _bytes4Mapping.slot - } - - bytes storage derived = _bytes4Mapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(uint256 => bytes) private _uint256Mapping; - - function testDeriveMappingUint256(uint256 key) public { - bytes32 baseSlot; - assembly { - baseSlot := _uint256Mapping.slot - } - - bytes storage derived = _uint256Mapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(uint32 => bytes) private _uint32Mapping; - - function testDeriveMappingUint32(uint32 key) public { - bytes32 baseSlot; - assembly { - baseSlot := _uint32Mapping.slot - } - - bytes storage derived = _uint32Mapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(int256 => bytes) private _int256Mapping; - - function testDeriveMappingInt256(int256 key) public { - bytes32 baseSlot; - assembly { - baseSlot := _int256Mapping.slot - } - - bytes storage derived = _int256Mapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(int32 => bytes) private _int32Mapping; - - function testDeriveMappingInt32(int32 key) public { - bytes32 baseSlot; - assembly { - baseSlot := _int32Mapping.slot - } - - bytes storage derived = _int32Mapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(string => bytes) private _stringMapping; - - function testDeriveMappingString(string memory key) public { - bytes32 baseSlot; - assembly { - baseSlot := _stringMapping.slot - } - - bytes storage derived = _stringMapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } - - mapping(bytes => bytes) private _bytesMapping; - - function testDeriveMappingBytes(bytes memory key) public { - bytes32 baseSlot; - assembly { - baseSlot := _bytesMapping.slot - } - - bytes storage derived = _bytesMapping[key]; - bytes32 derivedSlot; - assembly { - derivedSlot := derived.slot - } - - assertEq(baseSlot.deriveMapping(key), derivedSlot); - } -} diff --git a/lib_openzeppelin_contracts/test/utils/SlotDerivation.test.js b/lib_openzeppelin_contracts/test/utils/SlotDerivation.test.js deleted file mode 100644 index 22582b3..0000000 --- a/lib_openzeppelin_contracts/test/utils/SlotDerivation.test.js +++ /dev/null @@ -1,58 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { erc7201Slot } = require('../helpers/storage'); -const { generators } = require('../helpers/random'); - -async function fixture() { - const [account] = await ethers.getSigners(); - const mock = await ethers.deployContract('$SlotDerivation'); - return { mock, account }; -} - -describe('SlotDerivation', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('namespaces', function () { - const namespace = 'example.main'; - - it('erc-7201', async function () { - expect(await this.mock.$erc7201Slot(namespace)).to.equal(erc7201Slot(namespace)); - }); - }); - - describe('derivation', function () { - it('offset', async function () { - const base = generators.bytes32(); - const offset = generators.uint256(); - expect(await this.mock.$offset(base, offset)).to.equal((ethers.toBigInt(base) + offset) & ethers.MaxUint256); - }); - - it('array', async function () { - const base = generators.bytes32(); - expect(await this.mock.$deriveArray(base)).to.equal(ethers.keccak256(base)); - }); - - describe('mapping', function () { - for (const { type, key, isValueType } of [ - { type: 'bool', key: true, isValueType: true }, - { type: 'address', key: generators.address(), isValueType: true }, - { type: 'bytes32', key: generators.bytes32(), isValueType: true }, - { type: 'uint256', key: generators.uint256(), isValueType: true }, - { type: 'int256', key: generators.int256(), isValueType: true }, - { type: 'bytes', key: generators.hexBytes(128), isValueType: false }, - { type: 'string', key: 'lorem ipsum', isValueType: false }, - ]) { - it(type, async function () { - const base = generators.bytes32(); - const expected = isValueType - ? ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode([type, 'bytes32'], [key, base])) - : ethers.solidityPackedKeccak256([type, 'bytes32'], [key, base]); - expect(await this.mock[`$deriveMapping(bytes32,${type})`](base, key)).to.equal(expected); - }); - } - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/StorageSlot.test.js b/lib_openzeppelin_contracts/test/utils/StorageSlot.test.js deleted file mode 100644 index 35e83e2..0000000 --- a/lib_openzeppelin_contracts/test/utils/StorageSlot.test.js +++ /dev/null @@ -1,106 +0,0 @@ -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'); - -const TYPES = [ - { name: 'Boolean', type: 'bool', value: true, isValueType: true, zero: false }, - { name: 'Address', type: 'address', value: generators.address(), isValueType: true, zero: generators.address.zero }, - { name: 'Bytes32', type: 'bytes32', value: generators.bytes32(), isValueType: true, zero: generators.bytes32.zero }, - { name: 'Uint256', type: 'uint256', value: generators.uint256(), isValueType: true, zero: generators.uint256.zero }, - { name: 'Int256', type: 'int256', value: generators.int256(), isValueType: true, zero: generators.int256.zero }, - { name: 'Bytes', type: 'bytes', value: generators.hexBytes(128), isValueType: false, zero: generators.hexBytes.zero }, - { name: 'String', type: 'string', value: 'lorem ipsum', isValueType: false, zero: '' }, -]; - -async function fixture() { - return { mock: await ethers.deployContract('StorageSlotMock') }; -} - -describe('StorageSlot', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - for (const { name, type, value, zero } of TYPES) { - describe(`${type} storage slot`, function () { - it('set', async function () { - await this.mock.getFunction(`set${name}Slot`)(slot, value); - }); - - describe('get', function () { - beforeEach(async function () { - await this.mock.getFunction(`set${name}Slot`)(slot, value); - }); - - it('from right slot', async function () { - expect(await this.mock.getFunction(`get${name}Slot`)(slot)).to.equal(value); - }); - - it('from other slot', async function () { - expect(await this.mock.getFunction(`get${name}Slot`)(otherSlot)).to.equal(zero); - }); - }); - }); - } - - for (const { name, type, value, zero } of TYPES.filter(type => !type.isValueType)) { - describe(`${type} storage pointer`, function () { - it('set', async function () { - await this.mock.getFunction(`set${name}Storage`)(slot, value); - }); - - describe('get', function () { - beforeEach(async function () { - await this.mock.getFunction(`set${name}Storage`)(slot, value); - }); - - it('from right slot', async function () { - expect(await this.mock.getFunction(`${type}Map`)(slot)).to.equal(value); - expect(await this.mock.getFunction(`get${name}Storage`)(slot)).to.equal(value); - }); - - it('from other slot', async function () { - expect(await this.mock.getFunction(`${type}Map`)(otherSlot)).to.equal(zero); - expect(await this.mock.getFunction(`get${name}Storage`)(otherSlot)).to.equal(zero); - }); - }); - }); - } - - 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/lib_openzeppelin_contracts/test/utils/Strings.test.js b/lib_openzeppelin_contracts/test/utils/Strings.test.js deleted file mode 100644 index 643172b..0000000 --- a/lib_openzeppelin_contracts/test/utils/Strings.test.js +++ /dev/null @@ -1,153 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const mock = await ethers.deployContract('$Strings'); - return { mock }; -} - -describe('Strings', function () { - before(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('toString', function () { - const values = [ - 0n, - 7n, - 10n, - 99n, - 100n, - 101n, - 123n, - 4132n, - 12345n, - 1234567n, - 1234567890n, - 123456789012345n, - 12345678901234567890n, - 123456789012345678901234567890n, - 1234567890123456789012345678901234567890n, - 12345678901234567890123456789012345678901234567890n, - 123456789012345678901234567890123456789012345678901234567890n, - 1234567890123456789012345678901234567890123456789012345678901234567890n, - ]; - - describe('uint256', function () { - it('converts MAX_UINT256', async function () { - const value = ethers.MaxUint256; - expect(await this.mock.$toString(value)).to.equal(value.toString(10)); - }); - - for (const value of values) { - it(`converts ${value}`, async function () { - expect(await this.mock.$toString(value)).to.equal(value); - }); - } - }); - - describe('int256', function () { - it('converts MAX_INT256', async function () { - const value = ethers.MaxInt256; - expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); - }); - - it('converts MIN_INT256', async function () { - const value = ethers.MinInt256; - expect(await this.mock.$toStringSigned(value)).to.equal(value.toString(10)); - }); - - for (const value of values) { - it(`convert ${value}`, async function () { - expect(await this.mock.$toStringSigned(value)).to.equal(value); - }); - - it(`convert negative ${value}`, async function () { - const negated = -value; - expect(await this.mock.$toStringSigned(negated)).to.equal(negated.toString(10)); - }); - } - }); - }); - - describe('toHexString', function () { - it('converts 0', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0n)).to.equal('0x00'); - }); - - it('converts a positive number', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(0x4132n)).to.equal('0x4132'); - }); - - it('converts MAX_UINT256', async function () { - expect(await this.mock.getFunction('$toHexString(uint256)')(ethers.MaxUint256)).to.equal( - `0x${ethers.MaxUint256.toString(16)}`, - ); - }); - }); - - describe('toHexString fixed', function () { - it('converts a positive number (long)', async function () { - expect(await this.mock.getFunction('$toHexString(uint256,uint256)')(0x4132n, 32n)).to.equal( - '0x0000000000000000000000000000000000000000000000000000000000004132', - ); - }); - - 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`) - .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)}`, - ); - }); - }); - - describe('toHexString address', function () { - it('converts a random address', async function () { - const addr = '0xa9036907dccae6a1e0033479b12e837e5cf5a02f'; - expect(await this.mock.getFunction('$toHexString(address)')(addr)).to.equal(addr); - }); - - it('converts an address with leading zeros', async function () { - const addr = '0x0000e0ca771e21bd00057f54a68c30d400000000'; - expect(await this.mock.getFunction('$toHexString(address)')(addr)).to.equal(addr); - }); - }); - - describe('equal', function () { - it('compares two empty strings', async function () { - expect(await this.mock.$equal('', '')).to.be.true; - }); - - it('compares two equal strings', async function () { - expect(await this.mock.$equal('a', 'a')).to.be.true; - }); - - it('compares two different strings', async function () { - expect(await this.mock.$equal('a', 'b')).to.be.false; - }); - - it('compares two different strings of different lengths', async function () { - expect(await this.mock.$equal('a', 'aa')).to.be.false; - expect(await this.mock.$equal('aa', 'a')).to.be.false; - }); - - it('compares two different large strings', async function () { - const str1 = 'a'.repeat(201); - const str2 = 'a'.repeat(200) + 'b'; - expect(await this.mock.$equal(str1, str2)).to.be.false; - }); - - it('compares two equal large strings', async function () { - const str1 = 'a'.repeat(201); - const str2 = 'a'.repeat(201); - expect(await this.mock.$equal(str1, str2)).to.be.true; - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/cryptography/ECDSA.test.js b/lib_openzeppelin_contracts/test/utils/cryptography/ECDSA.test.js deleted file mode 100644 index 6b24bdb..0000000 --- a/lib_openzeppelin_contracts/test/utils/cryptography/ECDSA.test.js +++ /dev/null @@ -1,213 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const TEST_MESSAGE = ethers.id('OpenZeppelin'); -const WRONG_MESSAGE = ethers.id('Nope'); -const NON_HASH_MESSAGE = '0xabcd'; - -async function fixture() { - const [signer] = await ethers.getSigners(); - const mock = await ethers.deployContract('$ECDSA'); - return { signer, mock }; -} - -describe('ECDSA', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('recover with invalid signature', function () { - it('with short signature', async function () { - await expect(this.mock.$recover(TEST_MESSAGE, '0x1234')) - .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength') - .withArgs(2); - }); - - it('with long signature', async function () { - await expect( - // eslint-disable-next-line max-len - this.mock.$recover( - TEST_MESSAGE, - '0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', - ), - ) - .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength') - .withArgs(85); - }); - }); - - describe('recover with valid signature', function () { - describe('using .sign', function () { - it('returns signer address with correct signature', async function () { - // Create the signature - const signature = await this.signer.signMessage(TEST_MESSAGE); - - // Recover the signer address from the generated message and signature. - expect(await this.mock.$recover(ethers.hashMessage(TEST_MESSAGE), signature)).to.equal(this.signer); - }); - - it('returns signer address with correct signature for arbitrary length message', async function () { - // Create the signature - const signature = await this.signer.signMessage(NON_HASH_MESSAGE); - - // Recover the signer address from the generated message and signature. - expect(await this.mock.$recover(ethers.hashMessage(NON_HASH_MESSAGE), signature)).to.equal(this.signer); - }); - - it('returns a different address', async function () { - const signature = await this.signer.signMessage(TEST_MESSAGE); - expect(await this.mock.$recover(WRONG_MESSAGE, signature)).to.not.be.equal(this.signer); - }); - - 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( - this.mock, - 'ECDSAInvalidSignature', - ); - }); - }); - - describe('with v=27 signature', function () { - const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c'; - // eslint-disable-next-line max-len - const signatureWithoutV = - '0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892'; - - it('works with correct v value', async function () { - const v = '0x1b'; // 27 = 1b. - const signature = ethers.concat([signatureWithoutV, v]); - expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer); - - const { r, s, yParityAndS: vs } = ethers.Signature.from(signature); - expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal( - signer, - ); - - expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.equal(signer); - }); - - it('rejects incorrect v value', async function () { - const v = '0x1c'; // 28 = 1c. - const signature = ethers.concat([signatureWithoutV, v]); - expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer); - - const { r, s, yParityAndS: vs } = ethers.Signature.from(signature); - expect( - await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s), - ).to.not.equal(signer); - - expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.not.equal( - signer, - ); - }); - - it('reverts wrong v values', async function () { - for (const v of ['0x00', '0x01']) { - const signature = ethers.concat([signatureWithoutV, v]); - await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError( - this.mock, - 'ECDSAInvalidSignature', - ); - - const { r, s } = ethers.Signature.from(signature); - await expect( - this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s), - ).to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignature'); - } - }); - - it('rejects short EIP2098 format', async function () { - const v = '0x1b'; // 27 = 1b. - const signature = ethers.concat([signatureWithoutV, v]); - - const { compactSerialized } = ethers.Signature.from(signature); - await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized)) - .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength') - .withArgs(64); - }); - }); - - describe('with v=28 signature', function () { - const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E'; - // eslint-disable-next-line max-len - const signatureWithoutV = - '0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0'; - - it('works with correct v value', async function () { - const v = '0x1c'; // 28 = 1c. - const signature = ethers.concat([signatureWithoutV, v]); - expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer); - - const { r, s, yParityAndS: vs } = ethers.Signature.from(signature); - expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal( - signer, - ); - - expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.equal(signer); - }); - - it('rejects incorrect v value', async function () { - const v = '0x1b'; // 27 = 1b. - const signature = ethers.concat([signatureWithoutV, v]); - expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer); - - const { r, s, yParityAndS: vs } = ethers.Signature.from(signature); - expect( - await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s), - ).to.not.equal(signer); - - expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.not.equal( - signer, - ); - }); - - it('reverts invalid v values', async function () { - for (const v of ['0x00', '0x01']) { - const signature = ethers.concat([signatureWithoutV, v]); - await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError( - this.mock, - 'ECDSAInvalidSignature', - ); - - const { r, s } = ethers.Signature.from(signature); - await expect( - this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s), - ).to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignature'); - } - }); - - it('rejects short EIP2098 format', async function () { - const v = '0x1b'; // 28 = 1b. - const signature = ethers.concat([signatureWithoutV, v]); - - const { compactSerialized } = ethers.Signature.from(signature); - await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized)) - .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength') - .withArgs(64); - }); - }); - - it('reverts with high-s value signature', async function () { - const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'; - // eslint-disable-next-line max-len - const highSSignature = - '0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b'; - - const r = ethers.dataSlice(highSSignature, 0, 32); - const s = ethers.dataSlice(highSSignature, 32, 64); - const v = ethers.dataSlice(highSSignature, 64, 65); - - await expect(this.mock.$recover(message, highSSignature)) - .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS') - .withArgs(s); - await expect(this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)) - .to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS') - .withArgs(s); - expect(() => ethers.Signature.from(highSSignature)).to.throw('non-canonical s'); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/cryptography/EIP712.test.js b/lib_openzeppelin_contracts/test/utils/cryptography/EIP712.test.js deleted file mode 100644 index 2b6e7fa..0000000 --- a/lib_openzeppelin_contracts/test/utils/cryptography/EIP712.test.js +++ /dev/null @@ -1,105 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { getDomain, domainSeparator, hashTypedData } = require('../../helpers/eip712'); -const { formatType } = require('../../helpers/eip712-types'); - -const LENGTHS = { - short: ['A Name', '1'], - long: ['A'.repeat(40), 'B'.repeat(40)], -}; - -const fixture = async () => { - const [from, to] = await ethers.getSigners(); - - const lengths = {}; - for (const [shortOrLong, [name, version]] of Object.entries(LENGTHS)) { - lengths[shortOrLong] = { name, version }; - lengths[shortOrLong].eip712 = await ethers.deployContract('$EIP712Verifier', [name, version]); - lengths[shortOrLong].domain = { - name, - version, - chainId: await ethers.provider.getNetwork().then(({ chainId }) => chainId), - verifyingContract: lengths[shortOrLong].eip712.target, - }; - } - - return { from, to, lengths }; -}; - -describe('EIP712', function () { - for (const [shortOrLong, [name, version]] of Object.entries(LENGTHS)) { - describe(`with ${shortOrLong} name and version`, function () { - beforeEach('deploying', async function () { - Object.assign(this, await loadFixture(fixture)); - Object.assign(this, this.lengths[shortOrLong]); - }); - - describe('domain separator', function () { - it('is internally available', async function () { - const expected = await domainSeparator(this.domain); - - expect(await this.eip712.$_domainSeparatorV4()).to.equal(expected); - }); - - it("can be rebuilt using EIP-5267's eip712Domain", async function () { - const rebuildDomain = await getDomain(this.eip712); - expect(rebuildDomain).to.be.deep.equal(this.domain); - }); - - if (shortOrLong === 'short') { - // Long strings are in storage, and the proxy will not be properly initialized unless - // the upgradeable contract variant is used and the initializer is invoked. - - it('adjusts when behind proxy', async function () { - const factory = await ethers.deployContract('$Clones'); - - const clone = await factory - .$clone(this.eip712) - .then(tx => tx.wait()) - .then(receipt => receipt.logs.find(ev => ev.fragment.name == 'return$clone_address').args.instance) - .then(address => ethers.getContractAt('$EIP712Verifier', address)); - - const expectedDomain = { ...this.domain, verifyingContract: clone.target }; - expect(await getDomain(clone)).to.be.deep.equal(expectedDomain); - - const expectedSeparator = await domainSeparator(expectedDomain); - expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator); - }); - } - }); - - it('hash digest', async function () { - const structhash = ethers.hexlify(ethers.randomBytes(32)); - expect(await this.eip712.$_hashTypedDataV4(structhash)).to.equal(hashTypedData(this.domain, structhash)); - }); - - it('digest', async function () { - const types = { - Mail: formatType({ - to: 'address', - contents: 'string', - }), - }; - - const message = { - to: this.to.address, - contents: 'very interesting', - }; - - const signature = await this.from.signTypedData(this.domain, types, message); - - await expect(this.eip712.verify(signature, this.from.address, message.to, message.contents)).to.not.be.reverted; - }); - - it('name', async function () { - expect(await this.eip712.$_EIP712Name()).to.equal(name); - }); - - it('version', async function () { - expect(await this.eip712.$_EIP712Version()).to.equal(version); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/utils/cryptography/MerkleProof.test.js b/lib_openzeppelin_contracts/test/utils/cryptography/MerkleProof.test.js deleted file mode 100644 index 9d5502d..0000000 --- a/lib_openzeppelin_contracts/test/utils/cryptography/MerkleProof.test.js +++ /dev/null @@ -1,173 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { StandardMerkleTree } = 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 }; -} - -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'); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/cryptography/MessageHashUtils.test.js b/lib_openzeppelin_contracts/test/utils/cryptography/MessageHashUtils.test.js deleted file mode 100644 index f20f5a3..0000000 --- a/lib_openzeppelin_contracts/test/utils/cryptography/MessageHashUtils.test.js +++ /dev/null @@ -1,68 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { domainSeparator, hashTypedData } = require('../../helpers/eip712'); - -async function fixture() { - const mock = await ethers.deployContract('$MessageHashUtils'); - return { mock }; -} - -describe('MessageHashUtils', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('toEthSignedMessageHash', function () { - it('prefixes bytes32 data correctly', async function () { - const message = ethers.randomBytes(32); - const expectedHash = ethers.hashMessage(message); - - expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message)).to.equal(expectedHash); - }); - - it('prefixes dynamic length data correctly', async function () { - const message = ethers.randomBytes(128); - const expectedHash = ethers.hashMessage(message); - - expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes)')(message)).to.equal(expectedHash); - }); - - it('version match for bytes32', async function () { - const message = ethers.randomBytes(32); - const fixed = await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message); - const dynamic = await this.mock.getFunction('$toEthSignedMessageHash(bytes)')(message); - - expect(fixed).to.equal(dynamic); - }); - }); - - describe('toDataWithIntendedValidatorHash', function () { - it('returns the digest correctly', async function () { - const verifier = ethers.Wallet.createRandom().address; - const message = ethers.randomBytes(128); - const expectedHash = ethers.solidityPackedKeccak256( - ['string', 'address', 'bytes'], - ['\x19\x00', verifier, message], - ); - - expect(await this.mock.$toDataWithIntendedValidatorHash(verifier, message)).to.equal(expectedHash); - }); - }); - - describe('toTypedDataHash', function () { - it('returns the digest correctly', async function () { - const domain = { - name: 'Test', - version: '1', - chainId: 1n, - verifyingContract: ethers.Wallet.createRandom().address, - }; - const structhash = ethers.randomBytes(32); - const expectedHash = hashTypedData(domain, structhash); - - expect(await this.mock.$toTypedDataHash(domainSeparator(domain), structhash)).to.equal(expectedHash); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/cryptography/SignatureChecker.test.js b/lib_openzeppelin_contracts/test/utils/cryptography/SignatureChecker.test.js deleted file mode 100644 index e6a0849..0000000 --- a/lib_openzeppelin_contracts/test/utils/cryptography/SignatureChecker.test.js +++ /dev/null @@ -1,61 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const TEST_MESSAGE = ethers.id('OpenZeppelin'); -const TEST_MESSAGE_HASH = ethers.hashMessage(TEST_MESSAGE); - -const WRONG_MESSAGE = ethers.id('Nope'); -const WRONG_MESSAGE_HASH = ethers.hashMessage(WRONG_MESSAGE); - -async function fixture() { - const [signer, other] = await ethers.getSigners(); - const mock = await ethers.deployContract('$SignatureChecker'); - const wallet = await ethers.deployContract('ERC1271WalletMock', [signer]); - const malicious = await ethers.deployContract('ERC1271MaliciousMock'); - const signature = await signer.signMessage(TEST_MESSAGE); - - return { signer, other, mock, wallet, malicious, signature }; -} - -describe('SignatureChecker (ERC1271)', function () { - before('deploying', async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('EOA account', function () { - it('with matching signer and signature', async function () { - expect(await this.mock.$isValidSignatureNow(this.signer, TEST_MESSAGE_HASH, this.signature)).to.be.true; - }); - - it('with invalid signer', async function () { - expect(await this.mock.$isValidSignatureNow(this.other, TEST_MESSAGE_HASH, this.signature)).to.be.false; - }); - - it('with invalid signature', async function () { - expect(await this.mock.$isValidSignatureNow(this.signer, WRONG_MESSAGE_HASH, this.signature)).to.be.false; - }); - }); - - describe('ERC1271 wallet', function () { - for (const fn of ['isValidERC1271SignatureNow', 'isValidSignatureNow']) { - describe(fn, function () { - it('with matching signer and signature', async function () { - expect(await this.mock.getFunction(`$${fn}`)(this.wallet, TEST_MESSAGE_HASH, this.signature)).to.be.true; - }); - - it('with invalid signer', async function () { - expect(await this.mock.getFunction(`$${fn}`)(this.mock, TEST_MESSAGE_HASH, this.signature)).to.be.false; - }); - - it('with invalid signature', async function () { - expect(await this.mock.getFunction(`$${fn}`)(this.wallet, WRONG_MESSAGE_HASH, this.signature)).to.be.false; - }); - - it('with malicious wallet', async function () { - expect(await this.mock.getFunction(`$${fn}`)(this.malicious, TEST_MESSAGE_HASH, this.signature)).to.be.false; - }); - }); - } - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/introspection/ERC165.test.js b/lib_openzeppelin_contracts/test/utils/introspection/ERC165.test.js deleted file mode 100644 index 8117c69..0000000 --- a/lib_openzeppelin_contracts/test/utils/introspection/ERC165.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { shouldSupportInterfaces } = require('./SupportsInterface.behavior'); - -async function fixture() { - return { - mock: await ethers.deployContract('$ERC165'), - }; -} - -describe('ERC165', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - shouldSupportInterfaces(); -}); diff --git a/lib_openzeppelin_contracts/test/utils/introspection/ERC165Checker.test.js b/lib_openzeppelin_contracts/test/utils/introspection/ERC165Checker.test.js deleted file mode 100644 index 1bbe8a5..0000000 --- a/lib_openzeppelin_contracts/test/utils/introspection/ERC165Checker.test.js +++ /dev/null @@ -1,245 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const DUMMY_ID = '0xdeadbeef'; -const DUMMY_ID_2 = '0xcafebabe'; -const DUMMY_ID_3 = '0xdecafbad'; -const DUMMY_UNSUPPORTED_ID = '0xbaddcafe'; -const DUMMY_UNSUPPORTED_ID_2 = '0xbaadcafe'; -const DUMMY_ACCOUNT = '0x1111111111111111111111111111111111111111'; - -async function fixture() { - return { mock: await ethers.deployContract('$ERC165Checker') }; -} - -describe('ERC165Checker', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('ERC165 missing return data', function () { - before(async function () { - this.target = await ethers.deployContract('ERC165MissingData'); - }); - - it('does not support ERC165', async function () { - expect(await this.mock.$supportsERC165(this.target)).to.be.false; - }); - - it('does not support mock interface via supportsInterface', async function () { - expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false; - }); - - it('does not support mock interface via supportsAllInterfaces', async function () { - expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false; - }); - - it('does not support mock interface via getSupportedInterfaces', async function () { - expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]); - }); - - it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { - expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.false; - }); - }); - - describe('ERC165 malicious return data', function () { - beforeEach(async function () { - this.target = await ethers.deployContract('ERC165MaliciousData'); - }); - - it('does not support ERC165', async function () { - expect(await this.mock.$supportsERC165(this.target)).to.be.false; - }); - - it('does not support mock interface via supportsInterface', async function () { - expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false; - }); - - it('does not support mock interface via supportsAllInterfaces', async function () { - expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false; - }); - - it('does not support mock interface via getSupportedInterfaces', async function () { - expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]); - }); - - it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { - expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.true; - }); - }); - - describe('ERC165 not supported', function () { - beforeEach(async function () { - this.target = await ethers.deployContract('ERC165NotSupported'); - }); - - it('does not support ERC165', async function () { - expect(await this.mock.$supportsERC165(this.target)).to.be.false; - }); - - it('does not support mock interface via supportsInterface', async function () { - expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false; - }); - - it('does not support mock interface via supportsAllInterfaces', async function () { - expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false; - }); - - it('does not support mock interface via getSupportedInterfaces', async function () { - expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]); - }); - - it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { - expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.false; - }); - }); - - describe('ERC165 supported', function () { - beforeEach(async function () { - this.target = await ethers.deployContract('ERC165InterfacesSupported', [[]]); - }); - - it('supports ERC165', async function () { - expect(await this.mock.$supportsERC165(this.target)).to.be.true; - }); - - it('does not support mock interface via supportsInterface', async function () { - expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.false; - }); - - it('does not support mock interface via supportsAllInterfaces', async function () { - expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.false; - }); - - it('does not support mock interface via getSupportedInterfaces', async function () { - expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([false]); - }); - - it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { - expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.false; - }); - }); - - describe('ERC165 and single interface supported', function () { - beforeEach(async function () { - this.target = await ethers.deployContract('ERC165InterfacesSupported', [[DUMMY_ID]]); - }); - - it('supports ERC165', async function () { - expect(await this.mock.$supportsERC165(this.target)).to.be.true; - }); - - it('supports mock interface via supportsInterface', async function () { - expect(await this.mock.$supportsInterface(this.target, DUMMY_ID)).to.be.true; - }); - - it('supports mock interface via supportsAllInterfaces', async function () { - expect(await this.mock.$supportsAllInterfaces(this.target, [DUMMY_ID])).to.be.true; - }); - - it('supports mock interface via getSupportedInterfaces', async function () { - expect(await this.mock.$getSupportedInterfaces(this.target, [DUMMY_ID])).to.deep.equal([true]); - }); - - it('supports mock interface via supportsERC165InterfaceUnchecked', async function () { - expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, DUMMY_ID)).to.be.true; - }); - }); - - describe('ERC165 and many interfaces supported', function () { - const supportedInterfaces = [DUMMY_ID, DUMMY_ID_2, DUMMY_ID_3]; - beforeEach(async function () { - this.target = await ethers.deployContract('ERC165InterfacesSupported', [supportedInterfaces]); - }); - - it('supports ERC165', async function () { - expect(await this.mock.$supportsERC165(this.target)).to.be.true; - }); - - it('supports each interfaceId via supportsInterface', async function () { - for (const interfaceId of supportedInterfaces) { - expect(await this.mock.$supportsInterface(this.target, interfaceId)).to.be.true; - } - }); - - it('supports all interfaceIds via supportsAllInterfaces', async function () { - expect(await this.mock.$supportsAllInterfaces(this.target, supportedInterfaces)).to.be.true; - }); - - it('supports none of the interfaces queried via supportsAllInterfaces', async function () { - const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2]; - - expect(await this.mock.$supportsAllInterfaces(this.target, interfaceIdsToTest)).to.be.false; - }); - - it('supports not all of the interfaces queried via supportsAllInterfaces', async function () { - const interfaceIdsToTest = [...supportedInterfaces, DUMMY_UNSUPPORTED_ID]; - expect(await this.mock.$supportsAllInterfaces(this.target, interfaceIdsToTest)).to.be.false; - }); - - it('supports all interfaceIds via getSupportedInterfaces', async function () { - expect(await this.mock.$getSupportedInterfaces(this.target, supportedInterfaces)).to.deep.equal( - supportedInterfaces.map(i => supportedInterfaces.includes(i)), - ); - }); - - it('supports none of the interfaces queried via getSupportedInterfaces', async function () { - const interfaceIdsToTest = [DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2]; - - expect(await this.mock.$getSupportedInterfaces(this.target, interfaceIdsToTest)).to.deep.equal( - interfaceIdsToTest.map(i => supportedInterfaces.includes(i)), - ); - }); - - it('supports not all of the interfaces queried via getSupportedInterfaces', async function () { - const interfaceIdsToTest = [...supportedInterfaces, DUMMY_UNSUPPORTED_ID]; - - expect(await this.mock.$getSupportedInterfaces(this.target, interfaceIdsToTest)).to.deep.equal( - interfaceIdsToTest.map(i => supportedInterfaces.includes(i)), - ); - }); - - it('supports each interfaceId via supportsERC165InterfaceUnchecked', async function () { - for (const interfaceId of supportedInterfaces) { - expect(await this.mock.$supportsERC165InterfaceUnchecked(this.target, interfaceId)).to.be.true; - } - }); - }); - - describe('account address does not support ERC165', function () { - it('does not support ERC165', async function () { - expect(await this.mock.$supportsERC165(DUMMY_ACCOUNT)).to.be.false; - }); - - it('does not support mock interface via supportsInterface', async function () { - expect(await this.mock.$supportsInterface(DUMMY_ACCOUNT, DUMMY_ID)).to.be.false; - }); - - it('does not support mock interface via supportsAllInterfaces', async function () { - expect(await this.mock.$supportsAllInterfaces(DUMMY_ACCOUNT, [DUMMY_ID])).to.be.false; - }); - - it('does not support mock interface via getSupportedInterfaces', async function () { - expect(await this.mock.$getSupportedInterfaces(DUMMY_ACCOUNT, [DUMMY_ID])).to.deep.equal([false]); - }); - - it('does not support mock interface via supportsERC165InterfaceUnchecked', async function () { - expect(await this.mock.$supportsERC165InterfaceUnchecked(DUMMY_ACCOUNT, DUMMY_ID)).to.be.false; - }); - }); - - it('Return bomb resistance', async function () { - this.target = await ethers.deployContract('ERC165ReturnBombMock'); - - const { gasUsed: gasUsed1 } = await this.mock.$supportsInterface.send(this.target, DUMMY_ID).then(tx => tx.wait()); - expect(gasUsed1).to.be.lessThan(120_000n); // 3*30k + 21k + some margin - - const { gasUsed: gasUsed2 } = await this.mock.$getSupportedInterfaces - .send(this.target, [DUMMY_ID, DUMMY_ID_2, DUMMY_ID_3, DUMMY_UNSUPPORTED_ID, DUMMY_UNSUPPORTED_ID_2]) - .then(tx => tx.wait()); - - expect(gasUsed2).to.be.lessThan(250_000n); // (2+5)*30k + 21k + some margin - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/introspection/SupportsInterface.behavior.js b/lib_openzeppelin_contracts/test/utils/introspection/SupportsInterface.behavior.js deleted file mode 100644 index c2bd1a4..0000000 --- a/lib_openzeppelin_contracts/test/utils/introspection/SupportsInterface.behavior.js +++ /dev/null @@ -1,145 +0,0 @@ -const { expect } = require('chai'); -const { interfaceId } = require('../../helpers/methods'); -const { mapValues } = require('../../helpers/iterate'); - -const INVALID_ID = '0xffffffff'; -const SIGNATURES = { - ERC165: ['supportsInterface(bytes4)'], - ERC721: [ - 'balanceOf(address)', - 'ownerOf(uint256)', - 'approve(address,uint256)', - 'getApproved(uint256)', - 'setApprovalForAll(address,bool)', - 'isApprovedForAll(address,address)', - 'transferFrom(address,address,uint256)', - 'safeTransferFrom(address,address,uint256)', - 'safeTransferFrom(address,address,uint256,bytes)', - ], - ERC721Enumerable: ['totalSupply()', 'tokenOfOwnerByIndex(address,uint256)', 'tokenByIndex(uint256)'], - ERC721Metadata: ['name()', 'symbol()', 'tokenURI(uint256)'], - ERC1155: [ - 'balanceOf(address,uint256)', - 'balanceOfBatch(address[],uint256[])', - 'setApprovalForAll(address,bool)', - 'isApprovedForAll(address,address)', - 'safeTransferFrom(address,address,uint256,uint256,bytes)', - 'safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)', - ], - ERC1155MetadataURI: ['uri(uint256)'], - ERC1155Receiver: [ - 'onERC1155Received(address,address,uint256,uint256,bytes)', - 'onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)', - ], - ERC1363: [ - 'transferAndCall(address,uint256)', - 'transferAndCall(address,uint256,bytes)', - 'transferFromAndCall(address,address,uint256)', - 'transferFromAndCall(address,address,uint256,bytes)', - 'approveAndCall(address,uint256)', - 'approveAndCall(address,uint256,bytes)', - ], - AccessControl: [ - 'hasRole(bytes32,address)', - 'getRoleAdmin(bytes32)', - 'grantRole(bytes32,address)', - 'revokeRole(bytes32,address)', - 'renounceRole(bytes32,address)', - ], - AccessControlEnumerable: ['getRoleMember(bytes32,uint256)', 'getRoleMemberCount(bytes32)'], - AccessControlDefaultAdminRules: [ - 'defaultAdminDelay()', - 'pendingDefaultAdminDelay()', - 'defaultAdmin()', - 'pendingDefaultAdmin()', - 'defaultAdminDelayIncreaseWait()', - 'changeDefaultAdminDelay(uint48)', - 'rollbackDefaultAdminDelay()', - 'beginDefaultAdminTransfer(address)', - 'acceptDefaultAdminTransfer()', - 'cancelDefaultAdminTransfer()', - ], - Governor: [ - 'name()', - 'version()', - 'COUNTING_MODE()', - 'hashProposal(address[],uint256[],bytes[],bytes32)', - 'state(uint256)', - 'proposalThreshold()', - 'proposalSnapshot(uint256)', - 'proposalDeadline(uint256)', - 'proposalProposer(uint256)', - 'proposalEta(uint256)', - 'proposalNeedsQueuing(uint256)', - 'votingDelay()', - 'votingPeriod()', - 'quorum(uint256)', - 'getVotes(address,uint256)', - 'getVotesWithParams(address,uint256,bytes)', - 'hasVoted(uint256,address)', - 'propose(address[],uint256[],bytes[],string)', - 'queue(address[],uint256[],bytes[],bytes32)', - 'execute(address[],uint256[],bytes[],bytes32)', - 'cancel(address[],uint256[],bytes[],bytes32)', - 'castVote(uint256,uint8)', - 'castVoteWithReason(uint256,uint8,string)', - 'castVoteWithReasonAndParams(uint256,uint8,string,bytes)', - 'castVoteBySig(uint256,uint8,address,bytes)', - 'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,bytes)', - ], - ERC2981: ['royaltyInfo(uint256,uint256)'], -}; - -const INTERFACE_IDS = mapValues(SIGNATURES, interfaceId); - -function shouldSupportInterfaces(interfaces = []) { - interfaces.unshift('ERC165'); - - describe('ERC165', function () { - beforeEach(function () { - this.contractUnderTest = this.mock || this.token; - }); - - 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); - } - }); - - it('returns true', async function () { - for (const k of interfaces) { - const interfaceId = INTERFACE_IDS[k] ?? k; - expect(await this.contractUnderTest.supportsInterface(interfaceId), `does not support ${k}`).to.be.true; - } - }); - }); - - describe('when the interfaceId is not supported', function () { - it('uses less than 30k', async function () { - expect(await this.contractUnderTest.supportsInterface.estimateGas(INVALID_ID)).to.lte(30_000n); - }); - - it('returns false', async function () { - expect(await this.contractUnderTest.supportsInterface(INVALID_ID), `supports ${INVALID_ID}`).to.be.false; - }); - }); - - it('all interface functions are in ABI', async function () { - for (const k of interfaces) { - // skip interfaces for which we don't have a function list - if (SIGNATURES[k] === undefined) continue; - - // Check the presence of each function in the contract's interface - for (const fnSig of SIGNATURES[k]) { - expect(this.contractUnderTest.interface.hasFunction(fnSig), `did not find ${fnSig}`).to.be.true; - } - } - }); - }); -} - -module.exports = { - shouldSupportInterfaces, -}; diff --git a/lib_openzeppelin_contracts/test/utils/math/Math.t.sol b/lib_openzeppelin_contracts/test/utils/math/Math.t.sol deleted file mode 100644 index 31a6b58..0000000 --- a/lib_openzeppelin_contracts/test/utils/math/Math.t.sol +++ /dev/null @@ -1,311 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test, stdError} from "@forge-std/Test.sol"; - -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -contract MathTest is Test { - function testSelect(bool f, uint256 a, uint256 b) public { - assertEq(Math.ternary(f, a, b), f ? a : b); - } - - // MIN & MAX - function testMinMax(uint256 a, uint256 b) public { - assertEq(Math.min(a, b), a < b ? a : b); - assertEq(Math.max(a, b), a > b ? a : b); - } - - // CEILDIV - function testCeilDiv(uint256 a, uint256 b) public { - vm.assume(b > 0); - - uint256 result = Math.ceilDiv(a, b); - - if (result == 0) { - assertEq(a, 0); - } else { - uint256 expect = a / b; - if (expect * b < a) { - expect += 1; - } - assertEq(result, expect); - } - } - - // SQRT - function testSqrt(uint256 input, uint8 r) public { - Math.Rounding rounding = _asRounding(r); - - uint256 result = Math.sqrt(input, rounding); - - // square of result is bigger than input - if (_squareBigger(result, input)) { - assertTrue(Math.unsignedRoundsUp(rounding)); - assertTrue(_squareSmaller(result - 1, input)); - } - // square of result is smaller than input - else if (_squareSmaller(result, input)) { - assertFalse(Math.unsignedRoundsUp(rounding)); - assertTrue(_squareBigger(result + 1, input)); - } - // input is perfect square - else { - assertEq(result * result, input); - } - } - - function _squareBigger(uint256 value, uint256 ref) private pure returns (bool) { - (bool noOverflow, uint256 square) = Math.tryMul(value, value); - return !noOverflow || square > ref; - } - - function _squareSmaller(uint256 value, uint256 ref) private pure returns (bool) { - return value * value < ref; - } - - // INV - function testInvMod(uint256 value, uint256 p) public { - _testInvMod(value, p, true); - } - - function testInvMod2(uint256 seed) public { - uint256 p = 2; // prime - _testInvMod(bound(seed, 1, p - 1), p, false); - } - - function testInvMod17(uint256 seed) public { - uint256 p = 17; // prime - _testInvMod(bound(seed, 1, p - 1), p, false); - } - - function testInvMod65537(uint256 seed) public { - uint256 p = 65537; // prime - _testInvMod(bound(seed, 1, p - 1), p, false); - } - - function testInvModP256(uint256 seed) public { - uint256 p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff; // prime - _testInvMod(bound(seed, 1, p - 1), p, false); - } - - function _testInvMod(uint256 value, uint256 p, bool allowZero) private { - uint256 inverse = Math.invMod(value, p); - if (inverse != 0) { - assertEq(mulmod(value, inverse, p), 1); - assertLt(inverse, p); - } else { - assertTrue(allowZero); - } - } - - // LOG2 - function testLog2(uint256 input, uint8 r) public { - Math.Rounding rounding = _asRounding(r); - - uint256 result = Math.log2(input, rounding); - - if (input == 0) { - assertEq(result, 0); - } else if (_powerOf2Bigger(result, input)) { - assertTrue(Math.unsignedRoundsUp(rounding)); - assertTrue(_powerOf2Smaller(result - 1, input)); - } else if (_powerOf2Smaller(result, input)) { - assertFalse(Math.unsignedRoundsUp(rounding)); - assertTrue(_powerOf2Bigger(result + 1, input)); - } else { - assertEq(2 ** result, input); - } - } - - function _powerOf2Bigger(uint256 value, uint256 ref) private pure returns (bool) { - return value >= 256 || 2 ** value > ref; // 2**256 overflows uint256 - } - - function _powerOf2Smaller(uint256 value, uint256 ref) private pure returns (bool) { - return 2 ** value < ref; - } - - // LOG10 - function testLog10(uint256 input, uint8 r) public { - Math.Rounding rounding = _asRounding(r); - - uint256 result = Math.log10(input, rounding); - - if (input == 0) { - assertEq(result, 0); - } else if (_powerOf10Bigger(result, input)) { - assertTrue(Math.unsignedRoundsUp(rounding)); - assertTrue(_powerOf10Smaller(result - 1, input)); - } else if (_powerOf10Smaller(result, input)) { - assertFalse(Math.unsignedRoundsUp(rounding)); - assertTrue(_powerOf10Bigger(result + 1, input)); - } else { - assertEq(10 ** result, input); - } - } - - function _powerOf10Bigger(uint256 value, uint256 ref) private pure returns (bool) { - return value >= 78 || 10 ** value > ref; // 10**78 overflows uint256 - } - - function _powerOf10Smaller(uint256 value, uint256 ref) private pure returns (bool) { - return 10 ** value < ref; - } - - // LOG256 - function testLog256(uint256 input, uint8 r) public { - Math.Rounding rounding = _asRounding(r); - - uint256 result = Math.log256(input, rounding); - - if (input == 0) { - assertEq(result, 0); - } else if (_powerOf256Bigger(result, input)) { - assertTrue(Math.unsignedRoundsUp(rounding)); - assertTrue(_powerOf256Smaller(result - 1, input)); - } else if (_powerOf256Smaller(result, input)) { - assertFalse(Math.unsignedRoundsUp(rounding)); - assertTrue(_powerOf256Bigger(result + 1, input)); - } else { - assertEq(256 ** result, input); - } - } - - function _powerOf256Bigger(uint256 value, uint256 ref) private pure returns (bool) { - return value >= 32 || 256 ** value > ref; // 256**32 overflows uint256 - } - - function _powerOf256Smaller(uint256 value, uint256 ref) private pure returns (bool) { - return 256 ** value < ref; - } - - // MULDIV - function testMulDiv(uint256 x, uint256 y, uint256 d) public { - // Full precision for x * y - (uint256 xyHi, uint256 xyLo) = _mulHighLow(x, y); - - // Assume result won't overflow (see {testMulDivDomain}) - // This also checks that `d` is positive - vm.assume(xyHi < d); - - // Perform muldiv - uint256 q = Math.mulDiv(x, y, d); - - // Full precision for q * d - (uint256 qdHi, uint256 qdLo) = _mulHighLow(q, d); - // Add remainder of x * y / d (computed as rem = (x * y % d)) - (uint256 qdRemLo, uint256 c) = _addCarry(qdLo, mulmod(x, y, d)); - uint256 qdRemHi = qdHi + c; - - // Full precision check that x * y = q * d + rem - assertEq(xyHi, qdRemHi); - assertEq(xyLo, qdRemLo); - } - - function testMulDivDomain(uint256 x, uint256 y, uint256 d) public { - (uint256 xyHi, ) = _mulHighLow(x, y); - - // Violate {testMulDiv} assumption (covers d is 0 and result overflow) - vm.assume(xyHi >= d); - - // we are outside the scope of {testMulDiv}, we expect muldiv to revert - vm.expectRevert(d == 0 ? stdError.divisionError : stdError.arithmeticError); - Math.mulDiv(x, y, d); - } - - // MOD EXP - function testModExp(uint256 b, uint256 e, uint256 m) public { - if (m == 0) { - vm.expectRevert(stdError.divisionError); - } - uint256 result = Math.modExp(b, e, m); - assertLt(result, m); - assertEq(result, _nativeModExp(b, e, m)); - } - - function testTryModExp(uint256 b, uint256 e, uint256 m) public { - (bool success, uint256 result) = Math.tryModExp(b, e, m); - assertEq(success, m != 0); - if (success) { - assertLt(result, m); - assertEq(result, _nativeModExp(b, e, m)); - } else { - assertEq(result, 0); - } - } - - function testModExpMemory(uint256 b, uint256 e, uint256 m) public { - if (m == 0) { - vm.expectRevert(stdError.divisionError); - } - bytes memory result = Math.modExp(abi.encodePacked(b), abi.encodePacked(e), abi.encodePacked(m)); - assertEq(result.length, 0x20); - uint256 res = abi.decode(result, (uint256)); - assertLt(res, m); - assertEq(res, _nativeModExp(b, e, m)); - } - - function testTryModExpMemory(uint256 b, uint256 e, uint256 m) public { - (bool success, bytes memory result) = Math.tryModExp( - abi.encodePacked(b), - abi.encodePacked(e), - abi.encodePacked(m) - ); - if (success) { - assertEq(result.length, 0x20); // m is a uint256, so abi.encodePacked(m).length is 0x20 - uint256 res = abi.decode(result, (uint256)); - assertLt(res, m); - assertEq(res, _nativeModExp(b, e, m)); - } else { - assertEq(result.length, 0); - } - } - - function _nativeModExp(uint256 b, uint256 e, uint256 m) private pure returns (uint256) { - if (m == 1) return 0; - uint256 r = 1; - while (e > 0) { - if (e % 2 > 0) { - r = mulmod(r, b, m); - } - b = mulmod(b, b, m); - e >>= 1; - } - return r; - } - - // Helpers - function _asRounding(uint8 r) private pure returns (Math.Rounding) { - vm.assume(r < uint8(type(Math.Rounding).max)); - return Math.Rounding(r); - } - - function _mulHighLow(uint256 x, uint256 y) private pure returns (uint256 high, uint256 low) { - (uint256 x0, uint256 x1) = (x & type(uint128).max, x >> 128); - (uint256 y0, uint256 y1) = (y & type(uint128).max, y >> 128); - - // Karatsuba algorithm - // https://en.wikipedia.org/wiki/Karatsuba_algorithm - uint256 z2 = x1 * y1; - uint256 z1a = x1 * y0; - uint256 z1b = x0 * y1; - uint256 z0 = x0 * y0; - - uint256 carry = ((z1a & type(uint128).max) + (z1b & type(uint128).max) + (z0 >> 128)) >> 128; - - high = z2 + (z1a >> 128) + (z1b >> 128) + carry; - - unchecked { - low = x * y; - } - } - - function _addCarry(uint256 x, uint256 y) private pure returns (uint256 res, uint256 carry) { - unchecked { - res = x + y; - } - carry = res < x ? 1 : 0; - } -} diff --git a/lib_openzeppelin_contracts/test/utils/math/Math.test.js b/lib_openzeppelin_contracts/test/utils/math/Math.test.js deleted file mode 100644 index f38f2f3..0000000 --- a/lib_openzeppelin_contracts/test/utils/math/Math.test.js +++ /dev/null @@ -1,562 +0,0 @@ -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 { Rounding } = require('../../helpers/enums'); -const { min, max, modExp } = require('../../helpers/math'); -const { generators } = require('../../helpers/random'); -const { product, range } = require('../../helpers/iterate'); - -const RoundingDown = [Rounding.Floor, Rounding.Trunc]; -const RoundingUp = [Rounding.Ceil, Rounding.Expand]; - -const bytes = (value, width = undefined) => ethers.Typed.bytes(ethers.toBeHex(value, width)); -const uint256 = value => ethers.Typed.uint256(value); -bytes.zero = '0x'; -uint256.zero = 0n; - -async function testCommutative(fn, lhs, rhs, expected, ...extra) { - expect(await fn(lhs, rhs, ...extra)).to.deep.equal(expected); - expect(await fn(rhs, lhs, ...extra)).to.deep.equal(expected); -} - -async function fixture() { - const mock = await ethers.deployContract('$Math'); - - // disambiguation, we use the version with explicit rounding - mock.$mulDiv = mock['$mulDiv(uint256,uint256,uint256,uint8)']; - mock.$sqrt = mock['$sqrt(uint256,uint8)']; - mock.$log2 = mock['$log2(uint256,uint8)']; - mock.$log10 = mock['$log10(uint256,uint8)']; - mock.$log256 = mock['$log256(uint256,uint8)']; - - return { mock }; -} - -describe('Math', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('tryAdd', function () { - it('adds correctly', async function () { - const a = 5678n; - const b = 1234n; - await testCommutative(this.mock.$tryAdd, a, b, [true, a + b]); - }); - - it('reverts on addition overflow', async function () { - const a = ethers.MaxUint256; - const b = 1n; - await testCommutative(this.mock.$tryAdd, a, b, [false, 0n]); - }); - }); - - describe('trySub', function () { - it('subtracts correctly', async function () { - const a = 5678n; - const b = 1234n; - expect(await this.mock.$trySub(a, b)).to.deep.equal([true, a - b]); - }); - - it('reverts if subtraction result would be negative', async function () { - const a = 1234n; - const b = 5678n; - expect(await this.mock.$trySub(a, b)).to.deep.equal([false, 0n]); - }); - }); - - describe('tryMul', function () { - it('multiplies correctly', async function () { - const a = 1234n; - const b = 5678n; - await testCommutative(this.mock.$tryMul, a, b, [true, a * b]); - }); - - it('multiplies by zero correctly', async function () { - const a = 0n; - const b = 5678n; - await testCommutative(this.mock.$tryMul, a, b, [true, a * b]); - }); - - it('reverts on multiplication overflow', async function () { - const a = ethers.MaxUint256; - const b = 2n; - await testCommutative(this.mock.$tryMul, a, b, [false, 0n]); - }); - }); - - describe('tryDiv', function () { - it('divides correctly', async function () { - const a = 5678n; - const b = 5678n; - expect(await this.mock.$tryDiv(a, b)).to.deep.equal([true, a / b]); - }); - - it('divides zero correctly', async function () { - const a = 0n; - const b = 5678n; - expect(await this.mock.$tryDiv(a, b)).to.deep.equal([true, a / b]); - }); - - it('returns complete number result on non-even division', async function () { - const a = 7000n; - const b = 5678n; - expect(await this.mock.$tryDiv(a, b)).to.deep.equal([true, a / b]); - }); - - it('reverts on division by zero', async function () { - const a = 5678n; - const b = 0n; - expect(await this.mock.$tryDiv(a, b)).to.deep.equal([false, 0n]); - }); - }); - - describe('tryMod', function () { - describe('modulos correctly', function () { - it('when the dividend is smaller than the divisor', async function () { - const a = 284n; - const b = 5678n; - expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]); - }); - - it('when the dividend is equal to the divisor', async function () { - const a = 5678n; - const b = 5678n; - expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]); - }); - - it('when the dividend is larger than the divisor', async function () { - const a = 7000n; - const b = 5678n; - expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]); - }); - - it('when the dividend is a multiple of the divisor', async function () { - const a = 17034n; // 17034 == 5678 * 3 - const b = 5678n; - expect(await this.mock.$tryMod(a, b)).to.deep.equal([true, a % b]); - }); - }); - - it('reverts with a 0 divisor', async function () { - const a = 5678n; - const b = 0n; - expect(await this.mock.$tryMod(a, b)).to.deep.equal([false, 0n]); - }); - }); - - describe('max', function () { - it('is correctly detected in both position', async function () { - await testCommutative(this.mock.$max, 1234n, 5678n, max(1234n, 5678n)); - }); - }); - - describe('min', function () { - it('is correctly detected in both position', async function () { - await testCommutative(this.mock.$min, 1234n, 5678n, min(1234n, 5678n)); - }); - }); - - describe('average', function () { - it('is correctly calculated with two odd numbers', async function () { - const a = 57417n; - const b = 95431n; - expect(await this.mock.$average(a, b)).to.equal((a + b) / 2n); - }); - - it('is correctly calculated with two even numbers', async function () { - const a = 42304n; - const b = 84346n; - expect(await this.mock.$average(a, b)).to.equal((a + b) / 2n); - }); - - it('is correctly calculated with one even and one odd number', async function () { - const a = 57417n; - const b = 84346n; - expect(await this.mock.$average(a, b)).to.equal((a + b) / 2n); - }); - - it('is correctly calculated with two max uint256 numbers', async function () { - const a = ethers.MaxUint256; - expect(await this.mock.$average(a, a)).to.equal(a); - }); - }); - - describe('ceilDiv', function () { - it('reverts on zero division', async function () { - const a = 2n; - const b = 0n; - // It's unspecified because it's a low level 0 division error - await expect(this.mock.$ceilDiv(a, b)).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO); - }); - - it('does not round up a zero result', async function () { - const a = 0n; - const b = 2n; - const r = 0n; - expect(await this.mock.$ceilDiv(a, b)).to.equal(r); - }); - - it('does not round up on exact division', async function () { - const a = 10n; - const b = 5n; - const r = 2n; - expect(await this.mock.$ceilDiv(a, b)).to.equal(r); - }); - - it('rounds up on division with remainders', async function () { - const a = 42n; - const b = 13n; - const r = 4n; - expect(await this.mock.$ceilDiv(a, b)).to.equal(r); - }); - - it('does not overflow', async function () { - const a = ethers.MaxUint256; - const b = 2n; - const r = 1n << 255n; - expect(await this.mock.$ceilDiv(a, b)).to.equal(r); - }); - - it('correctly computes max uint256 divided by 1', async function () { - const a = ethers.MaxUint256; - const b = 1n; - const r = ethers.MaxUint256; - expect(await this.mock.$ceilDiv(a, b)).to.equal(r); - }); - }); - - describe('mulDiv', function () { - it('divide by 0', async function () { - const a = 1n; - const b = 1n; - const c = 0n; - await expect(this.mock.$mulDiv(a, b, c, Rounding.Floor)).to.be.revertedWithPanic(PANIC_CODES.DIVISION_BY_ZERO); - }); - - it('reverts with result higher than 2 ^ 256', async function () { - const a = 5n; - const b = ethers.MaxUint256; - const c = 2n; - await expect(this.mock.$mulDiv(a, b, c, Rounding.Floor)).to.be.revertedWithPanic( - PANIC_CODES.ARITHMETIC_UNDER_OR_OVERFLOW, - ); - }); - - describe('does round down', function () { - it('small values', async function () { - for (const rounding of RoundingDown) { - expect(await this.mock.$mulDiv(3n, 4n, 5n, rounding)).to.equal(2n); - expect(await this.mock.$mulDiv(3n, 5n, 5n, rounding)).to.equal(3n); - } - }); - - it('large values', async function () { - for (const rounding of RoundingDown) { - expect(await this.mock.$mulDiv(42n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding)).to.equal(41n); - - expect(await this.mock.$mulDiv(17n, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal(17n); - - expect( - await this.mock.$mulDiv(ethers.MaxUint256 - 1n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding), - ).to.equal(ethers.MaxUint256 - 2n); - - expect( - await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding), - ).to.equal(ethers.MaxUint256 - 1n); - - expect(await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal( - ethers.MaxUint256, - ); - } - }); - }); - - describe('does round up', function () { - it('small values', async function () { - for (const rounding of RoundingUp) { - expect(await this.mock.$mulDiv(3n, 4n, 5n, rounding)).to.equal(3n); - expect(await this.mock.$mulDiv(3n, 5n, 5n, rounding)).to.equal(3n); - } - }); - - it('large values', async function () { - for (const rounding of RoundingUp) { - expect(await this.mock.$mulDiv(42n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding)).to.equal(42n); - - expect(await this.mock.$mulDiv(17n, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal(17n); - - expect( - await this.mock.$mulDiv(ethers.MaxUint256 - 1n, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding), - ).to.equal(ethers.MaxUint256 - 1n); - - expect( - await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256 - 1n, ethers.MaxUint256, rounding), - ).to.equal(ethers.MaxUint256 - 1n); - - expect(await this.mock.$mulDiv(ethers.MaxUint256, ethers.MaxUint256, ethers.MaxUint256, rounding)).to.equal( - ethers.MaxUint256, - ); - } - }); - }); - }); - - describe('invMod', function () { - for (const factors of [ - [0n], - [1n], - [2n], - [17n], - [65537n], - [0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn], - [3n, 5n], - [3n, 7n], - [47n, 53n], - ]) { - const p = factors.reduce((acc, f) => acc * f, 1n); - - describe(`using p=${p} which is ${p > 1 && factors.length > 1 ? 'not ' : ''}a prime`, function () { - it('trying to inverse 0 returns 0', async function () { - expect(await this.mock.$invMod(0, p)).to.equal(0n); - expect(await this.mock.$invMod(p, p)).to.equal(0n); // p is 0 mod p - }); - - if (p != 0) { - for (const value of Array.from({ length: 16 }, generators.uint256)) { - const isInversible = factors.every(f => value % f); - it(`trying to inverse ${value}`, async function () { - const result = await this.mock.$invMod(value, p); - if (isInversible) { - expect((value * result) % p).to.equal(1n); - } else { - expect(result).to.equal(0n); - } - }); - } - } - }); - } - }); - - describe('modExp', function () { - for (const [name, type] of Object.entries({ uint256, bytes })) { - describe(`with ${name} inputs`, function () { - it('is correctly calculating modulus', async function () { - const b = 3n; - const e = 200n; - const m = 50n; - - expect(await this.mock.$modExp(type(b), type(e), type(m))).to.equal(type(b ** e % m).value); - }); - - it('is correctly reverting when modulus is zero', async function () { - const b = 3n; - const e = 200n; - const m = 0n; - - await expect(this.mock.$modExp(type(b), type(e), type(m))).to.be.revertedWithPanic( - PANIC_CODES.DIVISION_BY_ZERO, - ); - }); - }); - } - - describe('with large bytes inputs', function () { - for (const [[b, log2b], [e, log2e], [m, log2m]] of product( - range(320, 512, 64).map(e => [2n ** BigInt(e) + 1n, e]), - range(320, 512, 64).map(e => [2n ** BigInt(e) + 1n, e]), - range(320, 512, 64).map(e => [2n ** BigInt(e) + 1n, e]), - )) { - it(`calculates b ** e % m (b=2**${log2b}+1) (e=2**${log2e}+1) (m=2**${log2m}+1)`, async function () { - const mLength = ethers.dataLength(ethers.toBeHex(m)); - - expect(await this.mock.$modExp(bytes(b), bytes(e), bytes(m))).to.equal(bytes(modExp(b, e, m), mLength).value); - }); - } - }); - }); - - describe('tryModExp', function () { - for (const [name, type] of Object.entries({ uint256, bytes })) { - describe(`with ${name} inputs`, function () { - it('is correctly calculating modulus', async function () { - const b = 3n; - const e = 200n; - const m = 50n; - - expect(await this.mock.$tryModExp(type(b), type(e), type(m))).to.deep.equal([true, type(b ** e % m).value]); - }); - - it('is correctly reverting when modulus is zero', async function () { - const b = 3n; - const e = 200n; - const m = 0n; - - expect(await this.mock.$tryModExp(type(b), type(e), type(m))).to.deep.equal([false, type.zero]); - }); - }); - } - - describe('with large bytes inputs', function () { - for (const [[b, log2b], [e, log2e], [m, log2m]] of product( - range(320, 513, 64).map(e => [2n ** BigInt(e) + 1n, e]), - range(320, 513, 64).map(e => [2n ** BigInt(e) + 1n, e]), - range(320, 513, 64).map(e => [2n ** BigInt(e) + 1n, e]), - )) { - it(`calculates b ** e % m (b=2**${log2b}+1) (e=2**${log2e}+1) (m=2**${log2m}+1)`, async function () { - const mLength = ethers.dataLength(ethers.toBeHex(m)); - - expect(await this.mock.$tryModExp(bytes(b), bytes(e), bytes(m))).to.deep.equal([ - true, - bytes(modExp(b, e, m), mLength).value, - ]); - }); - } - }); - }); - - describe('sqrt', function () { - it('rounds down', async function () { - for (const rounding of RoundingDown) { - expect(await this.mock.$sqrt(0n, rounding)).to.equal(0n); - expect(await this.mock.$sqrt(1n, rounding)).to.equal(1n); - expect(await this.mock.$sqrt(2n, rounding)).to.equal(1n); - expect(await this.mock.$sqrt(3n, rounding)).to.equal(1n); - expect(await this.mock.$sqrt(4n, rounding)).to.equal(2n); - expect(await this.mock.$sqrt(144n, rounding)).to.equal(12n); - expect(await this.mock.$sqrt(999999n, rounding)).to.equal(999n); - expect(await this.mock.$sqrt(1000000n, rounding)).to.equal(1000n); - expect(await this.mock.$sqrt(1000001n, rounding)).to.equal(1000n); - expect(await this.mock.$sqrt(1002000n, rounding)).to.equal(1000n); - expect(await this.mock.$sqrt(1002001n, rounding)).to.equal(1001n); - expect(await this.mock.$sqrt(ethers.MaxUint256, rounding)).to.equal(340282366920938463463374607431768211455n); - } - }); - - it('rounds up', async function () { - for (const rounding of RoundingUp) { - expect(await this.mock.$sqrt(0n, rounding)).to.equal(0n); - expect(await this.mock.$sqrt(1n, rounding)).to.equal(1n); - expect(await this.mock.$sqrt(2n, rounding)).to.equal(2n); - expect(await this.mock.$sqrt(3n, rounding)).to.equal(2n); - expect(await this.mock.$sqrt(4n, rounding)).to.equal(2n); - expect(await this.mock.$sqrt(144n, rounding)).to.equal(12n); - expect(await this.mock.$sqrt(999999n, rounding)).to.equal(1000n); - expect(await this.mock.$sqrt(1000000n, rounding)).to.equal(1000n); - expect(await this.mock.$sqrt(1000001n, rounding)).to.equal(1001n); - expect(await this.mock.$sqrt(1002000n, rounding)).to.equal(1001n); - expect(await this.mock.$sqrt(1002001n, rounding)).to.equal(1001n); - expect(await this.mock.$sqrt(ethers.MaxUint256, rounding)).to.equal(340282366920938463463374607431768211456n); - } - }); - }); - - describe('log', function () { - describe('log2', function () { - it('rounds down', async function () { - for (const rounding of RoundingDown) { - expect(await this.mock.$log2(0n, rounding)).to.equal(0n); - expect(await this.mock.$log2(1n, rounding)).to.equal(0n); - expect(await this.mock.$log2(2n, rounding)).to.equal(1n); - expect(await this.mock.$log2(3n, rounding)).to.equal(1n); - expect(await this.mock.$log2(4n, rounding)).to.equal(2n); - expect(await this.mock.$log2(5n, rounding)).to.equal(2n); - expect(await this.mock.$log2(6n, rounding)).to.equal(2n); - expect(await this.mock.$log2(7n, rounding)).to.equal(2n); - expect(await this.mock.$log2(8n, rounding)).to.equal(3n); - expect(await this.mock.$log2(9n, rounding)).to.equal(3n); - expect(await this.mock.$log2(ethers.MaxUint256, rounding)).to.equal(255n); - } - }); - - it('rounds up', async function () { - for (const rounding of RoundingUp) { - expect(await this.mock.$log2(0n, rounding)).to.equal(0n); - expect(await this.mock.$log2(1n, rounding)).to.equal(0n); - expect(await this.mock.$log2(2n, rounding)).to.equal(1n); - expect(await this.mock.$log2(3n, rounding)).to.equal(2n); - expect(await this.mock.$log2(4n, rounding)).to.equal(2n); - expect(await this.mock.$log2(5n, rounding)).to.equal(3n); - expect(await this.mock.$log2(6n, rounding)).to.equal(3n); - expect(await this.mock.$log2(7n, rounding)).to.equal(3n); - expect(await this.mock.$log2(8n, rounding)).to.equal(3n); - expect(await this.mock.$log2(9n, rounding)).to.equal(4n); - expect(await this.mock.$log2(ethers.MaxUint256, rounding)).to.equal(256n); - } - }); - }); - - describe('log10', function () { - it('rounds down', async function () { - for (const rounding of RoundingDown) { - expect(await this.mock.$log10(0n, rounding)).to.equal(0n); - expect(await this.mock.$log10(1n, rounding)).to.equal(0n); - expect(await this.mock.$log10(2n, rounding)).to.equal(0n); - expect(await this.mock.$log10(9n, rounding)).to.equal(0n); - expect(await this.mock.$log10(10n, rounding)).to.equal(1n); - expect(await this.mock.$log10(11n, rounding)).to.equal(1n); - expect(await this.mock.$log10(99n, rounding)).to.equal(1n); - expect(await this.mock.$log10(100n, rounding)).to.equal(2n); - expect(await this.mock.$log10(101n, rounding)).to.equal(2n); - expect(await this.mock.$log10(999n, rounding)).to.equal(2n); - expect(await this.mock.$log10(1000n, rounding)).to.equal(3n); - expect(await this.mock.$log10(1001n, rounding)).to.equal(3n); - expect(await this.mock.$log10(ethers.MaxUint256, rounding)).to.equal(77n); - } - }); - - it('rounds up', async function () { - for (const rounding of RoundingUp) { - expect(await this.mock.$log10(0n, rounding)).to.equal(0n); - expect(await this.mock.$log10(1n, rounding)).to.equal(0n); - expect(await this.mock.$log10(2n, rounding)).to.equal(1n); - expect(await this.mock.$log10(9n, rounding)).to.equal(1n); - expect(await this.mock.$log10(10n, rounding)).to.equal(1n); - expect(await this.mock.$log10(11n, rounding)).to.equal(2n); - expect(await this.mock.$log10(99n, rounding)).to.equal(2n); - expect(await this.mock.$log10(100n, rounding)).to.equal(2n); - expect(await this.mock.$log10(101n, rounding)).to.equal(3n); - expect(await this.mock.$log10(999n, rounding)).to.equal(3n); - expect(await this.mock.$log10(1000n, rounding)).to.equal(3n); - expect(await this.mock.$log10(1001n, rounding)).to.equal(4n); - expect(await this.mock.$log10(ethers.MaxUint256, rounding)).to.equal(78n); - } - }); - }); - - describe('log256', function () { - it('rounds down', async function () { - for (const rounding of RoundingDown) { - expect(await this.mock.$log256(0n, rounding)).to.equal(0n); - expect(await this.mock.$log256(1n, rounding)).to.equal(0n); - expect(await this.mock.$log256(2n, rounding)).to.equal(0n); - expect(await this.mock.$log256(255n, rounding)).to.equal(0n); - expect(await this.mock.$log256(256n, rounding)).to.equal(1n); - expect(await this.mock.$log256(257n, rounding)).to.equal(1n); - expect(await this.mock.$log256(65535n, rounding)).to.equal(1n); - expect(await this.mock.$log256(65536n, rounding)).to.equal(2n); - expect(await this.mock.$log256(65537n, rounding)).to.equal(2n); - expect(await this.mock.$log256(ethers.MaxUint256, rounding)).to.equal(31n); - } - }); - - it('rounds up', async function () { - for (const rounding of RoundingUp) { - expect(await this.mock.$log256(0n, rounding)).to.equal(0n); - expect(await this.mock.$log256(1n, rounding)).to.equal(0n); - expect(await this.mock.$log256(2n, rounding)).to.equal(1n); - expect(await this.mock.$log256(255n, rounding)).to.equal(1n); - expect(await this.mock.$log256(256n, rounding)).to.equal(1n); - expect(await this.mock.$log256(257n, rounding)).to.equal(2n); - expect(await this.mock.$log256(65535n, rounding)).to.equal(2n); - expect(await this.mock.$log256(65536n, rounding)).to.equal(2n); - expect(await this.mock.$log256(65537n, rounding)).to.equal(3n); - expect(await this.mock.$log256(ethers.MaxUint256, rounding)).to.equal(32n); - } - }); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/math/SafeCast.test.js b/lib_openzeppelin_contracts/test/utils/math/SafeCast.test.js deleted file mode 100644 index ab62406..0000000 --- a/lib_openzeppelin_contracts/test/utils/math/SafeCast.test.js +++ /dev/null @@ -1,159 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { range } = require('../../helpers/iterate'); - -async function fixture() { - const mock = await ethers.deployContract('$SafeCast'); - return { mock }; -} - -describe('SafeCast', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - for (const bits of range(8, 256, 8).map(ethers.toBigInt)) { - const maxValue = 2n ** bits - 1n; - - describe(`toUint${bits}`, () => { - it('downcasts 0', async function () { - expect(await this.mock[`$toUint${bits}`](0n)).is.equal(0n); - }); - - it('downcasts 1', async function () { - expect(await this.mock[`$toUint${bits}`](1n)).is.equal(1n); - }); - - it(`downcasts 2^${bits} - 1 (${maxValue})`, async function () { - expect(await this.mock[`$toUint${bits}`](maxValue)).is.equal(maxValue); - }); - - it(`reverts when downcasting 2^${bits} (${maxValue + 1n})`, async function () { - await expect(this.mock[`$toUint${bits}`](maxValue + 1n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintDowncast') - .withArgs(bits, maxValue + 1n); - }); - - it(`reverts when downcasting 2^${bits} + 1 (${maxValue + 2n})`, async function () { - await expect(this.mock[`$toUint${bits}`](maxValue + 2n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintDowncast') - .withArgs(bits, maxValue + 2n); - }); - }); - } - - describe('toUint256', () => { - it('casts 0', async function () { - expect(await this.mock.$toUint256(0n)).is.equal(0n); - }); - - it('casts 1', async function () { - expect(await this.mock.$toUint256(1n)).is.equal(1n); - }); - - it(`casts INT256_MAX (${ethers.MaxInt256})`, async function () { - expect(await this.mock.$toUint256(ethers.MaxInt256)).is.equal(ethers.MaxInt256); - }); - - it('reverts when casting -1', async function () { - await expect(this.mock.$toUint256(-1n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntToUint') - .withArgs(-1n); - }); - - it(`reverts when casting INT256_MIN (${ethers.MinInt256})`, async function () { - await expect(this.mock.$toUint256(ethers.MinInt256)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntToUint') - .withArgs(ethers.MinInt256); - }); - }); - - for (const bits of range(8, 256, 8).map(ethers.toBigInt)) { - const minValue = -(2n ** (bits - 1n)); - const maxValue = 2n ** (bits - 1n) - 1n; - - describe(`toInt${bits}`, () => { - it('downcasts 0', async function () { - expect(await this.mock[`$toInt${bits}`](0n)).is.equal(0n); - }); - - it('downcasts 1', async function () { - expect(await this.mock[`$toInt${bits}`](1n)).is.equal(1n); - }); - - it('downcasts -1', async function () { - expect(await this.mock[`$toInt${bits}`](-1n)).is.equal(-1n); - }); - - it(`downcasts -2^${bits - 1n} (${minValue})`, async function () { - expect(await this.mock[`$toInt${bits}`](minValue)).is.equal(minValue); - }); - - it(`downcasts 2^${bits - 1n} - 1 (${maxValue})`, async function () { - expect(await this.mock[`$toInt${bits}`](maxValue)).is.equal(maxValue); - }); - - it(`reverts when downcasting -2^${bits - 1n} - 1 (${minValue - 1n})`, async function () { - await expect(this.mock[`$toInt${bits}`](minValue - 1n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast') - .withArgs(bits, minValue - 1n); - }); - - it(`reverts when downcasting -2^${bits - 1n} - 2 (${minValue - 2n})`, async function () { - await expect(this.mock[`$toInt${bits}`](minValue - 2n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast') - .withArgs(bits, minValue - 2n); - }); - - it(`reverts when downcasting 2^${bits - 1n} (${maxValue + 1n})`, async function () { - await expect(this.mock[`$toInt${bits}`](maxValue + 1n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast') - .withArgs(bits, maxValue + 1n); - }); - - it(`reverts when downcasting 2^${bits - 1n} + 1 (${maxValue + 2n})`, async function () { - await expect(this.mock[`$toInt${bits}`](maxValue + 2n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedIntDowncast') - .withArgs(bits, maxValue + 2n); - }); - }); - } - - describe('toInt256', () => { - it('casts 0', async function () { - expect(await this.mock.$toInt256(0)).is.equal(0n); - }); - - it('casts 1', async function () { - expect(await this.mock.$toInt256(1)).is.equal(1n); - }); - - it(`casts INT256_MAX (${ethers.MaxInt256})`, async function () { - expect(await this.mock.$toInt256(ethers.MaxInt256)).is.equal(ethers.MaxInt256); - }); - - it(`reverts when casting INT256_MAX + 1 (${ethers.MaxInt256 + 1n})`, async function () { - await expect(this.mock.$toInt256(ethers.MaxInt256 + 1n)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintToInt') - .withArgs(ethers.MaxInt256 + 1n); - }); - - it(`reverts when casting UINT256_MAX (${ethers.MaxUint256})`, async function () { - await expect(this.mock.$toInt256(ethers.MaxUint256)) - .to.be.revertedWithCustomError(this.mock, 'SafeCastOverflowedUintToInt') - .withArgs(ethers.MaxUint256); - }); - }); - - describe('toUint (bool)', function () { - it('toUint(false) should be 0', async function () { - expect(await this.mock.$toUint(false)).to.equal(0n); - }); - - it('toUint(true) should be 1', async function () { - expect(await this.mock.$toUint(true)).to.equal(1n); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/math/SignedMath.t.sol b/lib_openzeppelin_contracts/test/utils/math/SignedMath.t.sol deleted file mode 100644 index f1b7711..0000000 --- a/lib_openzeppelin_contracts/test/utils/math/SignedMath.t.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; - -import {Math} from "../../../contracts/utils/math/Math.sol"; -import {SignedMath} from "../../../contracts/utils/math/SignedMath.sol"; - -contract SignedMathTest is Test { - function testSelect(bool f, int256 a, int256 b) public { - assertEq(SignedMath.ternary(f, a, b), f ? a : b); - } - - // MIN & MAX - function testMinMax(int256 a, int256 b) public { - assertEq(SignedMath.min(a, b), a < b ? a : b); - assertEq(SignedMath.max(a, b), a > b ? a : b); - } - - // MIN - function testMin(int256 a, int256 b) public { - int256 result = SignedMath.min(a, b); - - assertLe(result, a); - assertLe(result, b); - assertTrue(result == a || result == b); - } - - // MAX - function testMax(int256 a, int256 b) public { - int256 result = SignedMath.max(a, b); - - assertGe(result, a); - assertGe(result, b); - assertTrue(result == a || result == b); - } - - // AVERAGE - // 1. simple test, not full int256 range - function testAverage1(int256 a, int256 b) public { - a = bound(a, type(int256).min / 2, type(int256).max / 2); - b = bound(b, type(int256).min / 2, type(int256).max / 2); - - int256 result = SignedMath.average(a, b); - - assertEq(result, (a + b) / 2); - } - - // 2. more complex test, full int256 range - function testAverage2(int256 a, int256 b) public { - (int256 result, int256 min, int256 max) = ( - SignedMath.average(a, b), - SignedMath.min(a, b), - SignedMath.max(a, b) - ); - - // average must be between `a` and `b` - assertGe(result, min); - assertLe(result, max); - - unchecked { - // must be unchecked in order to support `a = type(int256).min, b = type(int256).max` - uint256 deltaLower = uint256(result - min); - uint256 deltaUpper = uint256(max - result); - uint256 remainder = uint256((a & 1) ^ (b & 1)); - assertEq(remainder, Math.max(deltaLower, deltaUpper) - Math.min(deltaLower, deltaUpper)); - } - } - - // ABS - function testAbs(int256 a) public { - uint256 result = SignedMath.abs(a); - - unchecked { - // must be unchecked in order to support `n = type(int256).min` - assertEq(result, a < 0 ? uint256(-a) : uint256(a)); - } - } -} diff --git a/lib_openzeppelin_contracts/test/utils/math/SignedMath.test.js b/lib_openzeppelin_contracts/test/utils/math/SignedMath.test.js deleted file mode 100644 index 877f3b4..0000000 --- a/lib_openzeppelin_contracts/test/utils/math/SignedMath.test.js +++ /dev/null @@ -1,53 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { min, max } = require('../../helpers/math'); - -async function testCommutative(fn, lhs, rhs, expected, ...extra) { - expect(await fn(lhs, rhs, ...extra)).to.deep.equal(expected); - expect(await fn(rhs, lhs, ...extra)).to.deep.equal(expected); -} - -async function fixture() { - const mock = await ethers.deployContract('$SignedMath'); - return { mock }; -} - -describe('SignedMath', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('max', function () { - it('is correctly detected in both position', async function () { - await testCommutative(this.mock.$max, -1234n, 5678n, max(-1234n, 5678n)); - }); - }); - - describe('min', function () { - it('is correctly detected in both position', async function () { - await testCommutative(this.mock.$min, -1234n, 5678n, min(-1234n, 5678n)); - }); - }); - - describe('average', function () { - it('is correctly calculated with various input', async function () { - for (const x of [ethers.MinInt256, -57417n, -42304n, -4n, -3n, 0n, 3n, 4n, 42304n, 57417n, ethers.MaxInt256]) { - for (const y of [ethers.MinInt256, -57417n, -42304n, -5n, -2n, 0n, 2n, 5n, 42304n, 57417n, ethers.MaxInt256]) { - expect(await this.mock.$average(x, y)).to.equal((x + y) / 2n); - } - } - }); - }); - - describe('abs', function () { - const abs = x => (x < 0n ? -x : x); - - for (const n of [ethers.MinInt256, ethers.MinInt256 + 1n, -1n, 0n, 1n, ethers.MaxInt256 - 1n, ethers.MaxInt256]) { - it(`correctly computes the absolute value of ${n}`, async function () { - expect(await this.mock.$abs(n)).to.equal(abs(n)); - }); - } - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/structs/BitMap.test.js b/lib_openzeppelin_contracts/test/utils/structs/BitMap.test.js deleted file mode 100644 index 5662ab1..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/BitMap.test.js +++ /dev/null @@ -1,149 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -async function fixture() { - const bitmap = await ethers.deployContract('$BitMaps'); - return { bitmap }; -} - -describe('BitMap', function () { - const keyA = 7891n; - const keyB = 451n; - const keyC = 9592328n; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('starts empty', async function () { - expect(await this.bitmap.$get(0, keyA)).to.be.false; - expect(await this.bitmap.$get(0, keyB)).to.be.false; - expect(await this.bitmap.$get(0, keyC)).to.be.false; - }); - - describe('setTo', function () { - it('set a key to true', async function () { - await this.bitmap.$setTo(0, keyA, true); - expect(await this.bitmap.$get(0, keyA)).to.be.true; - expect(await this.bitmap.$get(0, keyB)).to.be.false; - expect(await this.bitmap.$get(0, keyC)).to.be.false; - }); - - it('set a key to false', async function () { - await this.bitmap.$setTo(0, keyA, true); - await this.bitmap.$setTo(0, keyA, false); - expect(await this.bitmap.$get(0, keyA)).to.be.false; - expect(await this.bitmap.$get(0, keyB)).to.be.false; - expect(await this.bitmap.$get(0, keyC)).to.be.false; - }); - - it('set several consecutive keys', async function () { - await this.bitmap.$setTo(0, keyA + 0n, true); - await this.bitmap.$setTo(0, keyA + 1n, true); - await this.bitmap.$setTo(0, keyA + 2n, true); - await this.bitmap.$setTo(0, keyA + 3n, true); - await this.bitmap.$setTo(0, keyA + 4n, true); - await this.bitmap.$setTo(0, keyA + 2n, false); - await this.bitmap.$setTo(0, keyA + 4n, false); - expect(await this.bitmap.$get(0, keyA + 0n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 1n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 2n)).to.be.false; - expect(await this.bitmap.$get(0, keyA + 3n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 4n)).to.be.false; - }); - }); - - describe('set', function () { - it('adds a key', async function () { - await this.bitmap.$set(0, keyA); - expect(await this.bitmap.$get(0, keyA)).to.be.true; - expect(await this.bitmap.$get(0, keyB)).to.be.false; - expect(await this.bitmap.$get(0, keyC)).to.be.false; - }); - - it('adds several keys', async function () { - await this.bitmap.$set(0, keyA); - await this.bitmap.$set(0, keyB); - expect(await this.bitmap.$get(0, keyA)).to.be.true; - expect(await this.bitmap.$get(0, keyB)).to.be.true; - expect(await this.bitmap.$get(0, keyC)).to.be.false; - }); - - it('adds several consecutive keys', async function () { - await this.bitmap.$set(0, keyA + 0n); - await this.bitmap.$set(0, keyA + 1n); - await this.bitmap.$set(0, keyA + 3n); - expect(await this.bitmap.$get(0, keyA + 0n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 1n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 2n)).to.be.false; - expect(await this.bitmap.$get(0, keyA + 3n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 4n)).to.be.false; - }); - }); - - describe('unset', function () { - it('removes added keys', async function () { - await this.bitmap.$set(0, keyA); - await this.bitmap.$set(0, keyB); - await this.bitmap.$unset(0, keyA); - expect(await this.bitmap.$get(0, keyA)).to.be.false; - expect(await this.bitmap.$get(0, keyB)).to.be.true; - expect(await this.bitmap.$get(0, keyC)).to.be.false; - }); - - it('removes consecutive added keys', async function () { - await this.bitmap.$set(0, keyA + 0n); - await this.bitmap.$set(0, keyA + 1n); - await this.bitmap.$set(0, keyA + 3n); - await this.bitmap.$unset(0, keyA + 1n); - expect(await this.bitmap.$get(0, keyA + 0n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 1n)).to.be.false; - expect(await this.bitmap.$get(0, keyA + 2n)).to.be.false; - expect(await this.bitmap.$get(0, keyA + 3n)).to.be.true; - expect(await this.bitmap.$get(0, keyA + 4n)).to.be.false; - }); - - it('adds and removes multiple keys', async function () { - // [] - - await this.bitmap.$set(0, keyA); - await this.bitmap.$set(0, keyC); - - // [A, C] - - await this.bitmap.$unset(0, keyA); - await this.bitmap.$unset(0, keyB); - - // [C] - - await this.bitmap.$set(0, keyB); - - // [C, B] - - await this.bitmap.$set(0, keyA); - await this.bitmap.$unset(0, keyC); - - // [A, B] - - await this.bitmap.$set(0, keyA); - await this.bitmap.$set(0, keyB); - - // [A, B] - - await this.bitmap.$set(0, keyC); - await this.bitmap.$unset(0, keyA); - - // [B, C] - - await this.bitmap.$set(0, keyA); - await this.bitmap.$unset(0, keyB); - - // [A, C] - - expect(await this.bitmap.$get(0, keyA)).to.be.true; - expect(await this.bitmap.$get(0, keyB)).to.be.false; - expect(await this.bitmap.$get(0, keyC)).to.be.true; - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/structs/Checkpoints.t.sol b/lib_openzeppelin_contracts/test/utils/structs/Checkpoints.t.sol deleted file mode 100644 index fba05a0..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/Checkpoints.t.sol +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: MIT -// This file was procedurally generated from scripts/generate/templates/Checkpoints.t.js. - -pragma solidity ^0.8.20; - -import {Test} from "@forge-std/Test.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; - -contract CheckpointsTrace224Test is Test { - using Checkpoints for Checkpoints.Trace224; - - // Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that - // key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. - uint8 internal constant _KEY_MAX_GAP = 64; - - Checkpoints.Trace224 internal _ckpts; - - // helpers - function _boundUint32(uint32 x, uint32 min, uint32 max) internal pure returns (uint32) { - return SafeCast.toUint32(bound(uint256(x), uint256(min), uint256(max))); - } - - function _prepareKeys(uint32[] memory keys, uint32 maxSpread) internal pure { - uint32 lastKey = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint32 key = _boundUint32(keys[i], lastKey, lastKey + maxSpread); - keys[i] = key; - lastKey = key; - } - } - - function _assertLatestCheckpoint(bool exist, uint32 key, uint224 value) internal { - (bool _exist, uint32 _key, uint224 _value) = _ckpts.latestCheckpoint(); - assertEq(_exist, exist); - assertEq(_key, key); - assertEq(_value, value); - } - - // tests - function testPush(uint32[] memory keys, uint224[] memory values, uint32 pastKey) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - // initial state - assertEq(_ckpts.length(), 0); - assertEq(_ckpts.latest(), 0); - _assertLatestCheckpoint(false, 0, 0); - - uint256 duplicates = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint32 key = keys[i]; - uint224 value = values[i % values.length]; - if (i > 0 && key == keys[i - 1]) ++duplicates; - - // push - _ckpts.push(key, value); - - // check length & latest - assertEq(_ckpts.length(), i + 1 - duplicates); - assertEq(_ckpts.latest(), value); - _assertLatestCheckpoint(true, key, value); - } - - if (keys.length > 0) { - uint32 lastKey = keys[keys.length - 1]; - if (lastKey > 0) { - pastKey = _boundUint32(pastKey, 0, lastKey - 1); - - vm.expectRevert(); - this.push(pastKey, values[keys.length % values.length]); - } - } - } - - // used to test reverts - function push(uint32 key, uint224 value) external { - _ckpts.push(key, value); - } - - function testLookup(uint32[] memory keys, uint224[] memory values, uint32 lookup) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - uint32 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; - lookup = _boundUint32(lookup, 0, lastKey + _KEY_MAX_GAP); - - uint224 upper = 0; - uint224 lower = 0; - uint32 lowerKey = type(uint32).max; - for (uint256 i = 0; i < keys.length; ++i) { - uint32 key = keys[i]; - uint224 value = values[i % values.length]; - - // push - _ckpts.push(key, value); - - // track expected result of lookups - if (key <= lookup) { - upper = value; - } - // find the first key that is not smaller than the lookup key - if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) { - lowerKey = key; - } - if (key == lowerKey) { - lower = value; - } - } - - // check lookup - assertEq(_ckpts.lowerLookup(lookup), lower); - assertEq(_ckpts.upperLookup(lookup), upper); - assertEq(_ckpts.upperLookupRecent(lookup), upper); - } -} - -contract CheckpointsTrace208Test is Test { - using Checkpoints for Checkpoints.Trace208; - - // Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that - // key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. - uint8 internal constant _KEY_MAX_GAP = 64; - - Checkpoints.Trace208 internal _ckpts; - - // helpers - function _boundUint48(uint48 x, uint48 min, uint48 max) internal pure returns (uint48) { - return SafeCast.toUint48(bound(uint256(x), uint256(min), uint256(max))); - } - - function _prepareKeys(uint48[] memory keys, uint48 maxSpread) internal pure { - uint48 lastKey = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint48 key = _boundUint48(keys[i], lastKey, lastKey + maxSpread); - keys[i] = key; - lastKey = key; - } - } - - function _assertLatestCheckpoint(bool exist, uint48 key, uint208 value) internal { - (bool _exist, uint48 _key, uint208 _value) = _ckpts.latestCheckpoint(); - assertEq(_exist, exist); - assertEq(_key, key); - assertEq(_value, value); - } - - // tests - function testPush(uint48[] memory keys, uint208[] memory values, uint48 pastKey) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - // initial state - assertEq(_ckpts.length(), 0); - assertEq(_ckpts.latest(), 0); - _assertLatestCheckpoint(false, 0, 0); - - uint256 duplicates = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint48 key = keys[i]; - uint208 value = values[i % values.length]; - if (i > 0 && key == keys[i - 1]) ++duplicates; - - // push - _ckpts.push(key, value); - - // check length & latest - assertEq(_ckpts.length(), i + 1 - duplicates); - assertEq(_ckpts.latest(), value); - _assertLatestCheckpoint(true, key, value); - } - - if (keys.length > 0) { - uint48 lastKey = keys[keys.length - 1]; - if (lastKey > 0) { - pastKey = _boundUint48(pastKey, 0, lastKey - 1); - - vm.expectRevert(); - this.push(pastKey, values[keys.length % values.length]); - } - } - } - - // used to test reverts - function push(uint48 key, uint208 value) external { - _ckpts.push(key, value); - } - - function testLookup(uint48[] memory keys, uint208[] memory values, uint48 lookup) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - uint48 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; - lookup = _boundUint48(lookup, 0, lastKey + _KEY_MAX_GAP); - - uint208 upper = 0; - uint208 lower = 0; - uint48 lowerKey = type(uint48).max; - for (uint256 i = 0; i < keys.length; ++i) { - uint48 key = keys[i]; - uint208 value = values[i % values.length]; - - // push - _ckpts.push(key, value); - - // track expected result of lookups - if (key <= lookup) { - upper = value; - } - // find the first key that is not smaller than the lookup key - if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) { - lowerKey = key; - } - if (key == lowerKey) { - lower = value; - } - } - - // check lookup - assertEq(_ckpts.lowerLookup(lookup), lower); - assertEq(_ckpts.upperLookup(lookup), upper); - assertEq(_ckpts.upperLookupRecent(lookup), upper); - } -} - -contract CheckpointsTrace160Test is Test { - using Checkpoints for Checkpoints.Trace160; - - // Maximum gap between keys used during the fuzzing tests: the `_prepareKeys` function with make sure that - // key#n+1 is in the [key#n, key#n + _KEY_MAX_GAP] range. - uint8 internal constant _KEY_MAX_GAP = 64; - - Checkpoints.Trace160 internal _ckpts; - - // helpers - function _boundUint96(uint96 x, uint96 min, uint96 max) internal pure returns (uint96) { - return SafeCast.toUint96(bound(uint256(x), uint256(min), uint256(max))); - } - - function _prepareKeys(uint96[] memory keys, uint96 maxSpread) internal pure { - uint96 lastKey = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint96 key = _boundUint96(keys[i], lastKey, lastKey + maxSpread); - keys[i] = key; - lastKey = key; - } - } - - function _assertLatestCheckpoint(bool exist, uint96 key, uint160 value) internal { - (bool _exist, uint96 _key, uint160 _value) = _ckpts.latestCheckpoint(); - assertEq(_exist, exist); - assertEq(_key, key); - assertEq(_value, value); - } - - // tests - function testPush(uint96[] memory keys, uint160[] memory values, uint96 pastKey) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - // initial state - assertEq(_ckpts.length(), 0); - assertEq(_ckpts.latest(), 0); - _assertLatestCheckpoint(false, 0, 0); - - uint256 duplicates = 0; - for (uint256 i = 0; i < keys.length; ++i) { - uint96 key = keys[i]; - uint160 value = values[i % values.length]; - if (i > 0 && key == keys[i - 1]) ++duplicates; - - // push - _ckpts.push(key, value); - - // check length & latest - assertEq(_ckpts.length(), i + 1 - duplicates); - assertEq(_ckpts.latest(), value); - _assertLatestCheckpoint(true, key, value); - } - - if (keys.length > 0) { - uint96 lastKey = keys[keys.length - 1]; - if (lastKey > 0) { - pastKey = _boundUint96(pastKey, 0, lastKey - 1); - - vm.expectRevert(); - this.push(pastKey, values[keys.length % values.length]); - } - } - } - - // used to test reverts - function push(uint96 key, uint160 value) external { - _ckpts.push(key, value); - } - - function testLookup(uint96[] memory keys, uint160[] memory values, uint96 lookup) public { - vm.assume(values.length > 0 && values.length <= keys.length); - _prepareKeys(keys, _KEY_MAX_GAP); - - uint96 lastKey = keys.length == 0 ? 0 : keys[keys.length - 1]; - lookup = _boundUint96(lookup, 0, lastKey + _KEY_MAX_GAP); - - uint160 upper = 0; - uint160 lower = 0; - uint96 lowerKey = type(uint96).max; - for (uint256 i = 0; i < keys.length; ++i) { - uint96 key = keys[i]; - uint160 value = values[i % values.length]; - - // push - _ckpts.push(key, value); - - // track expected result of lookups - if (key <= lookup) { - upper = value; - } - // find the first key that is not smaller than the lookup key - if (key >= lookup && (i == 0 || keys[i - 1] < lookup)) { - lowerKey = key; - } - if (key == lowerKey) { - lower = value; - } - } - - // check lookup - assertEq(_ckpts.lowerLookup(lookup), lower); - assertEq(_ckpts.upperLookup(lookup), upper); - assertEq(_ckpts.upperLookupRecent(lookup), upper); - } -} diff --git a/lib_openzeppelin_contracts/test/utils/structs/Checkpoints.test.js b/lib_openzeppelin_contracts/test/utils/structs/Checkpoints.test.js deleted file mode 100644 index fd22544..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/Checkpoints.test.js +++ /dev/null @@ -1,146 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { VALUE_SIZES } = require('../../../scripts/generate/templates/Checkpoints.opts'); - -describe('Checkpoints', function () { - for (const length of VALUE_SIZES) { - describe(`Trace${length}`, function () { - const fixture = async () => { - const mock = await ethers.deployContract('$Checkpoints'); - const methods = { - at: (...args) => mock.getFunction(`$at_Checkpoints_Trace${length}`)(0, ...args), - latest: (...args) => mock.getFunction(`$latest_Checkpoints_Trace${length}`)(0, ...args), - latestCheckpoint: (...args) => mock.getFunction(`$latestCheckpoint_Checkpoints_Trace${length}`)(0, ...args), - length: (...args) => mock.getFunction(`$length_Checkpoints_Trace${length}`)(0, ...args), - push: (...args) => mock.getFunction(`$push(uint256,uint${256 - length},uint${length})`)(0, ...args), - lowerLookup: (...args) => mock.getFunction(`$lowerLookup(uint256,uint${256 - length})`)(0, ...args), - upperLookup: (...args) => mock.getFunction(`$upperLookup(uint256,uint${256 - length})`)(0, ...args), - upperLookupRecent: (...args) => - mock.getFunction(`$upperLookupRecent(uint256,uint${256 - length})`)(0, ...args), - }; - - return { mock, methods }; - }; - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('without checkpoints', function () { - it('at zero reverts', async function () { - // Reverts with array out of bound access, which is unspecified - await expect(this.methods.at(0)).to.be.reverted; - }); - - it('returns zero as latest value', async function () { - expect(await this.methods.latest()).to.equal(0n); - - const ckpt = await this.methods.latestCheckpoint(); - expect(ckpt[0]).to.be.false; - expect(ckpt[1]).to.equal(0n); - expect(ckpt[2]).to.equal(0n); - }); - - it('lookup returns 0', async function () { - expect(await this.methods.lowerLookup(0)).to.equal(0n); - expect(await this.methods.upperLookup(0)).to.equal(0n); - expect(await this.methods.upperLookupRecent(0)).to.equal(0n); - }); - }); - - describe('with checkpoints', function () { - beforeEach('pushing checkpoints', async function () { - this.checkpoints = [ - { key: 2n, value: 17n }, - { key: 3n, value: 42n }, - { key: 5n, value: 101n }, - { key: 7n, value: 23n }, - { key: 11n, value: 99n }, - ]; - for (const { key, value } of this.checkpoints) { - await this.methods.push(key, value); - } - }); - - it('at keys', async function () { - for (const [index, { key, value }] of this.checkpoints.entries()) { - const at = await this.methods.at(index); - expect(at._value).to.equal(value); - expect(at._key).to.equal(key); - } - }); - - it('length', async function () { - expect(await this.methods.length()).to.equal(this.checkpoints.length); - }); - - it('returns latest value', async function () { - const latest = this.checkpoints.at(-1); - expect(await this.methods.latest()).to.equal(latest.value); - expect(await this.methods.latestCheckpoint()).to.deep.equal([true, latest.key, latest.value]); - }); - - it('cannot push values in the past', async function () { - await expect(this.methods.push(this.checkpoints.at(-1).key - 1n, 0n)).to.be.revertedWithCustomError( - this.mock, - 'CheckpointUnorderedInsertion', - ); - }); - - it('can update last value', async function () { - const newValue = 42n; - - // check length before the update - expect(await this.methods.length()).to.equal(this.checkpoints.length); - - // update last key - await this.methods.push(this.checkpoints.at(-1).key, newValue); - expect(await this.methods.latest()).to.equal(newValue); - - // check that length did not change - expect(await this.methods.length()).to.equal(this.checkpoints.length); - }); - - it('lower lookup', async function () { - for (let i = 0; i < 14; ++i) { - const value = this.checkpoints.find(x => i <= x.key)?.value || 0n; - - expect(await this.methods.lowerLookup(i)).to.equal(value); - } - }); - - it('upper lookup & upperLookupRecent', async function () { - for (let i = 0; i < 14; ++i) { - const value = this.checkpoints.findLast(x => i >= x.key)?.value || 0n; - - expect(await this.methods.upperLookup(i)).to.equal(value); - expect(await this.methods.upperLookupRecent(i)).to.equal(value); - } - }); - - it('upperLookupRecent with more than 5 checkpoints', async function () { - const moreCheckpoints = [ - { key: 12n, value: 22n }, - { key: 13n, value: 131n }, - { key: 17n, value: 45n }, - { key: 19n, value: 31452n }, - { key: 21n, value: 0n }, - ]; - const allCheckpoints = [].concat(this.checkpoints, moreCheckpoints); - - for (const { key, value } of moreCheckpoints) { - await this.methods.push(key, value); - } - - for (let i = 0; i < 25; ++i) { - const value = allCheckpoints.findLast(x => i >= x.key)?.value || 0n; - expect(await this.methods.upperLookup(i)).to.equal(value); - expect(await this.methods.upperLookupRecent(i)).to.equal(value); - } - }); - }); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/utils/structs/CircularBuffer.test.js b/lib_openzeppelin_contracts/test/utils/structs/CircularBuffer.test.js deleted file mode 100644 index c3d61aa..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/CircularBuffer.test.js +++ /dev/null @@ -1,79 +0,0 @@ -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 { generators } = require('../../helpers/random'); - -const LENGTH = 4; - -async function fixture() { - const mock = await ethers.deployContract('$CircularBuffer'); - await mock.$setup(0, LENGTH); - return { mock }; -} - -describe('CircularBuffer', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('starts empty', async function () { - expect(await this.mock.$count(0)).to.equal(0n); - expect(await this.mock.$length(0)).to.equal(LENGTH); - expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false; - await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - }); - - it('push', async function () { - const values = Array.from({ length: LENGTH + 3 }, generators.bytes32); - - for (const [i, value] of values.map((v, i) => [i, v])) { - // push value - await this.mock.$push(0, value); - - // view of the values - const pushed = values.slice(0, i + 1); - const stored = pushed.slice(-LENGTH); - const dropped = pushed.slice(0, -LENGTH); - - // check count - expect(await this.mock.$length(0)).to.equal(LENGTH); - expect(await this.mock.$count(0)).to.equal(stored.length); - - // check last - for (const j in stored) { - expect(await this.mock.$last(0, j)).to.equal(stored.at(-j - 1)); - } - await expect(this.mock.$last(0, stored.length + 1)).to.be.revertedWithPanic( - PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS, - ); - - // check included and non-included values - for (const v of stored) { - expect(await this.mock.$includes(0, v)).to.be.true; - } - for (const v of dropped) { - expect(await this.mock.$includes(0, v)).to.be.false; - } - expect(await this.mock.$includes(0, ethers.ZeroHash)).to.be.false; - } - }); - - it('clear', async function () { - const value = generators.bytes32(); - await this.mock.$push(0, value); - - expect(await this.mock.$count(0)).to.equal(1n); - expect(await this.mock.$length(0)).to.equal(LENGTH); - expect(await this.mock.$includes(0, value)).to.be.true; - await this.mock.$last(0, 0); // not revert - - await this.mock.$clear(0); - - expect(await this.mock.$count(0)).to.equal(0n); - expect(await this.mock.$length(0)).to.equal(LENGTH); - expect(await this.mock.$includes(0, value)).to.be.false; - await expect(this.mock.$last(0, 0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/structs/DoubleEndedQueue.test.js b/lib_openzeppelin_contracts/test/utils/structs/DoubleEndedQueue.test.js deleted file mode 100644 index 3615dfb..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/DoubleEndedQueue.test.js +++ /dev/null @@ -1,102 +0,0 @@ -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('$DoubleEndedQueue'); - - /** Rebuild the content of the deque as a JS array. */ - const getContent = () => - mock.$length(0).then(length => Promise.all(Array.from({ length: Number(length) }, (_, i) => mock.$at(0, i)))); - - return { mock, getContent }; -} - -describe('DoubleEndedQueue', function () { - const coder = ethers.AbiCoder.defaultAbiCoder(); - const bytesA = coder.encode(['uint256'], [0xdeadbeef]); - const bytesB = coder.encode(['uint256'], [0x0123456789]); - const bytesC = coder.encode(['uint256'], [0x42424242]); - const bytesD = coder.encode(['uint256'], [0x171717]); - - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('when empty', function () { - it('getters', async function () { - expect(await this.mock.$empty(0)).to.be.true; - expect(await this.getContent()).to.have.ordered.members([]); - }); - - it('reverts on accesses', async function () { - await expect(this.mock.$popBack(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - await expect(this.mock.$popFront(0)).to.be.revertedWithPanic(PANIC_CODES.POP_ON_EMPTY_ARRAY); - await expect(this.mock.$back(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - await expect(this.mock.$front(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - }); - }); - - describe('when not empty', function () { - beforeEach(async function () { - await this.mock.$pushBack(0, bytesB); - await this.mock.$pushFront(0, bytesA); - await this.mock.$pushBack(0, bytesC); - this.content = [bytesA, bytesB, bytesC]; - }); - - it('getters', async function () { - expect(await this.mock.$empty(0)).to.be.false; - expect(await this.mock.$length(0)).to.equal(this.content.length); - expect(await this.mock.$front(0)).to.equal(this.content[0]); - expect(await this.mock.$back(0)).to.equal(this.content[this.content.length - 1]); - expect(await this.getContent()).to.have.ordered.members(this.content); - }); - - it('out of bounds access', async function () { - await expect(this.mock.$at(0, this.content.length)).to.be.revertedWithPanic( - PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS, - ); - }); - - describe('push', function () { - it('front', async function () { - await this.mock.$pushFront(0, bytesD); - this.content.unshift(bytesD); // add element at the beginning - - expect(await this.getContent()).to.have.ordered.members(this.content); - }); - - it('back', async function () { - await this.mock.$pushBack(0, bytesD); - this.content.push(bytesD); // add element at the end - - expect(await this.getContent()).to.have.ordered.members(this.content); - }); - }); - - describe('pop', function () { - it('front', async function () { - const value = this.content.shift(); // remove first element - await expect(this.mock.$popFront(0)).to.emit(this.mock, 'return$popFront').withArgs(value); - - expect(await this.getContent()).to.have.ordered.members(this.content); - }); - - it('back', async function () { - const value = this.content.pop(); // remove last element - await expect(this.mock.$popBack(0)).to.emit(this.mock, 'return$popBack').withArgs(value); - - expect(await this.getContent()).to.have.ordered.members(this.content); - }); - }); - - it('clear', async function () { - await this.mock.$clear(0); - - expect(await this.mock.$empty(0)).to.be.true; - expect(await this.getContent()).to.have.ordered.members([]); - }); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/structs/EnumerableMap.behavior.js b/lib_openzeppelin_contracts/test/utils/structs/EnumerableMap.behavior.js deleted file mode 100644 index 37da417..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/EnumerableMap.behavior.js +++ /dev/null @@ -1,151 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); - -const zip = (array1, array2) => array1.map((item, index) => [item, array2[index]]); - -function shouldBehaveLikeMap() { - async function expectMembersMatch(methods, keys, values) { - expect(keys.length).to.equal(values.length); - expect(await methods.length()).to.equal(keys.length); - expect([...(await methods.keys())]).to.have.members(keys); - - for (const [key, value] of zip(keys, values)) { - expect(await methods.contains(key)).to.be.true; - expect(await methods.get(key)).to.equal(value); - } - - expect(await Promise.all(keys.map((_, index) => methods.at(index)))).to.have.deep.members(zip(keys, values)); - } - - it('starts empty', async function () { - expect(await this.methods.contains(this.keyA)).to.be.false; - - await expectMembersMatch(this.methods, [], []); - }); - - describe('set', function () { - it('adds a key', async function () { - await expect(this.methods.set(this.keyA, this.valueA)).to.emit(this.mock, this.events.setReturn).withArgs(true); - - await expectMembersMatch(this.methods, [this.keyA], [this.valueA]); - }); - - it('adds several keys', async function () { - await this.methods.set(this.keyA, this.valueA); - await this.methods.set(this.keyB, this.valueB); - - await expectMembersMatch(this.methods, [this.keyA, this.keyB], [this.valueA, this.valueB]); - expect(await this.methods.contains(this.keyC)).to.be.false; - }); - - it('returns false when adding keys already in the set', async function () { - await this.methods.set(this.keyA, this.valueA); - - await expect(this.methods.set(this.keyA, this.valueA)).to.emit(this.mock, this.events.setReturn).withArgs(false); - - await expectMembersMatch(this.methods, [this.keyA], [this.valueA]); - }); - - it('updates values for keys already in the set', async function () { - await this.methods.set(this.keyA, this.valueA); - await this.methods.set(this.keyA, this.valueB); - - await expectMembersMatch(this.methods, [this.keyA], [this.valueB]); - }); - }); - - describe('remove', function () { - it('removes added keys', async function () { - await this.methods.set(this.keyA, this.valueA); - - await expect(this.methods.remove(this.keyA)).to.emit(this.mock, this.events.removeReturn).withArgs(true); - - expect(await this.methods.contains(this.keyA)).to.be.false; - await expectMembersMatch(this.methods, [], []); - }); - - it('returns false when removing keys not in the set', async function () { - await expect(await this.methods.remove(this.keyA)) - .to.emit(this.mock, this.events.removeReturn) - .withArgs(false); - - expect(await this.methods.contains(this.keyA)).to.be.false; - }); - - it('adds and removes multiple keys', async function () { - // [] - - await this.methods.set(this.keyA, this.valueA); - await this.methods.set(this.keyC, this.valueC); - - // [A, C] - - await this.methods.remove(this.keyA); - await this.methods.remove(this.keyB); - - // [C] - - await this.methods.set(this.keyB, this.valueB); - - // [C, B] - - await this.methods.set(this.keyA, this.valueA); - await this.methods.remove(this.keyC); - - // [A, B] - - await this.methods.set(this.keyA, this.valueA); - await this.methods.set(this.keyB, this.valueB); - - // [A, B] - - await this.methods.set(this.keyC, this.valueC); - await this.methods.remove(this.keyA); - - // [B, C] - - await this.methods.set(this.keyA, this.valueA); - await this.methods.remove(this.keyB); - - // [A, C] - - await expectMembersMatch(this.methods, [this.keyA, this.keyC], [this.valueA, this.valueC]); - - expect(await this.methods.contains(this.keyA)).to.be.true; - expect(await this.methods.contains(this.keyB)).to.be.false; - expect(await this.methods.contains(this.keyC)).to.be.true; - }); - }); - - describe('read', function () { - beforeEach(async function () { - await this.methods.set(this.keyA, this.valueA); - }); - - describe('get', function () { - it('existing value', async function () { - expect(await this.methods.get(this.keyA)).to.equal(this.valueA); - }); - - it('missing value', async function () { - await expect(this.methods.get(this.keyB)) - .to.be.revertedWithCustomError(this.mock, 'EnumerableMapNonexistentKey') - .withArgs(ethers.AbiCoder.defaultAbiCoder().encode([this.keyType], [this.keyB])); - }); - }); - - describe('tryGet', function () { - it('existing value', async function () { - expect(await this.methods.tryGet(this.keyA)).to.have.ordered.members([true, this.valueA]); - }); - - it('missing value', async function () { - expect(await this.methods.tryGet(this.keyB)).to.have.ordered.members([false, this.zeroValue]); - }); - }); - }); -} - -module.exports = { - shouldBehaveLikeMap, -}; diff --git a/lib_openzeppelin_contracts/test/utils/structs/EnumerableMap.test.js b/lib_openzeppelin_contracts/test/utils/structs/EnumerableMap.test.js deleted file mode 100644 index 5362e87..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/EnumerableMap.test.js +++ /dev/null @@ -1,65 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { mapValues } = require('../../helpers/iterate'); -const { generators } = require('../../helpers/random'); -const { TYPES, formatType } = require('../../../scripts/generate/templates/EnumerableMap.opts'); - -const { shouldBehaveLikeMap } = require('./EnumerableMap.behavior'); - -// Add Bytes32ToBytes32Map that must be tested but is not part of the generated types. -TYPES.unshift(formatType('bytes32', 'bytes32')); - -async function fixture() { - const mock = await ethers.deployContract('$EnumerableMap'); - const env = Object.fromEntries( - TYPES.map(({ name, keyType, valueType }) => [ - name, - { - keyType, - keys: Array.from({ length: 3 }, generators[keyType]), - values: Array.from({ length: 3 }, generators[valueType]), - zeroValue: generators[valueType].zero, - methods: mapValues( - { - set: `$set(uint256,${keyType},${valueType})`, - get: `$get_EnumerableMap_${name}(uint256,${keyType})`, - tryGet: `$tryGet_EnumerableMap_${name}(uint256,${keyType})`, - remove: `$remove_EnumerableMap_${name}(uint256,${keyType})`, - length: `$length_EnumerableMap_${name}(uint256)`, - at: `$at_EnumerableMap_${name}(uint256,uint256)`, - contains: `$contains_EnumerableMap_${name}(uint256,${keyType})`, - keys: `$keys_EnumerableMap_${name}(uint256)`, - }, - fnSig => - (...args) => - mock.getFunction(fnSig)(0, ...args), - ), - events: { - setReturn: `return$set_EnumerableMap_${name}_${keyType}_${valueType}`, - removeReturn: `return$remove_EnumerableMap_${name}_${keyType}`, - }, - }, - ]), - ); - - return { mock, env }; -} - -describe('EnumerableMap', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - for (const { name } of TYPES) { - describe(name, function () { - beforeEach(async function () { - Object.assign(this, this.env[name]); - [this.keyA, this.keyB, this.keyC] = this.keys; - [this.valueA, this.valueB, this.valueC] = this.values; - }); - - shouldBehaveLikeMap(); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/utils/structs/EnumerableSet.behavior.js b/lib_openzeppelin_contracts/test/utils/structs/EnumerableSet.behavior.js deleted file mode 100644 index d3d4f26..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/EnumerableSet.behavior.js +++ /dev/null @@ -1,116 +0,0 @@ -const { expect } = require('chai'); -const { PANIC_CODES } = require('@nomicfoundation/hardhat-chai-matchers/panic'); - -function shouldBehaveLikeSet() { - async function expectMembersMatch(methods, values) { - expect(await methods.length()).to.equal(values.length); - for (const value of values) expect(await methods.contains(value)).to.be.true; - - expect(await Promise.all(values.map((_, index) => methods.at(index)))).to.have.deep.members(values); - expect([...(await methods.values())]).to.have.deep.members(values); - } - - it('starts empty', async function () { - expect(await this.methods.contains(this.valueA)).to.be.false; - - await expectMembersMatch(this.methods, []); - }); - - describe('add', function () { - it('adds a value', async function () { - await expect(this.methods.add(this.valueA)).to.emit(this.mock, this.events.addReturn).withArgs(true); - - await expectMembersMatch(this.methods, [this.valueA]); - }); - - it('adds several values', async function () { - await this.methods.add(this.valueA); - await this.methods.add(this.valueB); - - await expectMembersMatch(this.methods, [this.valueA, this.valueB]); - expect(await this.methods.contains(this.valueC)).to.be.false; - }); - - it('returns false when adding values already in the set', async function () { - await this.methods.add(this.valueA); - - await expect(this.methods.add(this.valueA)).to.emit(this.mock, this.events.addReturn).withArgs(false); - - await expectMembersMatch(this.methods, [this.valueA]); - }); - }); - - describe('at', function () { - it('reverts when retrieving non-existent elements', async function () { - await expect(this.methods.at(0)).to.be.revertedWithPanic(PANIC_CODES.ARRAY_ACCESS_OUT_OF_BOUNDS); - }); - - it('retrieves existing element', async function () { - await this.methods.add(this.valueA); - expect(await this.methods.at(0)).to.equal(this.valueA); - }); - }); - - describe('remove', function () { - it('removes added values', async function () { - await this.methods.add(this.valueA); - - await expect(this.methods.remove(this.valueA)).to.emit(this.mock, this.events.removeReturn).withArgs(true); - - expect(await this.methods.contains(this.valueA)).to.be.false; - await expectMembersMatch(this.methods, []); - }); - - it('returns false when removing values not in the set', async function () { - await expect(this.methods.remove(this.valueA)).to.emit(this.mock, this.events.removeReturn).withArgs(false); - - expect(await this.methods.contains(this.valueA)).to.be.false; - }); - - it('adds and removes multiple values', async function () { - // [] - - await this.methods.add(this.valueA); - await this.methods.add(this.valueC); - - // [A, C] - - await this.methods.remove(this.valueA); - await this.methods.remove(this.valueB); - - // [C] - - await this.methods.add(this.valueB); - - // [C, B] - - await this.methods.add(this.valueA); - await this.methods.remove(this.valueC); - - // [A, B] - - await this.methods.add(this.valueA); - await this.methods.add(this.valueB); - - // [A, B] - - await this.methods.add(this.valueC); - await this.methods.remove(this.valueA); - - // [B, C] - - await this.methods.add(this.valueA); - await this.methods.remove(this.valueB); - - // [A, C] - - await expectMembersMatch(this.methods, [this.valueA, this.valueC]); - - expect(await this.methods.contains(this.valueB)).to.be.false; - }); - }); -} - -module.exports = { - shouldBehaveLikeSet, -}; diff --git a/lib_openzeppelin_contracts/test/utils/structs/EnumerableSet.test.js b/lib_openzeppelin_contracts/test/utils/structs/EnumerableSet.test.js deleted file mode 100644 index 66d6660..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/EnumerableSet.test.js +++ /dev/null @@ -1,61 +0,0 @@ -const { ethers } = require('hardhat'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { mapValues } = require('../../helpers/iterate'); -const { generators } = require('../../helpers/random'); -const { TYPES } = require('../../../scripts/generate/templates/EnumerableSet.opts'); - -const { shouldBehaveLikeSet } = require('./EnumerableSet.behavior'); - -const getMethods = (mock, fnSigs) => { - return mapValues( - fnSigs, - fnSig => - (...args) => - mock.getFunction(fnSig)(0, ...args), - ); -}; - -async function fixture() { - const mock = await ethers.deployContract('$EnumerableSet'); - - const env = Object.fromEntries( - TYPES.map(({ name, type }) => [ - type, - { - values: Array.from({ length: 3 }, generators[type]), - methods: getMethods(mock, { - add: `$add(uint256,${type})`, - remove: `$remove(uint256,${type})`, - contains: `$contains(uint256,${type})`, - length: `$length_EnumerableSet_${name}(uint256)`, - at: `$at_EnumerableSet_${name}(uint256,uint256)`, - values: `$values_EnumerableSet_${name}(uint256)`, - }), - events: { - addReturn: `return$add_EnumerableSet_${name}_${type}`, - removeReturn: `return$remove_EnumerableSet_${name}_${type}`, - }, - }, - ]), - ); - - return { mock, env }; -} - -describe('EnumerableSet', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - for (const { type } of TYPES) { - describe(type, function () { - beforeEach(function () { - Object.assign(this, this.env[type]); - [this.valueA, this.valueB, this.valueC] = this.values; - }); - - shouldBehaveLikeSet(); - }); - } -}); diff --git a/lib_openzeppelin_contracts/test/utils/structs/MerkleTree.test.js b/lib_openzeppelin_contracts/test/utils/structs/MerkleTree.test.js deleted file mode 100644 index 2603ac2..0000000 --- a/lib_openzeppelin_contracts/test/utils/structs/MerkleTree.test.js +++ /dev/null @@ -1,100 +0,0 @@ -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 { StandardMerkleTree } = require('@openzeppelin/merkle-tree'); - -const { generators } = require('../../helpers/random'); - -const makeTree = (leafs = [ethers.ZeroHash]) => - StandardMerkleTree.of( - leafs.map(leaf => [leaf]), - ['bytes32'], - { sortLeaves: false }, - ); - -const hashLeaf = leaf => makeTree().leafHash([leaf]); - -const DEPTH = 4n; // 16 slots -const ZERO = hashLeaf(ethers.ZeroHash); - -async function fixture() { - const mock = await ethers.deployContract('MerkleTreeMock'); - await mock.setup(DEPTH, ZERO); - return { mock }; -} - -describe('MerkleTree', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - it('sets initial values at setup', async function () { - const merkleTree = makeTree(Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash)); - - expect(await this.mock.root()).to.equal(merkleTree.root); - expect(await this.mock.depth()).to.equal(DEPTH); - expect(await this.mock.nextLeafIndex()).to.equal(0n); - }); - - describe('push', function () { - it('tree is correctly updated', async function () { - const leafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); - - // for each leaf slot - for (const i in leafs) { - // generate random leaf and hash it - const hashedLeaf = hashLeaf((leafs[i] = generators.bytes32())); - - // update leaf list and rebuild tree. - const tree = makeTree(leafs); - - // push value to tree - await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, i, tree.root); - - // check tree - expect(await this.mock.root()).to.equal(tree.root); - expect(await this.mock.nextLeafIndex()).to.equal(BigInt(i) + 1n); - } - }); - - it('revert when tree is full', async function () { - await Promise.all(Array.from({ length: 2 ** Number(DEPTH) }).map(() => this.mock.push(ethers.ZeroHash))); - - await expect(this.mock.push(ethers.ZeroHash)).to.be.revertedWithPanic(PANIC_CODES.TOO_MUCH_MEMORY_ALLOCATED); - }); - }); - - it('reset', async function () { - // empty tree - const zeroLeafs = Array.from({ length: 2 ** Number(DEPTH) }, () => ethers.ZeroHash); - const zeroTree = makeTree(zeroLeafs); - - // 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); - - // root should be that of a zero tree - expect(await this.mock.root()).to.equal(zeroTree.root); - expect(await this.mock.nextLeafIndex()).to.equal(0n); - - // push leaf and check root - await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root); - - expect(await this.mock.root()).to.equal(tree.root); - expect(await this.mock.nextLeafIndex()).to.equal(1n); - - // reset tree - await this.mock.setup(DEPTH, ZERO); - - expect(await this.mock.root()).to.equal(zeroTree.root); - expect(await this.mock.nextLeafIndex()).to.equal(0n); - - // re-push leaf and check root - await expect(this.mock.push(hashedLeaf)).to.emit(this.mock, 'LeafInserted').withArgs(hashedLeaf, 0, tree.root); - - expect(await this.mock.root()).to.equal(tree.root); - expect(await this.mock.nextLeafIndex()).to.equal(1n); - }); -}); diff --git a/lib_openzeppelin_contracts/test/utils/types/Time.test.js b/lib_openzeppelin_contracts/test/utils/types/Time.test.js deleted file mode 100644 index 3ab6fef..0000000 --- a/lib_openzeppelin_contracts/test/utils/types/Time.test.js +++ /dev/null @@ -1,135 +0,0 @@ -const { ethers } = require('hardhat'); -const { expect } = require('chai'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); - -const { product } = require('../../helpers/iterate'); -const { max } = require('../../helpers/math'); -const time = require('../../helpers/time'); - -const MAX_UINT32 = 1n << (32n - 1n); -const MAX_UINT48 = 1n << (48n - 1n); -const SOME_VALUES = [0n, 1n, 2n, 15n, 16n, 17n, 42n]; - -const asUint = (value, size) => { - value = ethers.toBigInt(value); - size = ethers.toBigInt(size); - expect(value).to.be.greaterThanOrEqual(0n, `value is not a valid uint${size}`); - expect(value).to.be.lessThan(1n << size, `value is not a valid uint${size}`); - return value; -}; - -const unpackDelay = delay => ({ - valueBefore: (asUint(delay, 112) >> 32n) % (1n << 32n), - valueAfter: (asUint(delay, 112) >> 0n) % (1n << 32n), - effect: (asUint(delay, 112) >> 64n) % (1n << 48n), -}); - -const packDelay = ({ valueBefore, valueAfter = 0n, effect = 0n }) => - (asUint(valueAfter, 32) << 0n) + (asUint(valueBefore, 32) << 32n) + (asUint(effect, 48) << 64n); - -const effectSamplesForTimepoint = timepoint => [ - 0n, - timepoint, - ...product([-1n, 1n], [1n, 2n, 17n, 42n]) - .map(([sign, shift]) => timepoint + sign * shift) - .filter(effect => effect > 0n && effect <= MAX_UINT48), - MAX_UINT48, -]; - -async function fixture() { - const mock = await ethers.deployContract('$Time'); - return { mock }; -} - -describe('Time', function () { - beforeEach(async function () { - Object.assign(this, await loadFixture(fixture)); - }); - - describe('clocks', function () { - it('timestamp', async function () { - expect(await this.mock.$timestamp()).to.equal(await time.clock.timestamp()); - }); - - it('block number', async function () { - expect(await this.mock.$blockNumber()).to.equal(await time.clock.blocknumber()); - }); - }); - - describe('Delay', function () { - describe('packing and unpacking', function () { - const valueBefore = 17n; - const valueAfter = 42n; - const effect = 69n; - const delay = 1272825341158973505578n; - - it('pack', async function () { - expect(await this.mock.$pack(valueBefore, valueAfter, effect)).to.equal(delay); - expect(packDelay({ valueBefore, valueAfter, effect })).to.equal(delay); - }); - - it('unpack', async function () { - expect(await this.mock.$unpack(delay)).to.deep.equal([valueBefore, valueAfter, effect]); - - expect(unpackDelay(delay)).to.deep.equal({ - valueBefore, - valueAfter, - effect, - }); - }); - }); - - it('toDelay', async function () { - for (const value of [...SOME_VALUES, MAX_UINT32]) { - expect(await this.mock.$toDelay(value).then(unpackDelay)).to.deep.equal({ - valueBefore: 0n, - valueAfter: value, - effect: 0n, - }); - } - }); - - it('get & getFull', async function () { - const timepoint = await time.clock.timestamp(); - const valueBefore = 24194n; - const valueAfter = 4214143n; - - for (const effect of effectSamplesForTimepoint(timepoint)) { - const isPast = effect <= timepoint; - const delay = packDelay({ valueBefore, valueAfter, effect }); - - expect(await this.mock.$get(delay)).to.equal(isPast ? valueAfter : valueBefore); - expect(await this.mock.$getFull(delay)).to.deep.equal([ - isPast ? valueAfter : valueBefore, - isPast ? 0n : valueAfter, - isPast ? 0n : effect, - ]); - } - }); - - it('withUpdate', async function () { - const timepoint = await time.clock.timestamp(); - const valueBefore = 24194n; - const valueAfter = 4214143n; - const newvalueAfter = 94716n; - - for (const effect of effectSamplesForTimepoint(timepoint)) - for (const minSetback of [...SOME_VALUES, MAX_UINT32]) { - const isPast = effect <= timepoint; - const expectedvalueBefore = isPast ? valueAfter : valueBefore; - const expectedSetback = max(minSetback, expectedvalueBefore - newvalueAfter, 0n); - - expect( - await this.mock.$withUpdate(packDelay({ valueBefore, valueAfter, effect }), newvalueAfter, minSetback), - ).to.deep.equal([ - packDelay({ - valueBefore: expectedvalueBefore, - valueAfter: newvalueAfter, - effect: timepoint + expectedSetback, - }), - timepoint + expectedSetback, - ]); - } - }); - }); -});