Skip to content

Shrinking in version 5.0.0 #385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 41 commits into
base: feat/version-5.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3768346
🤡 Temp new pytypes generator
michprev Sep 23, 2024
530ec16
🤡 Add context managers
michprev Sep 23, 2024
7d21c0d
✨ Hack revm/non-revm testing imports based on config
michprev Oct 13, 2024
54a9e68
🚧 wake-rs WIP update
michprev Jan 7, 2025
6cfba69
💥 Use `bytes` instead of `bytearray` in user-facing API
michprev Jan 7, 2025
4a6ce52
🔥 Remove duplicate code, update `wake_rs` imports
michprev Jan 10, 2025
87438ec
🚸 Always terminate test filename with newline
michprev Feb 21, 2025
bb4b718
⚡️ Optimize fetching JSON solc list
michprev Feb 24, 2025
0b1bb63
✨ Add support for coverage from wake-rs
michprev Feb 24, 2025
2d194e7
🐛 Add missing `revert_on_failure` kwarg
michprev Feb 26, 2025
7dc3f2c
🐛 Fix multiprocessing termination with no coverage
michprev Mar 2, 2025
76bdd97
✨ Implement full Foundry config import with `wake up`
michprev Dec 7, 2024
7111e55
⚡️ Implement `get_name_abi_from_explorer` caching
michprev Mar 2, 2025
141af1d
✨ Implement collecting fuzz test stats
michprev Mar 2, 2025
5197e7d
✨ Define `ExternalEvent` and `ExternalError`
michprev Mar 2, 2025
fd3f42d
✅ shrink somehow working, wip
meditationduck Mar 18, 2025
be735fa
✅ store rs chain random state
meditationduck Mar 18, 2025
fa3a2cc
✅ timestamp experiment
meditationduck Mar 19, 2025
49cd544
✅ update shrinking
meditationduck Mar 20, 2025
2e3b189
✅ add Debugging right before Exception
meditationduck Mar 20, 2025
6bcd2e6
✨ Import additional config options from Foundry
michprev Mar 16, 2025
1dfdc15
✨ Support Solidity 0.8.29
michprev Mar 16, 2025
3b73a6a
🚸 Do not update config after imported from Foundry
michprev Mar 22, 2025
fa56b2a
✨ Add `wake up gitignore` command
michprev Mar 22, 2025
7be7460
✨ Support Etherscan API v2
michprev Mar 22, 2025
d684c23
✨ Use Sourcify v2 API
michprev Mar 22, 2025
dad5195
🧑‍💻 Prefer Sourcify over Etherscan
michprev Mar 22, 2025
f3e6bd5
✨ Support JSON export in `wake open`
michprev Mar 22, 2025
888a284
🐛 Fix non-ambiguous paths in source path resolving
michprev Mar 22, 2025
424e0df
🐛 Assume empty accounts on node JSON-RPC error
michprev Mar 22, 2025
1840c72
📝 Clarify `linearized_base_contracts`
michprev Mar 23, 2025
2b925b4
🧑‍💻 Recognize Reth nodes
michprev Mar 23, 2025
c6d5b24
✨ Implement relevant detections filtering based on scan info
michprev Mar 24, 2025
814f02d
🧑‍💻 Use error exit codes in `wake open`
michprev Mar 24, 2025
5a7a2ad
🐛 Handle contract not found in Etherscan v2 API
michprev Mar 24, 2025
d270785
🐛 Fix generating pytypes indexes
michprev Mar 26, 2025
a47742c
🐛 Fix subprojects compilation error
michprev Mar 30, 2025
6441dff
🐛 Fix abs. paths in `wake open` with Etherscan standard JSON
michprev Apr 10, 2025
b88adbf
✨ Implement `CallTrace.from_native_trace`
michprev Apr 13, 2025
18f632c
✅ update timestamp difference for reproduction
meditationduck Apr 14, 2025
88229d3
✅ add mode
meditationduck Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-reference/ir/meta/storage-layout-specifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: wake.ir.meta.storage_layout_specifier
5 changes: 2 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Wake can be configured using configuration options loaded from multiple sources

