purge lib_forge_std and lib_openzeppelin_contracts
This commit is contained in:
@@ -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"]
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
@@ -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_))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {}
|
|
||||||
@@ -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 {}
|
|
||||||
@@ -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 {}
|
|
||||||
@@ -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 {}
|
|
||||||
187
lib_forge_std/test/fixtures/broadcast.log.json
vendored
187
lib_forge_std/test/fixtures/broadcast.log.json
vendored
@@ -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
|
|
||||||
}
|
|
||||||
8
lib_forge_std/test/fixtures/test.json
vendored
8
lib_forge_std/test/fixtures/test.json
vendored
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"a": 123,
|
|
||||||
"b": "test",
|
|
||||||
"c": {
|
|
||||||
"a": 123,
|
|
||||||
"b": "test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
lib_forge_std/test/fixtures/test.toml
vendored
6
lib_forge_std/test/fixtures/test.toml
vendored
@@ -1,6 +0,0 @@
|
|||||||
a = 123
|
|
||||||
b = "test"
|
|
||||||
|
|
||||||
[c]
|
|
||||||
a = 123
|
|
||||||
b = "test"
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Clones`: Add version of `clone` and `cloneDeterministic` that support sending value at creation.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`CircularBuffer`: Add a data structure that stores the last `N` values pushed to it.
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Math`: add an `invMod` function to get the modular multiplicative inverse of a number in Z/nZ.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Arrays`: add a `sort` functions for `address[]`, `bytes32[]` and `uint256[]` memory arrays.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
`TransparentUpgradeableProxy`: Make internal `_proxyAdmin()` getter have `view` visibility.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Arrays`: deprecate `findUpperBound` in favor of the new `lowerBound`.
|
|
||||||
@@ -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)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`DoubleEndedQueue`: Custom errors replaced with native panic codes.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`SlotDerivation`: Add a library of methods for derivating common storage slots.
|
|
||||||
@@ -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.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`StorageSlot`: Add primitives for operating on the transient storage space using a typed-slot representation.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Votes`: Set `_moveDelegateVotes` visibility to internal instead of private.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
`SafeCast`: Add `toUint(bool)` for operating on `bool` values as `uint256`.
|
|
||||||
@@ -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.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Hashes`: A library with commonly used hash functions.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Math`: Add `modExp` function that exposes the `EIP-198` precompile. Includes `uint256` and `bytes memory` versions.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Panic`: Add a library for reverting with panic codes.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Math`: Custom errors replaced with native panic codes.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Errors`: New library of common custom errors.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Arrays`: add new functions `lowerBound`, `upperBound`, `lowerBoundMemory` and `upperBoundMemory` for lookups in sorted arrays with potential duplicates.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`AccessManager`, `VestingWallet`, `TimelockController` and `ERC2771Forwarder`: Added a public `initializer` function in their corresponding upgradeable variants.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`Base64`: Add `encodeURL` following section 5 of RFC4648 for URL encoding
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`AccessControlEnumerable`: Add a `getRoleMembers` method to return all accounts that have `role`.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`VestingWalletCliff`: Add an extension of the `VestingWallet` contract with an added cliff.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`ReentrancyGuardTransient`: Added a variant of `ReentrancyGuard` that uses transient storage.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'openzeppelin-solidity': minor
|
|
||||||
---
|
|
||||||
|
|
||||||
`EnumerableMap`: add `UintToBytes32Map`, `AddressToAddressMap`, `AddressToBytes32Map` and `Bytes32ToAddressMap`.
|
|
||||||
@@ -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.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
comment: off
|
|
||||||
github_checks:
|
|
||||||
annotations: false
|
|
||||||
coverage:
|
|
||||||
status:
|
|
||||||
patch:
|
|
||||||
default:
|
|
||||||
target: 95%
|
|
||||||
only_pulls: true
|
|
||||||
project:
|
|
||||||
default:
|
|
||||||
threshold: 1%
|
|
||||||
@@ -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
|
|
||||||
@@ -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",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
require: 'hardhat/register',
|
|
||||||
timeout: 4000,
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 120,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.sol",
|
|
||||||
"options": {
|
|
||||||
"singleQuote": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"plugins": ["prettier-plugin-solidity"]
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
|
||||||
@@ -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)!
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"drips": {
|
|
||||||
"ethereum": {
|
|
||||||
"ownedBy": "0xAeb37910f93486C85A1F8F994b67E8187554d664"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<number>` 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.
|
|
||||||
@@ -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"
|
|
||||||
```
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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) |
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
patched
|
|
||||||
@@ -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: ;
|
|
||||||
@@ -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
|
|
||||||
```
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
pragma solidity ^0.8.20;
|
|
||||||
|
|
||||||
import {AccessControl} from "../patched/access/AccessControl.sol";
|
|
||||||
|
|
||||||
contract AccessControlHarness is AccessControl {}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {}
|
|
||||||
}
|
|
||||||
@@ -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 {}
|
|
||||||
}
|
|
||||||
@@ -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 {}
|
|
||||||
}
|
|
||||||
@@ -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) {}
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user