[api_keys]
# etherscan = "" (unset - no Etherscan API key)
# "goerli.etherscan" = "" (unset - no Goerli Etherscan API key)
# ...

[compiler.solc]
Expand Down Expand Up @@ -154,8 +153,8 @@ Command-line arguments for each command can be displayed using the `--help` opti

### `api_keys` namespace

The `api_keys` namespace may contain API keys for Etherscan, BscScan, PolygonScan, etc.
Blockchain explorer API keys are stored under the lowercase name of the explorer with a subdomain prefix if needed (e.g. `goerli.etherscan`).
The `api_keys` namespace may contain API keys for Etherscan and other services.
Etherscan v2 API is used so one API key is used for all supported chains.

Additionally, detectors and printers may use this namespace to load needed API keys.

Expand Down
2 changes: 2 additions & 0 deletions docs/static-analysis/working-with-ir.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ The IR tree can have a very complex structure. However, there are a few rules th
- there are only a few cases when expressions may be used without a parental statement (i.e. outside of a function/modifier body):
- in an [InheritanceSpecifier][wake.ir.meta.inheritance_specifier.InheritanceSpecifier] argument list,
- e.g. `:::solidity contract A is B(1, 2) {}`,
- in a [StorageLayoutSpecifier][wake.ir.meta.storage_layout_specifier.StorageLayoutSpecifier] base slot expression,
- e.g. `:::solidity contract C layout at (10 + 20) {}`,
- in a [ModifierInvocation][wake.ir.meta.modifier_invocation.ModifierInvocation] argument list,
- e.g. `:::solidity function foo() public onlyOwner(1, 2) {}`,
- in a [VariableDeclaration][wake.ir.declarations.variable_declaration.VariableDeclaration] initial value,
Expand Down
12 changes: 6 additions & 6 deletions docs/testing-framework/events-and-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ def test_events():

## Errors

Solidity user-defined errors are translated into Python dataclasses and inherit from `TransactionRevertedError` which inherits from `Exception`.
`TransactionRevertedError` also has a `tx` field that contains a transaction object for the transaction that caused the error.
Solidity user-defined errors are translated into Python dataclasses and inherit from `RevertError` which inherits from `Exception`.
`RevertError` also has a `tx` field that contains a transaction object for the transaction that caused the error.
The `tx` field is set to `None` if the reverted request was not a transaction.

`pytypes` for unused errors are not generated.
Expand All @@ -104,7 +104,7 @@ error NotEnoughFunds(

```python
@dataclasses.dataclass
class NotEnoughFunds(TransactionRevertedError):
class NotEnoughFunds(RevertError):
"""
Attributes:
requested (uint256): uint256
Expand Down Expand Up @@ -140,7 +140,7 @@ def test_errors():
from_=chain.accounts[1],
)
assert False, "Should have reverted"
except TransactionRevertedError as e:
except RevertError as e:
assert e == Counter.NotOwner()
tx = e.tx
```
Expand All @@ -165,7 +165,7 @@ def test_errors():
try:
counter.decrement()
assert False, "Should have reverted"
except TransactionRevertedError as e:
except RevertError as e:
assert e == Panic(PanicCodeEnum.UNDERFLOW_OVERFLOW)
tx = e.tx
```
Expand Down Expand Up @@ -200,7 +200,7 @@ class PanicCodeEnum(IntEnum):

Wake offers two helper functions (context managers) to handle errors - `must_revert` and `may_revert`. Both functions can accept:

- no arguments - any `TransactionRevertedError` is handled,
- no arguments - any `RevertError` is handled,
- a single error type - either `Error`, `Panic` or a user-defined type from `pytypes`,
- a single error instance - an instance of `Error`, `Panic` or a user-defined type from `pytypes`, e.g. `Error("some error")` or `Panic(PanicCodeEnum.UNDERFLOW_OVERFLOW)`,
- the error raised by the tested contract must exactly match the provided error instance,
Expand Down
4 changes: 2 additions & 2 deletions docs/testing-framework/helper-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Abi.encode_with_signature("setCount(uint256)", ['uint256'], [0xff])
!!! warning
The signature string must conform to the [ABI specification](https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector).
The common mistakes are:

- `uint` or `int` used instead of `uint256` or `int256`,
- return type specified,
- spaces in the signature string.
Expand Down Expand Up @@ -197,7 +197,7 @@ assert usdc_proxy.balanceOf(Account(1)) == 1000
from wake.testing import *


def revert_handler(e: TransactionRevertedError):
def revert_handler(e: RevertError):
if e.tx is not None:
print(e.tx.call_trace)
print(e.tx.console_logs)
Expand Down
2 changes: 1 addition & 1 deletion docs/testing-framework/transaction-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Every transaction object has the following properties:
| `gas_used` | gas used by the transaction | performs implicit `wait()` |
| `nonce` | nonce specified in the transaction | |
| `r` | `r` part of the ECDSA signature | performs implicit `wait()` |
| `raw_error` | `UnknownTransactionRevertedError` instance, `None` if the transaction succeeded | performs implicit `wait()` |
| `raw_error` | `UnknownRevertError` instance, `None` if the transaction succeeded | performs implicit `wait()` |
| `raw_events` | list of `UnknownEvent` instances emitted by the transaction | performs implicit `wait()` |
| `raw_return_value` | raw return value of the transaction; `Account` for contract deployment, `bytearray` otherwise | performs implicit `wait()`, raises `error` if the transaction failed |
| `return_value` | return value of the transaction | performs implicit `wait()`, raises `error` if the transaction failed |
Expand Down
6 changes: 3 additions & 3 deletions docs/testing-framework/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Troubleshooting

## `UnknownTransactionRevertedError(data=b'')`
## `UnknownRevertError(data=b'')`

In many different cases, a development chain or compiler auto-generated code does not provide any useful information about the revert reason.
This section describes the most common cases and how to debug them.
Expand All @@ -26,7 +26,7 @@ from wake.testing import *
from pytypes.contracts.Reverting import Reverting


def revert_handler(e: TransactionRevertedError):
def revert_handler(e: RevertError):
if e.tx is not None:
print(e.tx.call_trace)

Expand All @@ -44,7 +44,7 @@ def test_reverting():
### Contract code size limit

The Spurious Dragon hard fork introduced a limit on the size of a contract. The limit is 24,576 bytes of bytecode.
Due to the limit, a deployment transaction may fail with the `UnknownTransactionRevertedError` error without any reason data.
Due to the limit, a deployment transaction may fail with the `UnknownRevertError` error without any reason data.
In this case, the transaction call trace **does not contain any red cross**, but the transaction itself still fails.

To debug this error, compile the project and search for a warning message similar to the following:
Expand Down
4 changes: 2 additions & 2 deletions docs/wake-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"evm_version": {
"type": "string",
"description": "Version of the EVM to compile for",
"enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague"]
"enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague", "osaka"]
},
"exclude_paths": {
"type": "array",
Expand Down Expand Up @@ -461,7 +461,7 @@
"evm_version": {
"type": "string",
"description": "Version of the EVM to compile for",
"enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague"]
"enum": ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg", "istanbul", "berlin", "london", "paris", "shanghai", "cancun", "prague", "osaka"]
},
"optimizer": {
"type": "object",
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ nav:
- parameter_list: "api-reference/ir/meta/parameter-list.md"
- pragma_directive: "api-reference/ir/meta/pragma-directive.md"
- source_unit: "api-reference/ir/meta/source-unit.md"
- storage_layout_specifier: "api-reference/ir/meta/storage-layout-specifier.md"
- structured_documentation: "api-reference/ir/meta/structured-documentation.md"
- try_catch_clause: "api-reference/ir/meta/try-catch-clause.md"
- using_for_directive: "api-reference/ir/meta/using-for-directive.md"
Expand Down
8 changes: 4 additions & 4 deletions tests/test_svm.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async def test_basic_usage(run_cleanup, config):
svm = SolcVersionManager(config)

assert len(svm.list_installed()) == 0
assert "0.8.10" in svm.list_all()
assert "0.8.10" in svm.list_all(force=True)
await svm.install("0.8.10")
assert len(svm.list_installed()) == 1
assert "0.8.10" in svm.list_installed()
Expand Down Expand Up @@ -97,8 +97,8 @@ async def test_two_wake_root_paths(run_cleanup, config, config2):
svm1 = SolcVersionManager(config)
svm2 = SolcVersionManager(config2)

assert "0.8.10" in svm1.list_all()
assert "0.8.9" in svm2.list_all()
assert "0.8.10" in svm1.list_all(force=True)
assert "0.8.9" in svm2.list_all(force=True)
assert len(svm1.list_installed()) == 0
assert len(svm2.list_installed()) == 0
await svm1.install("0.8.10")
Expand Down Expand Up @@ -157,7 +157,7 @@ async def test_file_executable(run_cleanup, config):
output = subprocess.check_output([str(svm.get_path("0.8.10")), "--version"])
assert b"0.8.10" in output

oldest_version = svm.list_all()[0]
oldest_version = svm.list_all(force=True)[0]
await svm.install(oldest_version)
output = subprocess.check_output([str(svm.get_path(oldest_version)), "--version"])
assert str(oldest_version).encode("utf-8") in output
2 changes: 1 addition & 1 deletion tests/test_svm_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_svm_install(wake_root_path):
svm = SolcVersionManager(config)
cli_runner = CliRunner()

latest_version = next(reversed(svm.list_all()))
latest_version = next(reversed(svm.list_all(force=True)))
cli_result = cli_runner.invoke(
main,
["svm", "install", "*"],
Expand Down
88 changes: 44 additions & 44 deletions wake/cli/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,12 @@ def run_accounts(ctx: Context):
"""
Run Wake accounts manager.
"""
from eth_account import Account

from wake.config import WakeConfig

config = WakeConfig(local_config_path=ctx.obj.get("local_config_path", None))
config.load_configs()
ctx.obj["config"] = config

Account.enable_unaudited_hdwallet_features()


@run_accounts.command(name="new")
@click.option("--kdf", default="scrypt", help="Key derivation function to encrypt key")
Expand All @@ -54,7 +50,26 @@ def run_accounts(ctx: Context):
@click.option(
"--mnemonic/--no-mnemonic", is_flag=True, default=True, help="Print mnemonic"
)
@click.option("--language", "--lang", default="english", help="Mnemonic language")
@click.option(
"--language",
"--lang",
type=click.Choice(
[
"english",
"chinese_simplified",
"chinese_traditional",
"czech",
"french",
"italian",
"japanese",
"korean",
"portuguese",
"spanish",
]
),
default="english",
help="Mnemonic language",
)
@click.option("--words", default=12, help="Number of words")
@click.option("--hd-path", default="m/44'/60'/0'/0/0", help="HD path")
@click.option("--keystore", type=click.Path(file_okay=False), help="Keystore path")
Expand All @@ -74,10 +89,9 @@ def accounts_new(
"""
Create a new account.
"""
import json
import os

from eth_account import Account
from eth_utils.address import to_checksum_address
from wake_rs import Address, keccak256, new_mnemonic, to_checksum_address

if keystore is None:
path = Path(ctx.obj["config"].global_data_path) / "keystore"
Expand All @@ -89,29 +103,26 @@ def accounts_new(
if not path.is_dir():
raise click.BadParameter("Keystore path must be a directory")

path = path / f"{alias}.json"
if path.exists():
if (path / f"{alias}.json").exists():
raise click.BadParameter("Alias already exists, remove it first")

if mnemonic:
mnemonic_str = new_mnemonic(words, language)

passphrase = click.prompt(
"Mnemonic passphrase", default="", hide_input=True, confirmation_prompt=True
)
acc, mnemonic_str = Account.create_with_mnemonic(
passphrase, words, language, hd_path
)
acc = Address.from_mnemonic(mnemonic_str, passphrase, hd_path)
if click.confirm("Mnemonic will be printed, continue?"):
console.print(f"[bold]{mnemonic_str}[/bold]")
else:
acc = Account.create()

k = Account.encrypt(acc.key, password, kdf)
pk = keccak256(os.urandom(32))
acc = Address.from_key(pk)

with path.open("w") as f:
json.dump(k, f)
acc.export_keystore(alias, password, path)

console.print(
f"[green]Account created: {alias} {to_checksum_address(acc.address)}[/green]"
f"[green]Account created: {alias} {to_checksum_address(str(acc))}[/green]"
)


Expand All @@ -127,7 +138,7 @@ def accounts_remove(ctx: Context, keystore: Optional[str], alias: str):
"""
import json

from eth_utils.address import to_checksum_address
from wake_rs import to_checksum_address

if keystore is None:
path = Path(ctx.obj["config"].global_data_path) / "keystore"
Expand Down Expand Up @@ -158,7 +169,7 @@ def accounts_list(ctx: Context, keystore: Optional[str]):
"""
import json

from eth_utils.address import to_checksum_address
from wake_rs import to_checksum_address

if keystore is None:
path = Path(ctx.obj["config"].global_data_path) / "keystore"
Expand Down Expand Up @@ -201,10 +212,7 @@ def accounts_import(
"""
Import an account from a private key or mnemonic.
"""
import json

from eth_account import Account
from eth_utils.address import to_checksum_address
from wake_rs import Address, to_checksum_address

if keystore is None:
path = Path(ctx.obj["config"].global_data_path) / "keystore"
Expand All @@ -216,8 +224,7 @@ def accounts_import(
if not path.is_dir():
raise click.BadParameter("Keystore path must be a directory")

path = path / f"{alias}.json"
if path.exists():
if (path / f"{alias}.json").exists():
raise click.BadParameter("Alias already exists, remove it first")

key_or_mnemonic = click.prompt("Private key or mnemonic", hide_input=True)
Expand All @@ -236,17 +243,14 @@ def accounts_import(
key = None

if key is not None:
acc = Account.from_key(key)
acc = Address.from_key(key)
else:
acc = Account.from_mnemonic(key_or_mnemonic, "", hd_path)
acc = Address.from_mnemonic(key_or_mnemonic, "", hd_path)

k = Account.encrypt(acc.key, password, kdf)

with path.open("w") as f:
json.dump(k, f)
acc.export_keystore(alias, password, path)

console.print(
f"[green]Account imported: {alias} {to_checksum_address(acc.address)}[/green]"
f"[green]Account imported: {alias} {to_checksum_address(acc)}[/green]"
)


Expand All @@ -260,10 +264,7 @@ def accounts_export(ctx: Context, keystore: Optional[str], alias: str):
"""
Export an account's private key.
"""
import json

from eth_account import Account
from eth_utils.address import to_checksum_address
from wake_rs import Address, to_checksum_address

if keystore is None:
path = Path(ctx.obj["config"].global_data_path) / "keystore"
Expand All @@ -272,14 +273,13 @@ def accounts_export(ctx: Context, keystore: Optional[str], alias: str):
if not path.is_dir():
raise click.BadParameter("Keystore path must be a directory")

path = path / f"{alias}.json"
if not path.exists():
if not (path / f"{alias}.json").exists():
raise click.BadParameter("Alias does not exist")

with path.open() as f:
k = json.load(f)
acc = Address.from_alias(
alias, click.prompt("Password", default="", hide_input=True), path
)

x = Account.decrypt(k, click.prompt("Password", default="", hide_input=True))
console.print(
f"[green]Address {to_checksum_address(k['address'])} with private key {x.hex()}[/green]"
f"[green]Address {to_checksum_address(acc)} with private key 0x{acc.private_key.hex()}[/green]"
)
Loading
Loading