diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..e729148a --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,35 @@ +name: ci +on: + push: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + env: + PUBLISH: ${{ contains(fromJSON('["master", "main"]'), github.ref_name) && 'true' || 'false' }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + cache: 'pip' + - uses: actions/cache@v4 + with: + key: ${{ github.ref }} # cache by branch + path: .cache + - run: pip install -r requirements.txt + + - run: mkdocs build + if: env.PUBLISH == 'false' + + - uses: actions/upload-artifact@v4 + if: env.PUBLISH == 'false' + with: + name: site + path: site + + - run: mkdocs gh-deploy --force + if: env.PUBLISH == 'true' diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d0c3cbf1..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/api/abi_contract/overview.md b/docs/api/abi_contract/overview.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/api/cache.md b/docs/api/cache.md new file mode 100644 index 00000000..e7fc2731 --- /dev/null +++ b/docs/api/cache.md @@ -0,0 +1,29 @@ +# Cache + +## `set_cache_dir` + +!!! function "`boa.interpret.set_cache_dir()`" + + + **Description** + + Set the cache directory for the Vyper compilation results. + By default, this is set to `~/.cache/titanoboa`. + In case the directory is `None`, the cache will be disabled. + + --- + + **Parameters** + + - `cache_dir`: The directory to store the cache files. + +--- + +## `disable_cache` + +!!! function "`boa.interpret.disable_cache()`" + + + **Description** + + Set the cache directory for the Vyper compilation results. diff --git a/docs/api/common_classes/_BaseEVMContract.md b/docs/api/common_classes/_BaseEVMContract.md new file mode 100644 index 00000000..8b25f1e5 --- /dev/null +++ b/docs/api/common_classes/_BaseEVMContract.md @@ -0,0 +1,30 @@ +# `_BaseEVMContract` + +### Description + +The `_BaseEVMContract` class provides the base functionality for EVM contracts. It includes methods for handling contract deployment, execution, and interaction. + +### Methods + +- [stack_trace](stack_trace.md) +- [call_trace](call_trace.md) +- [handle_error](handle_error.md) + +### Properties + +- [address](address.md) + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> type(contract) + +``` \ No newline at end of file diff --git a/docs/api/common_classes/_BaseVyperContract.md b/docs/api/common_classes/_BaseVyperContract.md new file mode 100644 index 00000000..c6e1eeaa --- /dev/null +++ b/docs/api/common_classes/_BaseVyperContract.md @@ -0,0 +1,26 @@ +# `_BaseVyperContract` + +### Description + +The `_BaseVyperContract` class extends `_BaseEVMContract` and provides additional functionality specific to Vyper contracts. It includes methods for handling Vyper-specific features such as ABI encoding/decoding, event handling, and more. + +### Methods + +- [deployer](deployer.md) +- [abi](abi.md) +- [_constants](_constants.md) + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> type(contract) + +``` \ No newline at end of file diff --git a/docs/api/common_classes/_constants.md b/docs/api/common_classes/_constants.md new file mode 100644 index 00000000..cfb3d51c --- /dev/null +++ b/docs/api/common_classes/_constants.md @@ -0,0 +1,27 @@ +# `_constants` + +### Property + +```python +@property +_constants: ConstantsModel +``` + +### Description + +Provides access to the constants defined in the Vyper contract. + +- Returns: A `ConstantsModel` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... x: constant(uint256) = 123 +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> contract._constants.x +123 +``` \ No newline at end of file diff --git a/docs/api/common_classes/abi.md b/docs/api/common_classes/abi.md new file mode 100644 index 00000000..143ccb7c --- /dev/null +++ b/docs/api/common_classes/abi.md @@ -0,0 +1,30 @@ +# `abi` + +### Property + +```python +@property +abi: dict +``` + +### Description + +Returns the ABI (Application Binary Interface) of the Vyper contract. + +- Returns: A dictionary representing the ABI of the contract. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> contract_abi = contract.abi +>>> type(contract_abi) + +``` \ No newline at end of file diff --git a/docs/api/common_classes/address.md b/docs/api/common_classes/address.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/api/common_classes/call_trace.md b/docs/api/common_classes/call_trace.md new file mode 100644 index 00000000..d6cc5509 --- /dev/null +++ b/docs/api/common_classes/call_trace.md @@ -0,0 +1,33 @@ +# `call_trace` + + + +### Signature + +```python +call_trace() -> TraceFrame +``` + +### Description + +Returns the call trace of the computation. + +- Returns: A `TraceFrame` instance. + +### Examples + +!!! python + + ```python + >>> import boa + >>> src = """ + ... @external + ... def main(): + ... pass + ... """ + >>> deployer = boa.loads_partial(src, name="Foo") + >>> contract = deployer.deploy() + >>> contract.main() + >>> contract.call_trace() + + ``` \ No newline at end of file diff --git a/docs/api/common_classes/deployer.md b/docs/api/common_classes/deployer.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/api/common_classes/handle_error.md b/docs/api/common_classes/handle_error.md new file mode 100644 index 00000000..30404ce4 --- /dev/null +++ b/docs/api/common_classes/handle_error.md @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/docs/api/common_classes/stack_trace.md b/docs/api/common_classes/stack_trace.md new file mode 100644 index 00000000..c338c625 --- /dev/null +++ b/docs/api/common_classes/stack_trace.md @@ -0,0 +1,33 @@ +# `stack_trace` + +### Signature + +```python +stack_trace(computation: ComputationAPI) -> StackTrace +``` + +### Description + +Returns the stack trace of the computation. + +- `computation`: The computation to get the stack trace for. +- Returns: A `StackTrace` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... assert False, "error" +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> try: +... contract.main() +... except: +... pass +>>> contract.stack_trace(contract._computation) + +``` \ No newline at end of file diff --git a/docs/api/env/browser_env.md b/docs/api/env/browser_env.md new file mode 100644 index 00000000..599f2429 --- /dev/null +++ b/docs/api/env/browser_env.md @@ -0,0 +1,6 @@ +# BrowserEnv + +Inherits: [`NetworkEnv`](network_env.md) + + + diff --git a/docs/api/env/env.md b/docs/api/env/env.md new file mode 100644 index 00000000..9ffa6321 --- /dev/null +++ b/docs/api/env/env.md @@ -0,0 +1,656 @@ +# Env + +### Description + +TODO + + +### Attributes + +- `eoa`: The account to use as `msg.sender` for top-level calls and `tx.origin` in the context of state mutating function calls. +- `chain`: The global py-evm chain instance. + +--- + +## `alias` + +!!! function "`boa.env.alias(address, name)`" + + **Description** + + Associates an alias with an address. This is useful to make the address more human-readable in tracebacks. + + --- + + **Parameters** + + - `address`: The address to alias. + - `name`: The alias to use for the address. + +--- + +## `anchor` + +!!! function "`boa.env.anchor()`" + + **Description** + + A context manager which snapshots the state and the vm, and reverts to the snapshot on exit. + + --- + + **Examples** + + ```python + >>> import boa + >>> src = """ + ... value: public(uint256) + ... """ + >>> contract = boa.loads(src) + >>> contract.value() + 0 + >>> with boa.env.anchor(): + ... contract.eval("self.value += 1") + ... contract.value() + ... + 1 + >>> contract.value() + 0 + ``` + +--- + +## `deploy_code` + +!!! function "`boa.env.deploy_code(bytecode) -> bytes`" + + **Description** + + Deploy bytecode at a specific account. + + --- + + **Parameters** + + - `at`: The account the deployment bytecode will run at. + - `sender`: The account to set as `tx.origin` for the execution context and `msg.sender` for the top-level call. + - `gas`: The gas limit provided for the execution (a.k.a. `msg.gas`). + - `value`: The ether value to attach to the execution (a.k.a `msg.value`). + - `bytecode`: The deployment bytecode. + - `data`: The data to attach to the execution (a.k.a. `msg.data`). + - `pc`: The program counter to start the execution at. + + --- + + **Returns** + + The return value from the top-level call (typically the runtime bytecode of a contract). + + --- + + **Examples** + + ```python + >>> import boa + >>> code = bytes.fromhex("333452602034f3") # simply returns the caller + >>> boa.env.deploy_code(bytecode=code, sender="0x0000000022D53366457F9d5E68Ec105046FC4383").hex() + '0000000000000000000000000000000022d53366457f9d5e68ec105046fc4383' + >>> boa.env.vm.state.get_code(b"\x00" * 20).hex() + '0000000000000000000000000000000022d53366457f9d5e68ec105046fc4383' + ``` + +--- + +## `disable_gas_metering` + +!!! function "`boa.env.disable_gas_metering() -> None`" + + **Description** + + Disable gas metering by setting the gas meter class to `NoGasMeter`. + + --- + + **Example** + + ```python + >>> import boa + >>> boa.env.disable_gas_metering() + >>> # Subsequent operations will not meter gas + ``` + +--- + +## `enable_fast_mode` + +!!! function "`boa.env.enable_fast_mode() -> None`" + + **Description** + + Enable or disable fast mode. This can be useful for speeding up tests. + + --- + + **Parameters** + + - `flag`: Whether to enable or disable fast mode. + + --- + + **Warning** + + Fast mode is experimental and can break other features of boa (like coverage). + +--- + +## `enable_gas_profiling` + +!!! function "`boa.env.enable_gas_profiling() -> None`" + + **Description** + + Enable gas profiling by setting the gas meter class to `ProfilingGasMeter`. This is useful for detailed analysis of gas consumption in contract executions. + + --- + + **Example** + + ```python + >>> import boa + >>> boa.env.enable_gas_profiling() + >>> # Subsequent operations will use ProfilingGasMeter + ``` + +--- + +## `execute_code` + +!!! function "`boa.env.execute_code() -> bytes`" + + **Description** + + Execute bytecode at a specific account. + + --- + + **Parameters** + + - `at`: The account to target. + - `sender`: The account to set as `tx.origin` for the execution context and `msg.sender` for the top-level call. + - `gas`: The gas limit provided for the execution (a.k.a. `msg.gas`). + - `value`: The ether value to attach to the execution (a.k.a `msg.value`). + - `bytecode`: The runtime bytecode. + - `data`: The data to attach to the execution (a.k.a. `msg.data`). + - `pc`: The program counter to start the execution at. + + --- + + **Returns** + + The return value from the top-level call. + +--- + +## `gas_meter_class` + +!!! function "`boa.env.gas_meter_class()`" + + **Description** + + A context manager to temporarily set the gas meter class. This is useful for temporarily changing the gas metering behavior for specific operations. + + --- + + **Parameters** + + - `cls`: The gas meter class to use within the context. + + --- + + **Example** + + ```python + >>> import boa + >>> from boa.vm.gas_meters import ProfilingGasMeter + >>> with boa.env.gas_meter_class(ProfilingGasMeter): + ... # Operations using ProfilingGasMeter + ... pass + >>> # Gas meter class is reset to the previous value + ``` + +--- + +## `generate_address` + +!!! function "`boa.env.generate_address() -> str`" + + **Description** + + Generate an address and optionally alias it. + + --- + + **Parameters** + + - `alias`: The alias to use for the generated address. + + --- + + **Examples** + + ```python + >>> import boa + >>> boa.env.generate_address() + 'd13f0Bd22AFF8176761AEFBfC052a7490bDe268E' + ``` + +--- + +## `get_balance` + +!!! function "`boa.env.get_balance(address: str) -> int`" + + **Description** + + Get the ether balance of an account. + +--- + +## `get_code` + +!!! function "`boa.env.get_code(address)`" + + **Description** + + Get the bytecode stored at the specified address. This is useful for inspecting deployed contract bytecode. + + --- + + **Parameters** + + - `address`: The address to retrieve the code from. + + --- + + **Returns** + + The bytecode as bytes. + + --- + + **Example** + + ```python + >>> import boa + >>> code = boa.env.get_code("0x1234...") + >>> print(f"Bytecode length: {len(code)}") + ``` + +--- + +## `get_gas_meter_class` + +!!! function "`boa.env.get_gas_meter_class()`" + + **Description** + + Get the current gas meter class used in the environment. This method is useful for inspecting the current gas metering behavior in the environment. + + --- + + **Returns** + + The current gas meter class. + + --- + + **Example** + + ```python + >>> import boa + >>> gas_meter_class = boa.env.get_gas_meter_class() + >>> print(gas_meter_class.__name__) + 'GasMeter' # Default gas meter class + ``` + +--- + +## `get_gas_price` + +!!! function "`boa.env.get_gas_price()`" + + **Description** + + Get the current gas price used for transactions in the environment. + + --- + + **Returns** + + The current gas price as an integer. + + --- + + **Example** + + ```python + >>> import boa + >>> boa.env.get_gas_price() + 0 # Default gas price is 0 + ``` + +--- + +## `get_gas_used` + +!!! function "`boa.env.get_gas_used()`" + + **Description** + + Get the total amount of gas used in the current environment. This is useful for tracking gas consumption across multiple operations. + + --- + + **Returns** + + The total gas used as an integer. + + --- + + **Example** + + ```python + >>> import boa + >>> gas_used = boa.env.get_gas_used() + >>> print(f"Total gas used: {gas_used}") + ``` + +--- + +## `get_singleton` + +!!! function "`boa.env.get_singleton()`" + + **Description** + + Get or create the singleton instance of the `Env` class. This is typically used internally to ensure a single environment instance. + + --- + + **Returns** + + The singleton instance of the `Env` class. + + --- + + **Example** + + ```python + >>> import boa + >>> env = boa.env.get_singleton() + >>> # Use env for environment operations + ``` + +--- + +## `get_storage` + +!!! function "`boa.env.get_storage(address: str, slot: int) -> int`" + + **Description** + + Get the value stored at a specific storage slot for the given address. This allows direct access to contract storage, which can be useful for debugging and testing. + + --- + + **Parameters** + + - `address`: The address of the contract. + - `slot`: The storage slot to read from. + + --- + + **Returns** + + The value stored in the specified slot as an integer. + + --- + + **Example** + + ```python + >>> import boa + >>> value = boa.env.get_storage("0x1234...", 0) + >>> print(f"Value in slot 0: {value}") + ``` + +--- + +## `lookup_alias` + +!!! function "`boa.env.lookup_alias(address: str) -> str`" + + **Description** + + Look up the alias for a given address. This is useful for working with human-readable names for addresses. + + --- + + **Parameters** + + - `address`: The address to look up the alias for. + + --- + + **Returns** + + The alias associated with the address. + + --- + + **Example** + + ```python + >>> import boa + >>> alias = boa.env.lookup_alias("0x1234...") + >>> print(f"Alias for 0x1234... is {alias}") + ``` + +--- + +## `lookup_contract` + +!!! function "`boa.env.lookup_contract(address: str) -> Any`" + + **Description** + + Look up a contract object by its address. This is useful for retrieving previously registered contracts. + + --- + + **Parameters** + + - `address`: The address of the contract to look up. + + --- + + **Returns** + + The contract object if found, otherwise None. + + --- + + **Example** + + ```python + >>> import boa + >>> contract = boa.env.lookup_contract("0x1234...") + >>> if contract: + ... print("Contract found") + ... else: + ... print("Contract not found") + ``` + +--- + +## `prank` + +!!! function "`boa.env.prank(address)`" + + **Description** + + A context manager which temporarily sets `eoa` and resets it on exit. + + --- + + **Examples** + + ```python + >>> import boa + >>> boa.env.eoa + '0x0000000000000000000000000000000000000065' + >>> with boa.env.prank("0x00000000000000000000000000000000000000ff"): + ... boa.env.eoa + ... + '0x00000000000000000000000000000000000000ff' + >>> boa.env.eoa + ``` + +--- + +## `raw_call` + +!!! function "`boa.env.raw_call(to_address) -> bytes`" + + **Description** + + TODO too many details this should go in the explain section + Simple wrapper around `execute_code`, to execute as if the contract is being called from an EOA. + + --- + + **Parameters** + + - `to_address`: The contract to target. + - `sender`: The account to set as `tx.origin` for the execution context and `msg.sender` for the top-level call. + - `gas`: The gas limit provided for the execution (a.k.a. `msg.gas`). + - `value`: The ether value to attach to the execution (a.k.a `msg.value`). + - `data`: The data to attach to the execution (a.k.a. `msg.data`). + + --- + + **Returns** + + The return value from the top-level call. + +--- + +## `register_blueprint` + +!!! function "`boa.env.register_blueprint(bytecode, obj)`" + + **Description** + + Register a blueprint object with its bytecode in the environment. This is used for managing blueprint contracts in the environment. + + --- + + **Parameters** + + - `bytecode`: The bytecode of the blueprint. + - `obj`: The blueprint object to register. + + --- + + **Example** + + ```python + >>> import boa + >>> blueprint = boa.load_partial("path/to/blueprint.vy") + >>> boa.env.register_blueprint(blueprint.bytecode, blueprint) + ``` + +--- + +## `register_contract` + +!!! function "`boa.env.register_contract(address, obj)`" + + **Description** + + Register a contract object with its address in the environment. + + --- + + **Parameters** + + - `address`: The address of the contract. + - `obj`: The contract object to register. + + --- + + **Example** + + ```python + >>> import boa + >>> contract = boa.load("path/to/contract.vy") + >>> boa.env.register_contract(contract.address, contract) + ``` + + --- + + **Note** + + This is typically used internally but can be useful for manual contract management. + +--- + +## `reset_gas_metering_behavior` + +!!! function "`boa.env.reset_gas_metering_behavior()`" + + **Description** + + Reset gas metering to the default behavior by setting the gas meter class to `GasMeter`. + + --- + + **Example** + + ```python + >>> import boa + >>> boa.env.reset_gas_metering_behavior() + >>> # Gas metering is reset to default + ``` + + --- + + **Note** + + This is useful for restoring normal gas metering after using specialized gas meters. + +--- + +## `reset_gas_used` + +!!! function "`boa.env.reset_gas_used()`" + + **Description** + + Reset the gas usage counter to zero and reset access counters. + + --- + + **Example** + + ```python + >>> import boa + >>> boa.env.reset_gas_used() + >>> # Gas usage is now reset to 0 + ``` + + --- + + **Note** + + This is useful when you want to start a fresh gas measurement. \ No newline at end of file diff --git a/docs/api/env/network_env.md b/docs/api/env/network_env.md new file mode 100644 index 00000000..7df158af --- /dev/null +++ b/docs/api/env/network_env.md @@ -0,0 +1,11 @@ +# NetworkEnv + +Inherits: [`Env`](env.md) + + + +## `add_account` +!!! function "`add_account(account: Account, force_eoa=False)`" + + +## `anchor` diff --git a/docs/api/env/singleton.md b/docs/api/env/singleton.md new file mode 100644 index 00000000..6eb06f61 --- /dev/null +++ b/docs/api/env/singleton.md @@ -0,0 +1,21 @@ +# Pick your environment + +This page documents the functions to set the singleton environment. +If you need help choosing an environment, see the [Titanoboa Environments](../../explain/singleton_env.md) page. + +Note that all the `set_env` functions return an optional context manager. +See [context management](../../explain/singleton_env.md#automatic-context-management) section for more information. + +<-- TODO: Document set_env APIs with !!!function --> + +## `swap_env` +This is the same as `set_env`, but it **only** works in the context of a context manager. +Note the other set_env functions offer an optional usage of context manager, while `swap_env` requires it. + +## `set_env` +## `fork` + + +## `set_browser_env` +## `set_network_env` +## `reset_env` diff --git a/docs/api/exceptions/boa_error.md b/docs/api/exceptions/boa_error.md new file mode 100644 index 00000000..8dfa4039 --- /dev/null +++ b/docs/api/exceptions/boa_error.md @@ -0,0 +1,5 @@ +### `BoaError` + +#### Description + +Raised when an error occurs during contract execution. \ No newline at end of file diff --git a/docs/api/load_contracts.md b/docs/api/load_contracts.md new file mode 100644 index 00000000..6559585f --- /dev/null +++ b/docs/api/load_contracts.md @@ -0,0 +1,302 @@ +

Loading contracts

+ +Boa offers multiple ways to load contracts from various sources. Either from [local files](#from-local-files), [strings](#from-strings) or directly [from block explorer sources](#from-block-explorer-sources). + +--- + +## **From local files** + +### `load` +!!! function "`boa.load(filepath)`" + + + **Description** + + The `load` function is designed to compile a Vyper contract from a file located on disk. It provides a straightforward way to deploy contracts by reading the source code from the specified file path. + + --- + + **Parameters** + + - `filepath`: The contract source code file path. + - `*args`: Contract constructor arguments. + - `compiler_args: list = None`: Compiler arguments to be passed to the Vyper compiler. + - `filename: str = None`: The filename of the contract source code or a related file. + - `as_blueprint: bool = False`: Whether to deploy an [`eip-5202`](https://eips.ethereum.org/EIPS/eip-5202) blueprint of the compiled contract. + - `value: int = 0`: The amount of cryptocurrency to send with the transaction (default is 0). + - `env: Env = None`: The environment in which the contract is being deployed or executed. + - `override_address: Address = None`: A different address for the contract deployment or interaction. + - `skip_initcode: bool = False`: Whether to skip the execution of the contract's constructor code. + - `created_from: Address = None`: The address from which the contract is created. + - `gas: int = None`: The gas limit for the transaction. + + --- + + **Returns** + + A [`VyperContract`](vyper_contract/overview.md), [`VyperBlueprint`](vyper_blueprint/overview.md), or [`ABIContract`](abi_contract/overview.md) instance. + + If a legacy Vyper version is detected, an `ABIContract` may be returned due to VVM usage. See [Legacy Vyper Contracts](../explain/vvm_contracts.md) for more details. + + --- + + **Examples** + + SOON + +--- + +### `load_abi` +!!! function "`boa.load_abi(filename)`" + + + **Description** + + The `load_abi` function allows you to load a contract's ABI from a JSON file. + + --- + + **Parameters** + + - `filename`: The file containing the ABI as a JSON string (something like `my_abi.json`). + - `*args`: Additional arguments. + - `name`: The name of the contract (optional). + + --- + + **Returns** + + An [`ABIContract`](abi_contract/overview.md) instance. + + --- + + **Examples** + + SOON + +--- + +### `load_partial` +!!! function "`boa.load_partial(filepath)`" + + + **Description** + + The `load_partial` function is used to compile a Vyper contract from a file and return a deployer instance. + + --- + + **Parameters** + + - `filepath`: The contract source code file path. + - `*args`: Additional arguments. + - `compiler_args`: Argument to be passed to the Vyper compiler (optional). + + --- + + **Returns** + + A [`VyperDeployer`](vyper_deployer/overview.md) or [`VVMDeployer`](vvm_deployer/overview.md) instance. + + If a legacy Vyper version is detected, a `VVMDeployer` may be returned due to VVM usage. See [Legacy Vyper Contracts](../explain/vvm_contracts.md) for more details. + + --- + + **Examples** + + SOON + +--- + +### `load_vyi` +!!! function "`boa.load_vyi(filename)`" + + + **Description** + + The `load_vyi` function is designed to load a Vyper interface from a `.vyi` file. + + --- + + **Parameters** + + - `filename`: The file containing the Vyper interface. + - `name`: The name of the contract (optional). + + --- + + **Returns** + + An [`ABIContract`](abi_contract/overview.md) instance. + + --- + + **Examples** + + SOON + +--- + +## **From strings** + +### `loads` +!!! function "`boa.loads(source)`" + + + **Description** + + The `loads` function compiles Vyper source code provided as a string. This is useful for dynamic contract creation or testing scenarios where the source code is generated or modified at runtime. + + --- + + **Parameters** + + - `source`: The source code to compile and deploy. + - `*args`: Contract constructor arguments. + - `compiler_args: list = None`: Compiler arguments to be passed to the Vyper compiler. + - `as_blueprint: bool = False`: Whether to deploy an [`eip-5202`](https://eips.ethereum.org/EIPS/eip-5202) blueprint of the compiled contract. + - `value: int = 0`: The amount of cryptocurrency to send with the transaction (default is 0). + - `env: Env = None`: The environment in which the contract is being deployed or executed. + - `override_address: Address = None`: A different address for the contract deployment or interaction. + - `skip_initcode: bool = False`: Whether to skip the execution of the contract's constructor code. + - `created_from: Address = None`: The address from which the contract is created. + - `gas: int = None`: The gas limit for the transaction. + - `name`: The name of the contract. + + --- + + **Returns** + + A [`VyperContract`](vyper_contract/overview.md), [`VyperBlueprint`](vyper_blueprint/overview.md), or [`ABIContract`](abi_contract/overview.md) instance. + + If a legacy Vyper version is detected, an `ABIContract` may be returned due to VVM usage. See [Legacy Vyper Contracts](../explain/vvm_contracts.md) for more details. + + --- + + **Examples** + + SOON + +--- + +### `loads_abi` +!!! function "`boa.loads_abi(json_str)`" + + + **Description** + + The `loads_abi` function creates an `ABIContract` from a JSON string representing the contract's ABI. + + --- + + **Parameters** + + - `json_str`: The ABI as a JSON string (something which can be passed to `json.loads()`). + - `*args`: Additional arguments. + - `name`: The name of the contract (optional). + + --- + + **Returns** + + An [`ABIContract`](abi_contract/overview.md) instance. + + **Examples** + + SOON + +--- + +### `loads_partial` +!!! function "`boa.loads_partial(source)`" + + + **Description** + + The `loads_partial` function compiles Vyper source code provided as a string and returns a deployer instance. This function is useful for preparing contracts for deployment in environments where the source code is dynamically generated or modified. + + --- + + **Parameters** + + - `source`: The Vyper source code. + - `name`: The name of the contract (optional). + - `dedent`: If `True`, remove any common leading whitespace from every line in `source`. + - `compiler_args`: Argument to be passed to the Vyper compiler (optional). + + --- + + **Returns** + + A [`VyperDeployer`](vyper_deployer/overview.md) or [`VVMDeployer`](vvm_deployer/overview.md) instance. + + If a legacy Vyper version is detected, a `VVMDeployer` may be returned due to VVM usage. See [Legacy Vyper Contracts](../explain/vvm_contracts.md) for more details. + + --- + + **Examples** + + SOON + +--- + +### `loads_vyi` +!!! function "`boa.loads_vyi(source_code)`" + + + **Description** + + The `loads_vyi` function loads a Vyper interface from a string. This is useful for defining and using contract interfaces directly in code without needing separate interface files. + + --- + + **Parameters** + + - `source_code`: The Vyper interface source code as a string. + - `name`: The name of the contract (optional). + - `filename`: The filename for reference (optional). + + --- + + **Returns** + + An [`ABIContract`](abi_contract/overview.md) instance. + + --- + + **Examples** + + SOON + +--- + +## **From block explorer sources** + +### `from_etherscan` +!!! function "`boa.from_etherscan(address)`" + + + **Description** + + The `from_etherscan` function fetches the ABI for a contract at a given address from Etherscan and returns an `ABIContract` instance. This is particularly useful for interacting with contracts deployed on the Ethereum network when you have the contract address but not the source code. + + --- + + **Parameters** + + - `address`: The address. Can be str, bytes or Address. + - `name`: The name of the contract (optional). + - `uri`: The API endpoint URI (default: "https://api.etherscan.io/api"). + - `api_key`: The API key for Etherscan (optional). + + --- + + **Returns** + + An [`ABIContract`](abi_contract/overview.md) instance. + + --- + + **Examples** + + SOON diff --git a/docs/api/pyevm/deregister_precompile.md b/docs/api/pyevm/deregister_precompile.md new file mode 100644 index 00000000..a8c137fe --- /dev/null +++ b/docs/api/pyevm/deregister_precompile.md @@ -0,0 +1,15 @@ +# `deregister_precompile` + +### Signature + +```python +deregister_precompile(address: str, force: bool = True) +``` + +### Description + +Deregister a precompile. + +- `address`: The address of a previously registered precompile. +- `force`: Whether to force removal of the precompile at the specified address. +- Raises `ValueError`: If a precompile is not registered at the specified address and the force argument is `False`. \ No newline at end of file diff --git a/docs/api/pyevm/patch_opcode.md b/docs/api/pyevm/patch_opcode.md new file mode 100644 index 00000000..4e7ee079 --- /dev/null +++ b/docs/api/pyevm/patch_opcode.md @@ -0,0 +1,71 @@ +# `patch_opcode` + +### Signature + +```python +patch_opcode(opcode: int, fn: Callable[[eth.abc.ComputationAPI], None]) +``` + +### Description + +Patch an opcode. + +- `opcode`: The opcode to patch. +- `fn`: The function implementing the desired opcode functionality. + +### Examples + +The following code snippet implements tracing for the `CREATE` opcode and stores all newly created accounts in a list. + +```python +# example.py +import boa + +class CreateTracer: + def __init__(self, super_fn): + """Track addresses of contracts created via the CREATE opcode. + + Parameters: + super_fn: The original opcode implementation. + """ + self.super_fn = super_fn + self.trace = [] + + def __call__(self, computation): + # first, dispatch to the original opcode implementation provided by py-evm + self.super_fn(computation) + # then, store the output of the CREATE opcode in our `trace` list for later + self.trace.append("0x" + computation._stack.values[-1][-1].hex()) + +if __name__ == "__main__": + create_tracer = CreateTracer(boa.env.vm.state.computation_class.opcodes[0xf0]) + boa.patch_opcode(0xf0, create_tracer) + + source = """ +@external +def main(): + for _ in range(10): + addr: address = create_minimal_proxy_to(self) + """ + contract = boa.loads(source) + contract.main() # execute the contract function + print(create_tracer.trace) +``` + +Running the code would produce the following results: + +```bash +$ python example.py +[ + "0xd130b7e7f212ecadcfcca3cecc89f85ce6465896", + "0x37fdb059bf647b88dbe172619f00b8e8b1cf9338", + "0x40bcd509b3c1f42d535d1a8f57982729d4b52adb", + "0xaa35545ac7a733600d658c3f516ce2bb2be99866", + "0x29e303d13a16ea18c6b0e081eb566b55a74b42d6", + "0x3f69d814da1ebde421fe7dc99e24902b15af960b", + "0x719c0dc21639008a2855fdd13d0d6d89be53f991", + "0xf6086a85f5433f6fbdcdcf4f2ace7915086a5130", + "0x097dec6ea6b9eb5fc04db59c0d343f0e3b4097a0", + "0x905794c5566184e642ef14fb0e72cf68ff8c79bf" +] +``` \ No newline at end of file diff --git a/docs/api/pyevm/register_precompile.md b/docs/api/pyevm/register_precompile.md new file mode 100644 index 00000000..6b7ff7df --- /dev/null +++ b/docs/api/pyevm/register_precompile.md @@ -0,0 +1,26 @@ +# `register_precompile` + +### Signature + +```python +register_precompile(address: str, fn: Callable[[eth.abc.ComputationAPI], None], force: bool = False) +``` + +### Description + +Register a precompile. + +- `address`: The address to register the precompile at. +- `fn`: The function to execute when the precompile is called. +- `force`: Whether to overwrite the precompile function if one is already registered at the specified address. +- Raises `ValueError`: If a precompile is already registered at the specified address and the force argument is `False`. + +### Examples + +```python +>>> import boa +>>> log = lambda computation: print("0x" + computation.msg.sender.hex()) +>>> boa.register_precompile("0x00000000000000000000000000000000000000ff", log) +>>> boa.eval("raw_call(0x00000000000000000000000000000000000000ff, b'')") +0x0000000000000000000000000000000000000069 +``` \ No newline at end of file diff --git a/docs/api/testing.md b/docs/api/testing.md new file mode 100644 index 00000000..b46bb928 --- /dev/null +++ b/docs/api/testing.md @@ -0,0 +1,225 @@ +# Testing and Forking + +### Description + +Boa provides various utilities to test vyper contracts and execute them in a forked environment. + +--- + +## **Methods** + +### `eval` +!!! function "`boa.eval(statement)`" + + + **Description** + + Evaluate a Vyper statement in the context of a contract with no state. + + --- + + **Parameters** + + - `statement`: A valid Vyper statement. + + --- + + **Returns** + + The result of the statement execution. + + --- + + **Examples** + + ```python + >>> import boa + >>> boa.eval("keccak256('Hello World!')").hex() + '3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0' + >>> boa.eval("empty(uint256[10])") + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + ``` + +--- + +### `fork` +!!! function "`boa.fork(url)`" + + + **Description** + + Forks the environment to a local chain using the specified RPC URL and block identifier. This allows testing in a forked state of the blockchain. + + --- + + **Parameters** + + - `url: str`: The RPC URL to fork from. + - `block_identifier: int | str = "safe"`: The block identifier to fork from, can be an integer or a string (default is "safe"). + - `allow_dirty: bool = False`: If `True`, allows forking with a dirty state (default is `False`). + - `reset_traces: bool = True`: Whether to reset the traces. + - `cache_file: str | None = None`: The file to cache the forked state to. To learn more about caching see [Caching](../explain/caching.md). + - `**kwargs`: Additional arguments for the RPC. + --- + + **Returns** + + Sets the environment to the new forked state. To learn more about environments see [Titanoboa Environments](../explain/singleton_env.md). + + --- + + **Examples** + + ```python + SOON + ``` + + +--- + +### `boa.deal` +!!! function "`deal(token, receiver, amount)`" + + + **Description** + + Overwrites the balance of `receiver` for `token` to `amount`, and adjusts the total supply if `adjust_supply` is True. + + --- + + **Parameters** + + - `token`: The token contract to modify. + - `receiver`: The address to modify the balance for. + - `amount`: The new balance amount. + - `adjust_supply`: Whether to adjust the total supply of the token. Defaults to True. + + --- + + **Examples** + + === "Vanilla ERC20" + TODO verify this example + + Let's modify the balance of `alice` for `usdc` to 100. + + ```python + boa.fork(os.getenv("ETH_RPC_URL")) + + usdc = ERC20.at("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", name="USDC") + + alice = boa.env.generate_address("alice") + + deal(token, alice, 100 * 10 ** usdc.decimals()) # (1)! + + usdc.balanceOf(alice) # returns 100 * 10 ** 6 + ``` + + 1. We multiply the amount by `10 ** usdc.decimals()` to account for the token's decimals. + === "WETH" + TODO need to use adjust_supply=False + + + +--- + +### `boa.reverts` +!!! function "`reverts(reason)`" + + + **Description** + + A context manager which validates an execution error occurs with optional reason matching. + + --- + + **Parameters** + + - `reason`: A string to match against the execution error. + - `compiler`: A string to match against the internal compiler revert reason. + - `vm_error`: A string to match against the revert reason string. + + --- + + **Examples** + + Boa supports matching against different revert reasons, to learn more about them see [Revert Reasons](../explain/revert_reasons.md). + + === "Revert reason provided as a positional argument" + TODO reorganize this part + + ```python + import boa + + source = """ + @external + def foo(): + raise "0xdeadbeef" + + @external + def bar(): + raise # dev: 0xdeadbeef + """ + contract = boa.loads(source) + + with boa.reverts("0xdeadbeef"): + contract.foo() + + with boa.reverts("0xdeadbeef"): + contract.bar() + ``` + + === "Compiler revert reason" + + ```python + import boa + + source = """ + @external + def subtract(a: uint256, b: uint256) -> uint256: + return a - b + + @external + def addition(a: uint256, b: uint256) -> uint256: + return a + b + """ + contract = boa.loads(source) + + with boa.reverts(compiler="safesub"): + contract.subtract(1, 2) + + with boa.reverts(compiler="safeadd"): + contract.addition(1, 2**256 - 1) + ``` + + === "VM error reason" + + ```python + import boa + + source = """ + @external + def main(a: uint256): + assert a == 0, "A is not 0" + """ + contract = boa.loads(source) + + with boa.reverts(vm_error="A is not 0"): + contract.main(69) + ``` + + === "Developer revert comment" + + ```python + import boa + + source = """ + @external + def main(a: uint256): + assert a == 0 # dev: a is not 0 + """ + contract = boa.loads(source) + + with boa.reverts(dev="a is not 0"): + contract.main(69) + ``` diff --git a/docs/api/verify/get_verifier.md b/docs/api/verify/get_verifier.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/api/verify/overview.md b/docs/api/verify/overview.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/api/verify/set_verifier.md b/docs/api/verify/set_verifier.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/api/verify/verify.md b/docs/api/verify/verify.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/api/vvm_deployer/__call__.md b/docs/api/vvm_deployer/__call__.md new file mode 100644 index 00000000..12261106 --- /dev/null +++ b/docs/api/vvm_deployer/__call__.md @@ -0,0 +1,21 @@ +# `__call__` + +### Signature + +```python +def __call__(self, *args, **kwargs) +``` + +### Description + +Allows the instance to be called like a function to deploy the contract. + +- `*args`: Arguments to pass to the constructor. +- `**kwargs`: Keyword arguments to pass to the deploy method. + +### Examples + +```python +>>> deployer = VVMDeployer(abi, bytecode, filename) +>>> contract = deployer(arg1, arg2) +``` \ No newline at end of file diff --git a/docs/api/vvm_deployer/__init__.md b/docs/api/vvm_deployer/__init__.md new file mode 100644 index 00000000..8382c18d --- /dev/null +++ b/docs/api/vvm_deployer/__init__.md @@ -0,0 +1,24 @@ +# `__init__` + +### Signature + +```python +def __init__(self, abi, bytecode, filename) +``` + +### Description + +Initializes the `VVMDeployer` instance with the given ABI, bytecode, and filename. + +- `abi`: The ABI of the contract. +- `bytecode`: The bytecode of the contract. +- `filename`: The name of the contract file. + +### Examples + +```python +>>> abi = [...] # ABI of the contract +>>> bytecode = "0x..." # Bytecode of the contract +>>> filename = "MyContract" +>>> deployer = VVMDeployer(abi, bytecode, filename) +``` \ No newline at end of file diff --git a/docs/api/vvm_deployer/at.md b/docs/api/vvm_deployer/at.md new file mode 100644 index 00000000..c0430ad3 --- /dev/null +++ b/docs/api/vvm_deployer/at.md @@ -0,0 +1,21 @@ +# `at` + +### Signature + +```python +def at(self, address) +``` + +### Description + +Returns a contract instance at a given address. + +- `address`: The address of the deployed contract. + +### Examples + +```python +>>> deployer = VVMDeployer(abi, bytecode, filename) +>>> contract = deployer.deploy() +>>> contract_at_address = deployer.at(contract.address) +``` \ No newline at end of file diff --git a/docs/api/vvm_deployer/constructor.md b/docs/api/vvm_deployer/constructor.md new file mode 100644 index 00000000..e3705612 --- /dev/null +++ b/docs/api/vvm_deployer/constructor.md @@ -0,0 +1,19 @@ +# `constructor` + +### Signature + +```python +@cached_property +def constructor(self) +``` + +### Description + +Finds and returns the constructor function from the ABI. + +### Examples + +```python +>>> deployer = VVMDeployer(abi, bytecode, filename) +>>> constructor = deployer.constructor +``` \ No newline at end of file diff --git a/docs/api/vvm_deployer/deploy.md b/docs/api/vvm_deployer/deploy.md new file mode 100644 index 00000000..9b286ffd --- /dev/null +++ b/docs/api/vvm_deployer/deploy.md @@ -0,0 +1,21 @@ +# `deploy` + +### Signature + +```python +def deploy(self, *args, env=None) +``` + +### Description + +Deploys the contract with optional arguments and environment. + +- `*args`: Arguments to pass to the constructor. +- `env`: The environment to use for deployment. If not provided, a singleton environment is used. + +### Examples + +```python +>>> deployer = VVMDeployer(abi, bytecode, filename) +>>> contract = deployer.deploy(arg1, arg2) +``` \ No newline at end of file diff --git a/docs/api/vvm_deployer/factory.md b/docs/api/vvm_deployer/factory.md new file mode 100644 index 00000000..315ac7a0 --- /dev/null +++ b/docs/api/vvm_deployer/factory.md @@ -0,0 +1,19 @@ +# `factory` + +### Signature + +```python +@cached_property +def factory(self) +``` + +### Description + +Returns a contract factory from the ABI. + +### Examples + +```python +>>> deployer = VVMDeployer(abi, bytecode, filename) +>>> factory = deployer.factory +``` \ No newline at end of file diff --git a/docs/api/vvm_deployer/from_compiler_output.md b/docs/api/vvm_deployer/from_compiler_output.md new file mode 100644 index 00000000..651e8134 --- /dev/null +++ b/docs/api/vvm_deployer/from_compiler_output.md @@ -0,0 +1,26 @@ +# `from_compiler_output` + +### Signature + +```python +@classmethod +def from_compiler_output(cls, compiler_output, filename) +``` + +### Description + +Creates an instance of `VVMDeployer` from the compiler output. + +- `compiler_output`: The output from the compiler containing the ABI and bytecode. +- `filename`: The name of the contract file. + +### Examples + +```python +>>> compiler_output = { +... "abi": [...], +... "bytecode": "0x..." +... } +>>> filename = "MyContract" +>>> deployer = VVMDeployer.from_compiler_output(compiler_output, filename) +``` \ No newline at end of file diff --git a/docs/api/vvm_deployer/overview.md b/docs/api/vvm_deployer/overview.md new file mode 100644 index 00000000..aa2ca673 --- /dev/null +++ b/docs/api/vvm_deployer/overview.md @@ -0,0 +1,47 @@ +# `VVMDeployer` + +### Description + +The `VVMDeployer` class provides functionality for deploying smart contracts for older Vyper versions via the [Vyper Version Manager (VVM)](http://github.com/vyperlang/vvm). +It includes methods for handling contract deployment, execution, and interaction. + +### Methods + + +- [\_\_init\_\_](\_\_init\_\_.md) +- [from_compiler_output](from_compiler_output.md) +- [factory](factory.md) +- [constructor](constructor.md) +- [deploy](deploy.md) +- [\_\_call\_\_](\_\_call\_\_.md) +- [at](at.md) + +### Examples + +!!!python + + ```python + deployer = boa.loads_partial(""" + # pragma version 0.3.10 + + foo: public(uint256) + bar: public(uint256) + + @external + def __init__(bar: uint256): + self.foo = 42 + self.bar = bar + """) + contract = deployer.deploy() + + >>> type(deployer) + + + >>> type(contract) + + ``` + + +!!! warning + + Titanoboa will automatically read the version pragma in the source code and install the right compiler version via `vvm` \ No newline at end of file diff --git a/docs/api/vyper_blueprint/overview.md b/docs/api/vyper_blueprint/overview.md new file mode 100644 index 00000000..b531dcde --- /dev/null +++ b/docs/api/vyper_blueprint/overview.md @@ -0,0 +1,25 @@ +# `VyperBlueprint` + +### Description + +The `VyperBlueprint` class represents a blueprint of a Vyper contract. It is used to deploy contracts using the blueprint pattern, which allows for more efficient contract deployment. + +### Methods + + +TODO mention common classes + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> blueprint = deployer.deploy_as_blueprint() +>>> type(blueprint) + +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/at.md b/docs/api/vyper_contract/at.md new file mode 100644 index 00000000..0519ecba --- /dev/null +++ b/docs/api/vyper_contract/at.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/api/vyper_contract/decode_log.md b/docs/api/vyper_contract/decode_log.md new file mode 100644 index 00000000..f39f35b0 --- /dev/null +++ b/docs/api/vyper_contract/decode_log.md @@ -0,0 +1,31 @@ +# `decode_log` + +### Signature + +```python +decode_log(e) -> Event +``` + +### Description + +Decodes a log entry into an `Event` instance. + +- `e`: The log entry to decode. +- Returns: An `Event` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... log MyEvent() +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> contract.main() +>>> log_entry = contract.get_logs()[0] +>>> contract.decode_log(log_entry) + +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/deployer.md b/docs/api/vyper_contract/deployer.md new file mode 100644 index 00000000..eca571e2 --- /dev/null +++ b/docs/api/vyper_contract/deployer.md @@ -0,0 +1,12 @@ +# `deployer` + +### Property + +```python +@property +deployer: VyperDeployer +``` + +### Description + +The `VyperDeployer` instance associated with the contract. \ No newline at end of file diff --git a/docs/api/vyper_contract/eval.md b/docs/api/vyper_contract/eval.md new file mode 100644 index 00000000..72ad8f35 --- /dev/null +++ b/docs/api/vyper_contract/eval.md @@ -0,0 +1,30 @@ +# `eval` + +### Signature + +```python +eval(statement: str, value: int = 0, gas: int | None = None, sender: str | None = None) -> Any +``` + +### Description + +Evaluate a Vyper statement in the context of the contract. + +- `statement`: A vyper statement. +- `value`: The ether value to attach to the statement evaluation (a.k.a `msg.value`). +- `gas`: The gas limit provided for statement evaluation (a.k.a. `msg.gas`). +- `sender`: The account which will be the `tx.origin`, and `msg.sender` in the context of the evaluation. +- Returns: The result of the statement evaluation. + +### Examples + +```python +>>> import boa +>>> src = "value: public(uint256)" +>>> contract = boa.loads(src) +>>> contract.value() +0 +>>> contract.eval("self.value += 1") +>>> contract.value() +1 +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/get_logs.md b/docs/api/vyper_contract/get_logs.md new file mode 100644 index 00000000..95f0bcde --- /dev/null +++ b/docs/api/vyper_contract/get_logs.md @@ -0,0 +1,31 @@ +# `get_logs` + +### Signature + +```python +get_logs(computation=None, include_child_logs=True) -> List[Event] +``` + +### Description + +Returns the logs generated by the computation. + +- `computation`: The computation to get the logs for. If `None`, uses the last computation. +- `include_child_logs`: Whether to include logs from child computations. +- Returns: A list of `Event` instances. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... log MyEvent() +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> contract.main() +>>> contract.get_logs() +[] +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/inject_function.md b/docs/api/vyper_contract/inject_function.md new file mode 100644 index 00000000..c90f7974 --- /dev/null +++ b/docs/api/vyper_contract/inject_function.md @@ -0,0 +1,35 @@ +# `inject_function` + +### Signature + +```python +inject_function(fn_source_code, force=False) +``` + +### Description + +Injects a function into the contract without affecting the contract's source code. Useful for testing private functionality. + +- `fn_source_code`: The source code of the function to inject. +- `force`: Whether to force the injection if a function with the same name already exists. +- Returns: None. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> contract.inject_function(""" +... @internal +... def injected_function() -> uint256: +... return 42 +... """) +>>> contract.internal.injected_function() +42 +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/marshal_to_python.md b/docs/api/vyper_contract/marshal_to_python.md new file mode 100644 index 00000000..16c84d6b --- /dev/null +++ b/docs/api/vyper_contract/marshal_to_python.md @@ -0,0 +1,31 @@ +# `marshal_to_python` + +### Signature + +```python +marshal_to_python(computation, vyper_typ) -> Any +``` + +### Description + +Converts the result of a computation to a Python object based on the Vyper type. + +- `computation`: The computation result to be converted. +- `vyper_typ`: The Vyper type of the result. +- Returns: The result as a Python object. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main() -> uint256: +... return 42 +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> result = contract.main() +>>> contract.marshal_to_python(result, uint256) +42 +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/overview.md b/docs/api/vyper_contract/overview.md new file mode 100644 index 00000000..ac03babb --- /dev/null +++ b/docs/api/vyper_contract/overview.md @@ -0,0 +1,42 @@ +# `VyperContract` + +### Description + +A contract instance. + +Internal and external contract functions are available as methods on `VyperContract` instances. + +### Methods + +- [eval](eval.md) +- [deployer](deployer.md) +TODO mention common classes + +- [marshal_to_python](marshal_to_python.md) +- [stack_trace](stack_trace.md) +- [trace_source](trace_source.md) +- [get_logs](get_logs.md) +- [decode_log](decode_log.md) +- [inject_function](inject_function.md) + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... +... @internal +... def foo() -> uint256: +... return 123 +... """ +>>> contract = boa.loads_partial(src, name="Foo").deploy() +>>> type(contract.main) + +>>> type(contract.internal.foo) + +>>> contract.internal.foo() +123 +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/stack_trace.md b/docs/api/vyper_contract/stack_trace.md new file mode 100644 index 00000000..4059c68c --- /dev/null +++ b/docs/api/vyper_contract/stack_trace.md @@ -0,0 +1,33 @@ +# `stack_trace` + +### Signature + +```python +stack_trace(computation=None) -> StackTrace +``` + +### Description + +Returns the stack trace of the computation. + +- `computation`: The computation to get the stack trace for. If `None`, uses the last computation. +- Returns: A `StackTrace` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... assert False, "error" +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> try: +... contract.main() +... except: +... pass +>>> contract.stack_trace() + +``` \ No newline at end of file diff --git a/docs/api/vyper_contract/trace_source.md b/docs/api/vyper_contract/trace_source.md new file mode 100644 index 00000000..f90c9835 --- /dev/null +++ b/docs/api/vyper_contract/trace_source.md @@ -0,0 +1,33 @@ +# `trace_source` + +### Signature + +```python +trace_source(computation) -> Optional[VyperTraceSource] +``` + +### Description + +Returns the source of the trace for the computation. + +- `computation`: The computation to get the trace source for. +- Returns: A `VyperTraceSource` instance or `None`. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... assert False, "error" +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> try: +... contract.main() +... except: +... pass +>>> contract.trace_source(contract._computation) + +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/__call__.md b/docs/api/vyper_deployer/__call__.md new file mode 100644 index 00000000..b1702afd --- /dev/null +++ b/docs/api/vyper_deployer/__call__.md @@ -0,0 +1,30 @@ +# `__call__` + +### Signature + +```python +__call__(*args, **kwargs) -> VyperContract +``` + +### Description + +Deploys the Vyper contract and returns a `VyperContract` instance. This method is a shorthand for the `deploy` method. + +- `*args`: Positional arguments to pass to the contract's constructor. +- `**kwargs`: Keyword arguments to pass to the contract's constructor. +- Returns: A `VyperContract` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer() +>>> type(contract) + +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/_constants.md b/docs/api/vyper_deployer/_constants.md new file mode 100644 index 00000000..c2c857b1 --- /dev/null +++ b/docs/api/vyper_deployer/_constants.md @@ -0,0 +1,26 @@ +# `_constants` + +### Property + +```python +@property +_constants: ConstantsModel +``` + +### Description + +Provides access to the constants defined in the Vyper contract. + +- Returns: A `ConstantsModel` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... x: constant(uint256) = 123 +... """ +>>> deployer = boa.loads_partial(src) +>>> deployer._constants.MY_CONSTANT +123 +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/at.md b/docs/api/vyper_deployer/at.md new file mode 100644 index 00000000..973560da --- /dev/null +++ b/docs/api/vyper_deployer/at.md @@ -0,0 +1,30 @@ +# `at` + +### Signature + +```python +at(address: Any) -> VyperContract +``` + +### Description + +Returns a `VyperContract` instance at a given address. + +- `address`: The address where the contract is deployed. +- Returns: A `VyperContract` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, "Foo") +>>> contract = deployer.deploy() +>>> contract_at_address = deployer.at(contract.address) +>>> type(contract_at_address) + +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/deploy.md b/docs/api/vyper_deployer/deploy.md new file mode 100644 index 00000000..084112f4 --- /dev/null +++ b/docs/api/vyper_deployer/deploy.md @@ -0,0 +1,30 @@ +# `deploy` + +### Signature + +```python +deploy(*args, **kwargs) -> VyperContract +``` + +### Description + +Deploys the Vyper contract and returns a `VyperContract` instance. + +- `*args`: Positional arguments to pass to the contract's constructor. +- `**kwargs`: Keyword arguments to pass to the contract's constructor. +- Returns: A `VyperContract` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> type(contract) + +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/deploy_as_blueprint.md b/docs/api/vyper_deployer/deploy_as_blueprint.md new file mode 100644 index 00000000..6cfa33df --- /dev/null +++ b/docs/api/vyper_deployer/deploy_as_blueprint.md @@ -0,0 +1,30 @@ +# `deploy_as_blueprint` + +### Signature + +```python +deploy_as_blueprint(*args, **kwargs) -> VyperBlueprint +``` + +### Description + +Deploys the Vyper contract as a blueprint and returns a `VyperBlueprint` instance. + +- `*args`: Positional arguments to pass to the contract's constructor. +- `**kwargs`: Keyword arguments to pass to the contract's constructor. +- Returns: A `VyperBlueprint` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, "Foo") +>>> blueprint = deployer.deploy_as_blueprint() +>>> type(blueprint) + +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/overview.md b/docs/api/vyper_deployer/overview.md new file mode 100644 index 00000000..9602d4aa --- /dev/null +++ b/docs/api/vyper_deployer/overview.md @@ -0,0 +1,33 @@ +# `VyperDeployer` + +### Description + +The `VyperDeployer` class is responsible for deploying Vyper contracts. It handles the compilation of Vyper source code and provides methods to deploy contracts and interact with them. + +### Methods + +- [deploy](deploy.md) +- [deploy_as_blueprint](deploy_as_blueprint.md) +- [stomp](stomp.md) +- [at](at.md) +- [\_\_call\_\_](__call__.md) + +### Properties + +- [standard_json](standard_json.md) +- [_constants](_constants.md) + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, name="Foo") +>>> contract = deployer.deploy() +>>> type(contract) + +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/standard_json.md b/docs/api/vyper_deployer/standard_json.md new file mode 100644 index 00000000..0626e870 --- /dev/null +++ b/docs/api/vyper_deployer/standard_json.md @@ -0,0 +1,28 @@ +# `standard_json` + +### Property + +```python +@property +standard_json: dict +``` + +### Description + +Generates a standard JSON representation of the Vyper contract. + +- Returns: A dictionary representing the standard JSON output. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, "Foo") +>>> deployer.standard_json +{'contracts': {'Foo': {'abi': [...], 'bin': '...'}}} +``` \ No newline at end of file diff --git a/docs/api/vyper_deployer/stomp.md b/docs/api/vyper_deployer/stomp.md new file mode 100644 index 00000000..e7420f7e --- /dev/null +++ b/docs/api/vyper_deployer/stomp.md @@ -0,0 +1,29 @@ +# `stomp` + +### Signature + +```python +stomp(address: Any, data_section=None) -> VyperContract +``` + +### Description + +Replaces the bytecode at a given address with the contract's runtime bytecode. + +- `address`: The address where the contract is deployed. +- `data_section`: Optional data section to append to the bytecode. +- Returns: A `VyperContract` instance. + +### Examples + +```python +>>> import boa +>>> src = """ +... @external +... def main(): +... pass +... """ +>>> deployer = boa.loads_partial(src, "Foo") +>>> contract = deployer.deploy() +>>> contract.stomp(contract.address) +``` \ No newline at end of file diff --git a/docs/api/vyper_internal_function/overview.md b/docs/api/vyper_internal_function/overview.md new file mode 100644 index 00000000..42abee0c --- /dev/null +++ b/docs/api/vyper_internal_function/overview.md @@ -0,0 +1,19 @@ +# `VyperInternalFunction` + +### Description + +Internal contract functions are exposed by wrapping them with a dummy external contract function, appending the wrapper's AST at the top of the contract, and then generating bytecode to run internal methods (as external methods). Therefore, they share the same API as `VyperFunction`. Internal functions can be accessed using the `internal` namespace of a `VyperContract`. + +### Examples + +```python +>>> import boa +>>> src = """ +... @internal +... def main(a: uint256) -> uint256: +... return 1 + a +... """ +>>> contract = boa.loads(src) +>>> contract.internal.main(68) +69 +``` \ No newline at end of file diff --git a/docs/assets/images/moccasin-symbol-col.svg b/docs/assets/images/moccasin-symbol-col.svg new file mode 100644 index 00000000..4f083b63 --- /dev/null +++ b/docs/assets/images/moccasin-symbol-col.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/assets/images/python-logo-only.svg b/docs/assets/images/python-logo-only.svg new file mode 100644 index 00000000..467b07b2 --- /dev/null +++ b/docs/assets/images/python-logo-only.svg @@ -0,0 +1,265 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/titanoboa-symbol-col.svg b/docs/assets/images/titanoboa-symbol-col.svg new file mode 100644 index 00000000..4c73f8ca --- /dev/null +++ b/docs/assets/images/titanoboa-symbol-col.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/assets/images/titanoboa-symbol-mono-pos.svg b/docs/assets/images/titanoboa-symbol-mono-pos.svg new file mode 100644 index 00000000..668a9648 --- /dev/null +++ b/docs/assets/images/titanoboa-symbol-mono-pos.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/assets/images/vyper-symbol-col.svg b/docs/assets/images/vyper-symbol-col.svg new file mode 100644 index 00000000..c7618abf --- /dev/null +++ b/docs/assets/images/vyper-symbol-col.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/assets/javascript/extra.js b/docs/assets/javascript/extra.js new file mode 100644 index 00000000..eb9a0e8a --- /dev/null +++ b/docs/assets/javascript/extra.js @@ -0,0 +1,26 @@ +document.addEventListener('DOMContentLoaded', function() { + const functionAdmonitions = document.querySelectorAll('.admonition.function'); + + functionAdmonitions.forEach(admonition => { + const content = admonition.innerHTML; + const sourceCodeMatch = content.match(/source-code:\s*(https?:\/\/\S+)/); + if (sourceCodeMatch) { + const sourceCodeUrl = sourceCodeMatch[1]; + + // Remove the source-code line from the visible content + admonition.innerHTML = content.replace(/source-code:\s*https?:\/\/\S+\s*/, ''); + + const title = admonition.querySelector('.admonition-title'); + if (title) { + title.style.position = 'relative'; // Add this line + const sourceCodeButton = document.createElement('a'); + sourceCodeButton.textContent = 'Source Code'; + sourceCodeButton.href = sourceCodeUrl; + sourceCodeButton.target = '_blank'; + sourceCodeButton.classList.add('source-code-button'); + + title.appendChild(sourceCodeButton); + } + } + }); +}); diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css new file mode 100644 index 00000000..044b5848 --- /dev/null +++ b/docs/assets/stylesheets/extra.css @@ -0,0 +1,237 @@ +:root { + --md-primary-fg-color: #180C25; +} + +/* Style for hyperlinks in the main content, excluding navigation and tabs */ +.md-content a[href]:not(.md-nav__link):not(.tabbed-labels label a) { + color: #4a90e2; /* A moderate blue color */ + text-decoration: none; + border-bottom: 1px solid #81b3e0; /* A lighter blue for the underline */ + transition: color 0.2s ease-in-out, border-bottom 0.2s ease-in-out; +} + +.md-content a[href]:not(.md-nav__link):not(.tabbed-labels label a):hover { + color: #2c5282; /* Darken the text on hover */ + border-bottom: 2px solid #5a9de2; /* Thicken and slightly darken the underline on hover */ +} + + +/* ADMONITIONS */ + +/* Change font for all admonition titles */ +.md-typeset .admonition-title, +.md-typeset summary { + font-family: 'Inconsolata', monospace; + font-weight: 700; /* Bold */ +} + + +/* vyper */ +:root { + --md-admonition-icon--vyper: url('data:image/svg+xml;charset=utf-8,'); + } + + .md-typeset .admonition.vyper, + .md-typeset details.vyper { + border-color: #9F4CF2; + } + + .md-typeset .vyper > .admonition-title, + .md-typeset .vyper > summary { + background-color: rgba(159, 76, 242, 0.1); + border-color: #9F4CF2; + } + + .md-typeset .vyper > .admonition-title::before, + .md-typeset .vyper > summary::before { + background-color: #9F4CF2; + -webkit-mask-image: var(--md-admonition-icon--vyper); + mask-image: var(--md-admonition-icon--vyper); + } + + /* dark mode */ + [data-md-color-scheme="slate"] .md-typeset .vyper > .admonition-title, + [data-md-color-scheme="slate"] .md-typeset .vyper > summary { + background-color: rgba(159, 76, 242, 0.2); + } + + +/* titanoboa */ +:root { + --md-admonition-icon--titanoboa: url('data:image/svg+xml;charset=utf-8,'); + } + + .md-typeset .admonition.titanoboa, + .md-typeset details.titanoboa { + border-color: #42E21E; + } + + .md-typeset .titanoboa > .admonition-title, + .md-typeset .titanoboa > summary { + background-color: rgba(66, 226, 30, 0.1); + border-color: #42E21E; + } + + .md-typeset .titanoboa > .admonition-title::before, + .md-typeset .titanoboa > summary::before { + background-color: #42E21E; + -webkit-mask-image: var(--md-admonition-icon--titanoboa); + mask-image: var(--md-admonition-icon--titanoboa); + } + + /* dark mode */ + [data-md-color-scheme="slate"] .md-typeset .titanoboa > .admonition-title, + [data-md-color-scheme="slate"] .md-typeset .titanoboa > summary { + background-color: rgba(66, 226, 30, 0.2); + } + + +/* moccasin */ +:root { + --md-admonition-icon--moccasin: url('data:image/svg+xml;charset=utf-8,'); + } + + .md-typeset .admonition.moccasin, + .md-typeset details.moccasin { + border-color: #FF8F08; + } + + .md-typeset .moccasin > .admonition-title, + .md-typeset .moccasin > summary { + background-color: rgb(246, 242, 233); + border-color: #FF8F08; + } + + .md-typeset .moccasin > .admonition-title::before, + .md-typeset .moccasin > summary::before { + background-color: #FF8F08; + -webkit-mask-image: var(--md-admonition-icon--moccasin); + mask-image: var(--md-admonition-icon--moccasin); + } + + /* dark mode */ + [data-md-color-scheme="slate"] .md-typeset .moccasin > .admonition-title, + [data-md-color-scheme="slate"] .md-typeset .moccasin > summary { + background-color: rgb(246, 242, 233) + } + + +/* Python */ +:root { + --md-admonition-icon--python: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-typeset .admonition.python, +.md-typeset details.python { + border-color: #3776AB; +} + +.md-typeset .python > .admonition-title, +.md-typeset .python > summary { + background-color: rgba(55, 118, 171, 0.15); + border-color: #3776AB; +} + +.md-typeset .python > .admonition-title::before, +.md-typeset .python > summary::before { + background-color: #3776AB; + -webkit-mask-image: var(--md-admonition-icon--python); + mask-image: var(--md-admonition-icon--python); +} + +/* dark mode */ +[data-md-color-scheme="slate"] .md-typeset .python > .admonition-title, +[data-md-color-scheme="slate"] .md-typeset .python > summary { + background-color: rgba(55, 118, 171, 0.25); +} + +/* Styles for main navigation items including "Titanoboa docs" */ +.md-nav__title:first-child, +.md-nav__item--section > .md-nav__link { + text-decoration: underline; + text-underline-offset: 4px; + font-weight: bold; +} + +/* Optional: style for when hovering over these items */ +.md-nav__title:first-child:hover, +.md-nav__item--section > .md-nav__link:hover { + text-decoration-thickness: 2px; +} + +/* function */ +:root { + --md-admonition-icon--function: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-typeset .admonition.function, +.md-typeset details.function { + border-color: #180C25; + position: relative; +} + +.md-typeset .function > .admonition-title, +.md-typeset .function > summary { + background-color: rgba(24, 12, 37, 0.1); + border-color: #180C25; + display: flex; + justify-content: space-between; + align-items: center; + padding-right: 2.4rem; /* Increased padding-right */ + position: relative; +} + +.md-typeset .function > .admonition-title::before, +.md-typeset .function > summary::before { + background-color: #180C25; + -webkit-mask-image: var(--md-admonition-icon--function); + mask-image: var(--md-admonition-icon--function); +} + +/* Add this new rule to override the global styles */ +.md-content .md-typeset .admonition .source-code-link, +.md-content .md-typeset .admonition .source-code-link:hover { + color: #180C25 !important; + text-decoration: none !important; + border-bottom: none !important; + transition: color 0.2s ease-in-out !important; +} + +/* Existing styles */ +.source-code-link { + position: absolute; + top: 0.38rem; /* Adjusted from 0.25rem to move it down */ + right: 0.3rem; /* Adjusted from 0.4rem to move it right */ + display: inline-flex; + align-items: center; + font-size: 0.7rem; + font-weight: 600; +} + +.source-code-link::before { + content: ""; + display: inline-block; + width: 20px; + height: 20px; + margin-right: 0.3rem; + background-image: url('data:image/svg+xml;charset=utf-8,'); + background-repeat: no-repeat; + background-size: contain; + transition: all 0.2s ease-in-out; +} + +.source-code-link:hover::before { + background-image: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-content .md-typeset .admonition .source-code-link:hover, +.md-typeset .admonition .source-code-link:hover { + color: #4a90e2 !important; +} + +/* Override any global styles that might be adding underlines or borders */ +.md-typeset .source-code-link, +.md-typeset .source-code-link:hover { + border-bottom: none; + text-decoration: none; +} \ No newline at end of file diff --git a/docs/explain/caching.md b/docs/explain/caching.md new file mode 100644 index 00000000..b0df796b --- /dev/null +++ b/docs/explain/caching.md @@ -0,0 +1,43 @@ +# Caching + +## Forked States + +Titanoboa caches states when running in fork mode. +It uses [LevelDB](https://github.com/google/leveldb) via the [Plyvel](https://plyvel.readthedocs.io) wrapper. +This allows forking to take less time and use less memory. + +To enable it, install `plyvel`, which is a wrapper around the C++ LevelDB. +Titanoboa will automatically use it. + +The cache file is by default located at `~/.cache/titanoboa/fork.db` +To customize this folder, pass the `cache_file` argument to the `fork` function (see [fork](../api/testing.md#fork)). +In case cache_file is `None`, cache will be disabled. + +!!! note + If you are not using Linux, you might need to install `plyvel-ci` instead. + This is part of a [Pull Request](https://github.com/wbolster/plyvel/pull/129) waiting for merge since 2021. + +!!! warning + Caching a fresh block might lead to incorrect results and stale cache files. + +!!! warning + When running boa in parallel (e.g. with pytest-xdist), the cache file will be shared between all processes. + This can lead to more requests being made than necessary if multiple processes are requesting the same block. + +## Compilation results + +By default, Titanoboa caches compilation results on Disk. +The location of this cache is by default `~/.cache/titanoboa` and the files are called `{sha256_digest}.pickle`. + +To change the cache location, call [`set_cache_dir`](../api/cache.md#set_cache_dir) with the desired path. +In case the path is `None`, caching will be disabled. +Alternatively, call [`disable_cache`](../api/cache.md#disable_cache) to disable caching. + +## Etherscan + +The utility [`from_etherscan`](../api/load_contracts.md#from_etherscan) fetches the ABI for a contract at a given address from Etherscan and returns an `ABIContract` instance. + +Given Etherscan is rate-limited, it is recommended to cache the results. +In order to enable this, Titanoboa uses the [requests_cache](https://pypi.org/project/requests-cache/) package. + +If the package is available in the environment, all requests to Etherscan will be cached. diff --git a/docs/explain/revert_reasons.md b/docs/explain/revert_reasons.md new file mode 100644 index 00000000..5b049ebb --- /dev/null +++ b/docs/explain/revert_reasons.md @@ -0,0 +1,58 @@ +# Revert Reasons + +A contract may revert during the runtime via the `REVERT` opcode. +During execution, the revert does exactly the same thing independent on the error source. +However, for a developer, there are different reasons to get a revert. +Each of them may be used with [`boa.reverts`](../api/testing.md#boareverts) to test the contract behavior. + +## Compiler Revert Reasons + +These happen when the compiler generates the error message. +For example: +- Range errors +- Overflows +- Division by zero +- Re-entrancy locks + +Things like syntax errors will not be caught during the runtime, but the contract will fail to compile on the first place. + +## User Revert Reasons + +These happen when the user calls `raise` or `assert` fails in the contract. +The user may provide a reason for the revert, which will be shown to the end user. +!!! vyper + ```vyper + @external + def foo(x: uint256): + assert x > 0, "x must be greater than 0" + ``` + +Note that this may happen directly on the contract being called, or any external contract that the contract interacts with. + +## Dev Revert Reasons + +Developer reverts are also raised by `assert` statements in the code. +However, by adding a `# dev: ` comment after the assert call, Titanoboa is able to verify the reason and provide a more detailed error message. + +!!! vyper + ```vyper + @external + def foo(x: uint256): + assert x > 0 # dev: "x must be greater than 0" + ``` + +These reasons are completely offchain and useful when the contract storage is limited (EIP 170). + +Traditional revert strings cost gas and bytecode when deploying. +When a revert condition is triggered, extra gas will also be incurred. +Strings are relatively heavy - each character consumes gas. + +However, when using [`VyperContract`](../api/vyper_contract/overview.md), Titanoboa is able to track the line where the revert happened. +If it finds a `# dev: ` comment, it will provide a more detailed error message to the developer. + +This is particularly useful when testing contracts with [`boa.reverts`](../api/testing.md#boareverts), for example: +!!! python + ```python + with boa.reverts(dev="x must be greater than 0"): + contract.foo(0) + ``` diff --git a/docs/explain/singleton_env.md b/docs/explain/singleton_env.md new file mode 100644 index 00000000..666915e1 --- /dev/null +++ b/docs/explain/singleton_env.md @@ -0,0 +1,99 @@ +# Titanoboa Environments + +When calling boa functions like `boa.load` or `boa.loads` to [deploy a contract](../api/load_contracts.md), the global (singleton) environment is used by default. + +There are several types of environments depending on the usage. + +## Env + +The default environment, with a [py-evm](https://github.com/ethereum/py-evm) backend. +Without forking, this environment will start empty. + +This is used for most testing purposes. +To set it as the singleton env, call [`boa.reset_env`](../api/env/singleton.md#reset_env). + +### Forking + +The `Env` can be forked to a local chain using the [`fork`](../api/testing.md#fork) function. +That requires an RPC URL and (optionally) a block identifier. +This will customize the environment's AccountDB to use the RPC as data source. + +While forking, all execution will still run via [py-evm](https://github.com/ethereum/py-evm). +To set it as the singleton env, call [`boa.fork`](../api/env/singleton.md#fork). + +## `NetworkEnv` + +The [`NetworkEnv`](../api/env/network_env.md) is used to connect to a network via an RPC. +This is used to connect to a real network and deploy contracts. +To set it as the singleton env, call [`boa.set_network_env`](../api/env/singleton.md#set_network_env). + +To sign transactions, the `NetworkEnv` uses the `Account` object (from [eth_account](https://eth-account.readthedocs.io/en/stable/eth_account.html#module-eth_account.account)). +That object can be created from a private key, mnemonic or a keystore file. +It must be registered by calling [`add_account`](../api/env/network_env.md#add_account). + +See also [`BrowserEnv`](#browserenv) to use a browser wallet. + +## `BrowserEnv` + +The [`BrowserEnv`](../api/env/browser_env.md) is actually a [`NetworkEnv`](#networkenv), but used in a Jupyter notebook (or in Google Colab). +However, it uses a browser to interact with the network: +- No RPC URL is needed, as it uses the browser's wallet. +- No need to configure accounts, as it integrates with the browser's wallet for signing transactions. + +To set it as the singleton env, call [`boa.set_browser_env`](../api/env/singleton.md#set_browser_env). + +### Communication between Python and the browser +The browser environment injects JavaScript code into the notebook with the `display` iPython functionality. +The injected code handles the communication between the wallet and the Python kernel. + +When running in JupyterLab, a Tornado [HTTP endpoint](https://github.com/vyperlang/titanoboa/blob/v0.2.4/boa/integrations/jupyter/handlers.py) is created to receive wallet callbacks. +In Google Colab, the communication is done via the [`colab_eval_js` function](https://github.com/vyperlang/titanoboa/blob/v0.2.4/boa/integrations/jupyter/browser.py#L184-L187), which supports asynchronous code. + +## Automatic context management + +Since version v0.2.4, all the `set_env` functions return an optional context manager. +This allows you to use the environment in a `with` block, and it will automatically revert to the previous environment when the block exits. + +!!! python + ```python + + # set function may be called inside a context manager + with boa.set_network_env(rpc_url): + # This code will run in the network environment + ... + + # here the previous environment is restored + ``` + +However, it may be also called outside a context manager. +In that case, the previous environment is lost. + +!!! python + ```python + + boa.set_network_env(rpc_url) + + ``` + +## Anchor & auto-revert + +All the `env` classes allow you to set an anchor. +That means that anything that happens inside the `with` block, will be reverted in the end. + +When the `anchor` is set, the env will take a database snapshot. +When the block exits, the database will be reverted to that snapshot. + +For example: +!!! python + ```python + + with boa.env.anchor(): + contract = boa.loads(code) + # use contract here + + # now the contract is gone! + ``` + +### Test plugin +Titanoboa provides a pytest plugin that will automatically call `anchor` for every test function, unless the `ignore_isolation` marker is provided. +Read more in [testing with pytest](../tutorials/pytest.md#titanoboa-plugin). diff --git a/docs/explain/tx_context.md b/docs/explain/tx_context.md new file mode 100644 index 00000000..3f71880f --- /dev/null +++ b/docs/explain/tx_context.md @@ -0,0 +1,9 @@ +# Transaction Context + +Currently boa does not support transaction context. + +This is an issue that makes it impossible to have accurate gas profiling. + +TODO explain more + +TODO explain gas metering classes here \ No newline at end of file diff --git a/docs/explain/vvm_contracts.md b/docs/explain/vvm_contracts.md new file mode 100644 index 00000000..b60366f5 --- /dev/null +++ b/docs/explain/vvm_contracts.md @@ -0,0 +1,16 @@ +# Legacy Vyper Contracts + +Titanoboa supports legacy Vyper contracts, which are Vyper contracts that are not compatible with the latest Vyper version. + +[Vyper Version Manager (vvm)]() is used to whenver a contract with a Vyper version lower than the latest Vyper version is detected. + +VVM will install the correct version of the compiler on the fly and use it to compile the contract. + +However this comes with a performance overhead and some limitations, here's a non-exhaustive list of limitations: + +- The correct version of the compiler has to be downloaded on the fly, which can be slow on the first run. +- Functionalities that are specific to [`VyperContract`](../api/vyper_contract/overview.md) might not be available (i.e. pretty error traces). +- Using a legacy Vyper contract is not recommended as older versions contain known bugs and security issues. + + + diff --git a/docs/guides/forge.md b/docs/guides/forge.md new file mode 100644 index 00000000..f166c320 --- /dev/null +++ b/docs/guides/forge.md @@ -0,0 +1 @@ +# Forge analogues diff --git a/docs/guides/scripting/ipython_vyper_cells.md b/docs/guides/scripting/ipython_vyper_cells.md new file mode 100644 index 00000000..071f2302 --- /dev/null +++ b/docs/guides/scripting/ipython_vyper_cells.md @@ -0,0 +1,39 @@ +## ipython Vyper Cells + +Titanoboa supports iPython Vyper "magic" cells with the `%%vyper` magic command. +To enable the cell magic, add a `%load_ext boa.ipython` cell after installing boa. + +This means that you can write Vyper code in an iPython/Jupyter Notebook environment and execute it as if it was a Python cell (the contract will be compiled instead, and a `ContractFactory` will be returned). + +You can use Jupyter to execute Titanoboa code in network mode from your browser using any wallet - using your wallet to sign transactions and call the RPC. +To set up the environment, simply run [`boa.set_browser_env`](../../api/env/singleton.md#set_browser_env). +For a full example, please see [this example Jupyter notebook](https://colab.research.google.com/drive/1d79XDUBXNhxNX67KSlNnWADyB0_ef7tN). + +!!!python "iPython" + ```python + In [1]: import boa; boa.env.fork(url="") + + In [2]: %load_ext boa.ipython + + In [3]: %%vyper Test + ...: interface HasName: + ...: def name() -> String[32]: view + ...: + ...: @external + ...: def get_name_of(addr: HasName) -> String[32]: + ...: return addr.name() + Out[3]: + + In [4]: c = Test.deploy() + + In [5]: c.get_name_of("0xD533a949740bb3306d119CC777fa900bA034cd52") + Out[5]: 'Curve DAO Token' + ``` + +### JupyterLab +The Vyper team provides the website [try.vyperlang.org](https://try.vyperlang.org) where you can try Vyper code directly in your browser. +To run your own instance of JupyterLab, please check the [try.vyperlang.org repository](https://github.com/vyperlang/try.vyperlang.org/blob/93751db5b/README.md#running-locally). + +### Google Colab +Another convenient way to run Vyper code in the browser is by using [Google Colab](https://colab.research.google.com/). +This is a free service that allows you to run notebooks in the cloud without any setup. diff --git a/docs/guides/scripting/native_import_syntax.md b/docs/guides/scripting/native_import_syntax.md new file mode 100644 index 00000000..d1e02cf9 --- /dev/null +++ b/docs/guides/scripting/native_import_syntax.md @@ -0,0 +1,28 @@ +## Native Import Syntax + +Titanoboa supports the native Python import syntax for Vyper contracts. This means that you can import Vyper contracts in any Python script as if you were importing a Python module. + +For example, if you have a contract `contracts/Foo.vy`: + +```vyper +x: public(uint256) + +def __init__(x_initial: uint256): + self.x = x_initial +``` + +You can import it in a Python script `tests/bar.py` like this + +```python +from contracts import Foo + +my_contract = Foo(42) # This will create a new instance of the contract + +my_contract.x() # Do anything with the contract as you normally would +``` + +Internally this will use the `importlib` module to call [`boa.load_partial`](../../api/load_contracts.md#load_partial) on the file and create a `ContractFactory`. + +!!! note + For this to work `boa` must be imported first. + Due to limitations in the Python import system, only imports of the form `import Foo` or `from import Foo` will work and it is not possible to use `import `. diff --git a/docs/guides/testing/coverage.md b/docs/guides/testing/coverage.md new file mode 100644 index 00000000..e9ae6219 --- /dev/null +++ b/docs/guides/testing/coverage.md @@ -0,0 +1,29 @@ +## Coverage + +!!! warning + Coverage is not yet supported when using [fast mode](../../api/env/env.md#enable_fast_mode). + +Titanoboa offers coverage through the [coverage.py](https://coverage.readthedocs.io/) package. + +To use, add the following to `.coveragerc`: + +``` +[run] +plugins = boa.coverage +``` + +(for more information see https://coverage.readthedocs.io/en/latest/config.html) + +Then, run with `coverage run ...` + +To run with pytest, we do the following: + +``` +pytest --cov= --cov-branch ... +``` + +Finally, `coverage.py` saves coverage data to a file named `.coverage` in the directory it is run in. To view the formatted coverage data, you typically want to use `coverage report` or `coverage html`. See more options at https://coverage.readthedocs.io/en/latest/cmd.html. + +!!! note + Coverage is experimental and there may be odd corner cases! If so, please report them on github or in the `#titanoboa-interpreter` channel of the [Vyper discord](https://discord.gg/6tw7PTM7C2). + diff --git a/docs/guides/testing/fuzzing_strategies.md b/docs/guides/testing/fuzzing_strategies.md new file mode 100644 index 00000000..bd387843 --- /dev/null +++ b/docs/guides/testing/fuzzing_strategies.md @@ -0,0 +1,5 @@ +## Fuzzing Strategies + +Titanoboa offers custom [hypothesis](https://hypothesis.readthedocs.io/en/latest/quickstart.html) strategies for testing. These can be used to generate EVM-compliant random inputs for tests. + +TODO \ No newline at end of file diff --git a/docs/guides/testing/gas_profiling.md b/docs/guides/testing/gas_profiling.md new file mode 100644 index 00000000..47b4f5df --- /dev/null +++ b/docs/guides/testing/gas_profiling.md @@ -0,0 +1,49 @@ +# Gas profiling + +Titanoboa has native gas profiling tools that store and generate statistics upon calling a contract. When enabled, gas costs are stored per call in `global_profile().call_profiles` and `global_profile().line_profiles` dictionaries. +To enable gas profiling, + +1. decorate tests with `@pytest.mark.gas_profile`, or +2. run pytest with `--gas-profile`, e.g. `pytest tests/unitary --gas-profile` + +If `--gas-profile` is selected, to ignore gas profiling for specific tests, decorate the test with `@pytest.mark.ignore_gas_profiling`. + +```python +@pytest.mark.profile +def test_profile(): + + source_code = """ +@external +@view +def foo(a: uint256 = 0): + x: uint256 = a +""" + contract = boa.loads(source_code, name="FooContract") + contract.foo() +``` + +``` +┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━┳━━━━━┓ +┃ Contract ┃ Address ┃ Computation ┃ Count ┃ Mean ┃ Median ┃ Stdev ┃ Min ┃ Max ┃ +┑━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━╇━━━━━┩ +β”‚ FooContract β”‚ 0x0000000000000000000000000000000000000066 β”‚ foo β”‚ 1 β”‚ 88 β”‚ 88 β”‚ 0 β”‚ 88 β”‚ 88 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜ + + +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┓ +┃ Contract ┃ Computation ┃ Count ┃ Mean ┃ Median ┃ Stdev ┃ Min ┃ Max ┃ +┑━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━┩ +β”‚ Path: β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ Name: FooContract β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ Address: 0x0000000000000000000000000000000000000066 β”‚ β”‚ Count β”‚ Mean β”‚ Median β”‚ Stdev β”‚ Min β”‚ Max β”‚ +β”‚ ---------------------------------------------------- β”‚ -------------------------------------------------------------------------- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ +β”‚ Function: foo β”‚ 4: def foo(a: uint256 = 0): β”‚ 1 β”‚ 73 β”‚ 73 β”‚ 0 β”‚ 73 β”‚ 73 β”‚ +β”‚ β”‚ 5: x: uint256 = a β”‚ 1 β”‚ 15 β”‚ 15 β”‚ 0 β”‚ 15 β”‚ 15 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +!!! note + If a specific fixture is called in two separate tests, pytest will re-instantiate it. Meaning, if a Contract is deployed in a fixture, calling the fixture on tests in two separate files can lead to two deployments of that Contract, and hence two separate addresses in the profile table. + +!!! warning + Profiling does not work with pytest-xdist plugin at the moment. \ No newline at end of file diff --git a/docs/guides/testing/private_members.md b/docs/guides/testing/private_members.md new file mode 100644 index 00000000..942a2362 --- /dev/null +++ b/docs/guides/testing/private_members.md @@ -0,0 +1,89 @@ +# Accessing private members + +Titanoboa allows access to private members of a contract. This is useful for testing internal functions or variables without having to expose them to the outside world. + +Given a vyper module `foo.vy` in the same folder as your python code: + +```vyper +x: uint256 +y: immutable(uint256) + +def __init__(y_initial: uint256): + self.x = 42 + self.y = y_initial + +@internal +@pure +def _bar() -> uint256: + return 111 +``` + +We can notice that `x`, `y` and `_bar` are all private members of the contract that wouldn't be easily accessible in a normal context. Boa allows access to all of these members, let's see how. + +## Accessing internal functions + +`internal` functions can be accessed by calling the function from the `internal` attribute of the contract. + +```python +import foo + +my_contract = foo(1234) + +my_contract.internal._bar() # returns 111 +``` + +## Accessing private storage variables + +Private storage variables can be accessed by calling the variable from the `_storage` attribute of the contract: + +```python +import foo + +my_contract = foo(1234) + +my_contract._storage.x.get() # returns 42 +``` + +## Accessing private immutable variables + +Similarly private immutable variables can be accessed by calling the variable from the `_immutable` attribute of the contract: + +```python +import foo + +my_contract = foo(1234) + +my_contract._immutables.y # returns 1234 +``` + +## Accessing internal module variables + +Since vyper 0.4.0 it is possible to modularize contracts. + +Boa doesn't yet support accessing private module variables. However this can easily be done using [`eval`]() TODO. + +Given a module `bar.vy`: + +```vyper +# bar.vy +x: uint256 +``` + +If another contract `foo.vy` imports `bar`: + +```vyper +# foo.vy +import bar + +foo: public(uint256) +``` + +It is possible to access `bar.x` from `foo` using eval: + + +```python +foo = boa.load("foo.vy") + +foo.eval("bar.x") # returns the value of bar.x +``` + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..7c3c2d52 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +# Overview + +Titanoboa (also called `boa`) is a [Vyper](https://vyper.readthedocs.io/) interpreter designed to provide a modern, advanced and integrated development experience with: + +- [pretty tracebacks](api/common_classes/call_trace.md) +- [forking](api/testing.md#fork) +- [debugging features](tutorials/debug.md) +- [opcode patching](api/pyevm/patch_opcode.md) +- [pytest integration](tutorials/pytest.md#titanoboa-plugin) +- [jupyter integration](api/env/browser_env.md) +- [iPython integration](guides/scripting/ipython_vyper_cells.md) +- [native Python import syntax](guides/scripting/native_import_syntax.md) +- [legacy Vyper support](api/vvm_deployer/overview.md) +- *and more ...* + +`titanoboa` is not just a framework, but a library that can be used in any Python environment. +It is designed to be used in [jupyter notebooks](guides/scripting/ipython_vyper_cells.md), [Python scripts](guides/scripting/native_import_syntax.md), or [tests](tutorials/pytest.md) (any Python testing framework is compatible) to provide a seamless experience and as little context-switching overhead as possible between Python and Vyper. diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 747ffb7b..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 9304738c..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -sphinx-rtd-theme>=1.3.0,<1.4.0 diff --git a/docs/source/api.rst b/docs/source/api.rst deleted file mode 100644 index 3c6d8710..00000000 --- a/docs/source/api.rst +++ /dev/null @@ -1,837 +0,0 @@ -API Reference -============= - -High-Level Functionality ------------------------- - -.. module:: boa - -.. py:data:: env - :type: boa.environment.Env - - The global environment object. - -.. function:: load(fp: str, *args: Any, **kwargs: Any) -> VyperContract | VyperBlueprint - - Compile source from disk and return a deployed instance of the contract. - - :param fp: The contract source code file path. - :param args: Contract constructor arguments. - :param kwargs: Keyword arguments to pass to the :py:func:`loads` function. - - .. rubric:: Example - - .. code-block:: python - - # Foo.vy - @external - def addition(a: uint256, b: uint256) -> uint256: - return a + b - - .. code-block:: python - - >>> import boa - >>> boa.load("Foo.vy") - - - .. code-block:: python - - >>> import boa - >>> from vyper.compiler.settings import OptimizationLevel, Settings - >>> boa.load("Foo.vy", compiler_args={"settings": Settings(optimize=OptimizationLevel.CODESIZE)}) - - -.. function:: loads(source: str, *args: Any, as_blueprint: bool = False, name: str | None = None, compiler_args: dict | None = None, **kwargs) -> VyperContract | VyperBlueprint - - Compile source code and return a deployed instance of the contract. - - :param source: The source code to compile and deploy. - :param args: Contract constructor arguments. - :param as_blueprint: Whether to deploy an :eip:`5202` blueprint of the compiled contract. - :param name: The name of the contract. - :param compiler_args: Argument to be passed to the Vyper compiler. - :param kwargs: Keyword arguments to pass to the :py:class:`VyperContract` or :py:class:`VyperBlueprint` ``__init__`` method. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... value: public(uint256) - ... @external - ... def __init__(_initial_value: uint256): - ... self.value = _initial_value - ... """ - >>> boa.loads(src, 69) - - -.. function:: load_partial(fp: str, compiler_args: dict | None = None) -> VyperDeployer - - Compile source from disk and return a :py:class:`VyperDeployer`. - - :param fp: The contract source code file path. - :param compiler_args: Argument to be passed to the Vyper compiler. - :returns: A :py:class:`VyperDeployer` factory instance. - - .. rubric:: Example - - .. code-block:: python - - # Foo.vy - @external - def addition(a: uint256, b: uint256) -> uint256: - return a + b - - .. code-block:: python - - >>> import boa - >>> boa.load_partial("Foo.vy") - - -.. function:: loads_partial(source: str, name: str | None = None, dedent: bool = True, compiler_args: dict | None = None) -> VyperDeployer - - Compile source and return a :py:class:`VyperDeployer`. - - :param source: The Vyper source code. - :param name: The name of the contract. - :param dedent: If `True`, remove any common leading whitespace from every line in `source`. - :param compiler_args: Argument to be passed to the Vyper compiler. - :returns: A :py:class:`VyperDeployer` factory instance. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @external - ... def main(): - ... pass - ... """ - >>> boa.loads_partial(src, "Foo") - - - -.. function:: load_abi(filename: str, name: str = None) -> ABIContractFactory - - Return a :py:class:`ABIContractFactory` from an ABI file (.json) - - :param filename: The file containing the ABI as a JSON string (something like ``my_abi.json``) - :param name: The name of the contract. - :returns: A :py:class:`ABIContractFactory` factory instance. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> filename = "foo.json" - >>> boa.load_abi(src, name="Foo") - - - -.. function:: loads_abi(json_str: str, name: str = None) -> ABIContractFactory - - Return a :py:class:`ABIContractFactory` from an ABI string - - :param json_str: The ABI as a JSON string (something which can be passed to ``json.loads()``) - :param name: The name of the contract. - :returns: A :py:class:`ABIContractFactory` factory instance. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """[{"stateMutability": "nonpayable", "type": "function", "name": "foo", "inputs": [{"name": "", "type": "bytes"}], "outputs": [{"name": "", "type": "bytes"}]}]""" - >>> boa.loads_abi(src, name="Foo") - - - -.. function:: from_etherscan(address: str | bytes | Address, name: str = None, uri: str = "https://api.etherscan.io/api", api_key: str = None) -> ABIContract - - Fetch the ABI for an address from etherscan and return an :py:class:`ABIContract` - - :param address: The address. Can be str, bytes or Address - :param name: (Optional) The name of the contract. - :returns: A :py:class:`ABIContract` instance. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa, os - >>> boa.env.fork(os.environ["ALCHEMY_MAINNET_ENDPOINT"]) - >>> crvusd = boa.from_etherscan("0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E", name="crvUSD") - >>> crvusd - - >>> crvusd.totalSupply() - 730773174461124520709282012 - - -.. function:: eval(statement: str) -> Any - - Evaluate a Vyper statement in the context of a contract with no state. - - :param statement: A valid Vyper statement. - :returns: The result of the statement execution. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> boa.eval("keccak256('Hello World!')").hex() - '3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0' - >>> boa.eval("empty(uint256[10])") - (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - -.. function:: reverts(reason: str | None = None, /, **kwargs: str) - - A context manager which validates an execution error occurs with optional reason matching. - - .. note:: - - If a keyword argument is provided, as opposed to a positional argument, the argument - name and value will be used to validate against a developer revert comment. - - :param reason: A string to match against the execution error. - :param compiler: A string to match against the internal compiler revert reason. - :param vm_error: A string to match against the revert reason string. - :raises AssertionError: If there is more than one argument. - :raises ValueError: If the execution did not have an error. - :raises ValueError: If the reason string provided does not match the error that occurred. - - .. rubric:: Examples - - Revert reason provided as a positional argument: - - .. code-block:: python - - import boa - - source = """ - @external - def foo(): - raise "0xdeadbeef" - - @external - def bar(): - raise # dev: 0xdeadbeef - """ - contract = boa.loads(source) - - with boa.reverts("0xdeadbeef"): - contract.foo() - - with boa.reverts("0xdeadbeef"): - contract.bar() - - Compiler revert reason: - - .. code-block:: python - - import boa - - source = """ - @external - def subtract(a: uint256, b: uint256) -> uint256: - return a - b - - @external - def addition(a: uint256, b: uint256) -> uint256: - return a + b - """ - contract = boa.loads(source) - - with boa.reverts(compiler="safesub"): - contract.subtract(1, 2) - - with boa.reverts(compiler="safeadd"): - contract.addition(1, 2**256 - 1) - - VM error reason: - - .. code-block:: python - - import boa - - source = """ - @external - def main(a: uint256): - assert a == 0, "A is not 0" - """ - contract = boa.loads(source) - - with boa.reverts(vm_error="A is not 0"): - contract.main(69) - - Developer revert comment: - - .. code-block:: python - - import boa - - source = """ - @external - def main(a: uint256): - assert a == 0 # dev: a is not 0 - """ - contract = boa.loads(source) - - with boa.reverts(dev="a is not 0"): - contract.main(69) - -.. function:: register_precompile(address: str, fn: Callable[[eth.abc.ComputationAPI], None], force: bool = False) - - Register a precompile. - - :param address: The address to register the precompile at. - :param fn: The function to execute when the precompile is called. - :param force: Whether to overwrite the precompile function if one is already registered at the specified address. - :raises ValueError: If a precompile is already registered at the specified address and the force argument is ``False``. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> log = lambda computation: print("0x" + computation.msg.sender.hex()) - >>> boa.register_precompile("0x00000000000000000000000000000000000000ff", log) - >>> boa.eval("raw_call(0x00000000000000000000000000000000000000ff, b'')") - 0x0000000000000000000000000000000000000069 - -.. function:: deregister_precompile(address: str, force: bool = True) - - Deregister a precompile. - - :param address: The address of a previously registered precompile. - :param force: Whether to force removal of the precompile at the specified address. - :raises ValueError: If a precompile is not registered at the specified address and the force argument is ``False``. - -.. function:: patch_opcode(opcode: int, fn: Callable[[eth.abc.ComputationAPI], None]) - - Patch an opcode. - - :param opcode: The opcode to patch. - :param fn: The function implementing the desired opcode functionality. - - .. note:: - - The function provided as an argument should be defined with a single keyword parameter, ``computation``, like so: - - .. code-block:: python - - def baz(computation: eth.abc.ComputationAPI): - ... - - .. rubric:: Example - - The following code snippet implements tracing for the ``CREATE`` opcode and stores all - newly created accounts in a list. - - .. code-block:: python - - # example.py - import boa - - class CreateTracer: - def __init__(self, super_fn): - """Track addresses of contracts created via the CREATE opcode. - - Parameters: - super_fn: The original opcode implementation. - """ - self.super_fn = super_fn - self.trace = [] - - def __call__(self, computation): - # first, dispatch to the original opcode implementation provided by py-evm - self.super_fn(computation) - # then, store the output of the CREATE opcode in our `trace` list for later - self.trace.append("0x" + computation._stack.values[-1][-1].hex()) - - if __name__ == "__main__": - create_tracer = CreateTracer(boa.env.vm.state.computation_class.opcodes[0xf0]) - boa.patch_opcode(0xf0, create_tracer) - - source = """ - @external - def main(): - for _ in range(10): - addr: address = create_minimal_proxy_to(self) - """ - contract = boa.loads(source) - contract.main() # execute the contract function - print(create_tracer.trace) - - Running the code would produce the following results: - - .. code-block:: bash - - $ python example.py - [ - "0xd130b7e7f212ecadcfcca3cecc89f85ce6465896", - "0x37fdb059bf647b88dbe172619f00b8e8b1cf9338", - "0x40bcd509b3c1f42d535d1a8f57982729d4b52adb", - "0xaa35545ac7a733600d658c3f516ce2bb2be99866", - "0x29e303d13a16ea18c6b0e081eb566b55a74b42d6", - "0x3f69d814da1ebde421fe7dc99e24902b15af960b", - "0x719c0dc21639008a2855fdd13d0d6d89be53f991", - "0xf6086a85f5433f6fbdcdcf4f2ace7915086a5130", - "0x097dec6ea6b9eb5fc04db59c0d343f0e3b4097a0", - "0x905794c5566184e642ef14fb0e72cf68ff8c79bf" - ] - -Low-Level Functionality ------------------------ - -.. module:: boa.environment - -.. class:: Env - - A wrapper class around py-evm which provides a "contract-centric" API. - - .. attribute:: eoa - :type: str - - The account to use as ``msg.sender`` for top-level calls and ``tx.origin`` in the context of state mutating function calls. - - .. attribute:: chain - :type: eth.abc.ChainAPI - - The global py-evm chain instance. - - .. method:: enable_fast_mode(flag: bool = True) -> None: - - Enable or disable fast mode. This can be useful for speeding up tests. - - :param flag: Whether to enable or disable fast mode. - - .. method:: alias(address: str, name: str) -> None - - Associates an alias with an address. This is useful to make the address more human-readable in tracebacks. - - :param address: The address to alias. - :param name: The alias to use for the address. - - .. method:: generate_address(alias: str | None = None) -> str - - Generate an address and optionally alias it. - - :param alias: The alias to use for the generated address. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> boa.env.generate_address() - '0xd13f0Bd22AFF8176761AEFBfC052a7490bDe268E' - - .. method:: set_random_seed(seed: Any = None) -> None - - Set the random seed used by this ``Env`` to generate addresses. Useful in case you want to introduce some more randomization to how ``Env`` generates addresses. - - :param seed: The seed to pass to this ``Env``'s instance of ``random.Random``. Can be any value that ``random.Random()`` accepts. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> boa.env.set_random_seed(100) - >>> boa.env.generate_address() - '0x93944a25b3ADa3759918767471C5A3F3601652c5 - - .. method:: set_balance(address: str, value: int) - - Set the ether balance of an account. - - .. method:: get_balance(address: str) -> int - - Get the ether balance of an account. - - .. method:: fork(provider: str, **kwargs: Any) - - Fork the state of an external node allowing local simulation of state mutations. - - :param provider: The URL of the node provider to fork the state of. - :param block_identifier: The block identifier to fork the state at. The value may be an integer, bytes, a hexadecimal string or a pre-defined block identifier (``"earliest"`` , ``"latest"``, ``"pending"``, ``"safe"`` or ``"finalized"``). Defaults to ``"safe"``. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> boa.env.vm.state.block_number - 1 - >>> boa.env.fork("https://rpc.ankr.com/eth") - >>> boa.env.vm.state.block_number - 16038471 - - .. method:: anchor() - - A context manager which snapshots the state and the vm, and reverts to the snapshot on exit. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... value: public(uint256) - ... """ - >>> contract = boa.loads(src) - >>> contract.value() - 0 - >>> with boa.env.anchor(): - ... contract.eval("self.value += 1") - ... contract.value() - ... - 1 - >>> contract.value() - 0 - - .. method:: prank(address: str) - - A context manager which temporarily sets :py:attr:`eoa` and resets it on exit. - - .. rubric:: Example - - .. code-block:: - - >>> import boa - >>> boa.env.eoa - '0x0000000000000000000000000000000000000065' - >>> with boa.env.prank("0x00000000000000000000000000000000000000ff"): - ... boa.env.eoa - ... - '0x00000000000000000000000000000000000000ff' - >>> boa.env.eoa - - .. method:: deploy_code(at: str = "0x0000000000000000000000000000000000000000", sender: str | None = None, gas: int | None = None, value: int = 0, bytecode: bytes = b"", data: bytes = b"", pc: int = 0) -> bytes - - Deploy bytecode at a specific account. - - :param at: The account the deployment bytecode will run at. - :param sender: The account to set as ``tx.origin`` for the execution context and ``msg.sender`` for the top-level call. - :param gas: The gas limit provided for the execution (a.k.a. ``msg.gas``). - :param value: The ether value to attach to the execution (a.k.a ``msg.value``). - :param bytecode: The deployment bytecode. - :param data: The data to attach to the execution (a.k.a. ``msg.data``). - :param pc: The program counter to start the execution at. - :returns: The return value from the top-level call (typically the runtime bytecode of a contract). - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> code = bytes.fromhex("333452602034f3") # simply returns the caller - >>> boa.env.deploy_code(bytecode=code, sender="0x0000000022D53366457F9d5E68Ec105046FC4383").hex() - '0000000000000000000000000000000022d53366457f9d5e68ec105046fc4383' - >>> boa.env.vm.state.get_code(b"\x00" * 20).hex() - '0000000000000000000000000000000022d53366457f9d5e68ec105046fc4383' - - .. method:: execute_code(at: str = "0x0000000000000000000000000000000000000000", sender: str | None = None, gas: int | None = None, value: int = 0, bytecode: bytes = b"", data: bytes = b"", pc: int = 0) -> bytes - - Execute bytecode at a specific account. - - :param at: The account to target. - :param sender: The account to set as ``tx.origin`` for the execution context and ``msg.sender`` for the top-level call. - :param gas: The gas limit provided for the execution (a.k.a. ``msg.gas``). - :param value: The ether value to attach to the execution (a.k.a ``msg.value``). - :param bytecode: The runtime bytecode. - :param data: The data to attach to the execution (a.k.a. ``msg.data``). - :param pc: The program counter to start the execution at. - :returns: The return value from the top-level call. - - .. method:: raw_call(to_address: str, sender: str | None = None, gas: int | None = None, value: int = 0, data: bytes = b"") -> bytes - - Simple wrapper around `execute_code`, to execute as if the contract is being called from an EOA. - - :param to_address: The contract to target. - :param sender: The account to set as ``tx.origin`` for the execution context and ``msg.sender`` for the top-level call. - :param gas: The gas limit provided for the execution (a.k.a. ``msg.gas``). - :param value: The ether value to attach to the execution (a.k.a ``msg.value``). - :param data: The data to attach to the execution (a.k.a. ``msg.data``). - :returns: The return value from the top-level call. - - .. method:: time_travel(seconds: int = None, blocks: int = None, block_delta: int = 12) - - Fast forward, increase the chain timestamp and block number. - - :param seconds: Change current timestamp by `seconds` seconds. - :param blocks: Change block number by `blocks` blocks. - :param block_delta: The time between two blocks. Set to 12 as default. - -.. module:: boa.vyper.contract - -.. class:: VyperDeployer - - Vyper contract factory. - - .. method:: at(address: str) -> VyperContract - - Return a :py:class:`VyperContract` instance for a contract deployed at the provided address. - - :param address: The address of the contract. - :returns: A contract instance. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @external - ... def main(): - ... pass - ... """ - >>> ContractFactory = boa.loads_partial(src, "Foo") - >>> ContractFactory.at("0xD130B7E7F212ECADCfcCa3cecC89f85ce6465896") - - - .. method:: deploy(*args: Any, **kwargs: Any) -> VyperContract - - Deploy a new contract. - - :param args: The contract constructor arguments. - :param kwargs: Keyword arguments to pass to the :py:class:`VyperContract` ``__init__`` method. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @external - ... def main(): - ... pass - ... """ - >>> ContractFactory = boa.loads_partial(src, "Foo") - >>> ContractFactory.deploy() - - - .. method:: deploy_as_blueprint(*args: Any, **kwargs: Any) -> VyperBlueprint - - Deploy a new :eip:`5202` blueprint instance. - - :param args: Positional arguments to pass to the :py:class:`VyperBlueprint` ``__init__`` method. - :param kwargs: Keyword arguments to pass to the :py:class:`VyperBlueprint` ``__init__`` method. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @external - ... def main(): - ... pass - ... """ - >>> ContractFactory = boa.loads_partial(src, "Foo") - >>> ContractFactory.deploy_as_blueprint() - - -.. class:: VyperContract - - A contract instance. - - Internal and external contract functions are available as methods on :py:class:`VyperContract` instances. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @external - ... def main(): - ... pass - ... - ... @internal - ... def foo() -> uint256: - ... return 123 - ... """ - >>> contract = boa.loads(src) - >>> type(contract.main) - - >>> type(contract.foo) - - >>> contract.internal.foo() - 123 - - .. method:: eval(statement: str, value: int = 0, gas: int | None = None, sender: str | None = None) -> Any - - Evaluate a Vyper statement in the context of the contract. - - :param statement: A vyper statment. - :param value: The ether value to attach to the statement evaluation (a.k.a ``msg.value``). - :param gas: The gas limit provided for statement evaluation (a.k.a. ``msg.gas``). - :param sender: The account which will be the ``tx.origin``, and ``msg.sender`` in the context of the evaluation. - :returns: The result of the statement evaluation. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = "value: public(uint256)" - >>> contract = boa.loads(src) - >>> contract.value() - 0 - >>> contract.eval("self.value += 1") - >>> contract.value() - 1 - - .. property:: deployer - :type: VyperDeployer - -.. class:: VyperBlueprint - - Stub class for :eip:`5202` blueprints. - - .. property:: address - :type: str - -.. class:: VyperFunction - - .. .. method:: args_abi_type(nkwargs: int) - - .. :param nkwargs: The number of keyword arguments to include when calculating the signature. - .. :returns: A tuple containing the function's method id and the ABI schema of the function's arguments. - .. :rtype: tuple[bytes, str] - - .. .. rubric:: Example - - .. .. code-block:: python - - .. >>> import boa - .. >>> src = """ - .. ... @external - .. ... def main(a: uint256, b: uint256 = 0) -> uint256: - .. ... return a + b - .. ... """ - .. >>> contract = boa.loads(src) - .. >>> contract.main.args_abi_type(0) - .. (b'\xab:\xe2U', '(uint256)') - .. >>> contract.main.args_abi_type(1) - .. (b'\xccW,\xf9', '(uint256,uint256)') - - .. method:: prepare_calldata(*args: Any, **kwargs: Any) -> bytes: - Prepare the calldata that a function call would use. - This is useful for saving calldata for use in a transaction later. - - :param args: The positional arguments of the contract function. - :param kwargs: Keyword arguments of the contract function. - - :returns: The calldata that a call with a particular set of arguments would use, in bytes. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @external - ... def main(a: uint256) -> uint256: - ... return 1 + a - ... """ - >>> c = boa.loads(src) - >>> contract.main.prepare_calldata(68) - b'\xab:\xe2U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0D' - - - .. method:: __call__(*args: Any, value: int = 0, gas: int | None = None, sender: str | None = None, **kwargs: Any) -> Any - - Execute the function. - - :param args: The positional arguments of the contract function. - :param value: The ether value to attach to the execution of the function (a.k.a ``msg.value``). - :param gas: The gas limit provided for function execution (a.k.a. ``msg.gas``). - :param sender: The account which will be the ``tx.origin`` of the execution, and ``msg.sender`` of the top-level call. - :param kwargs: Keyword arguments of the contract function. - :returns: The result of the function. - - .. rubric:: Example - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @external - ... def main(a: uint256) -> uint256: - ... return 1 + a - ... """ - >>> contract = boa.loads(src) - >>> contract.main(68) - 69 - - .. attribute:: contract - :type: VyperContract - - The :py:class:`VyperContract` instance this :py:class:`VyperFunction` instance is attached to. - - .. attribute:: env - :type: boa.environment.Env - - The :py:class:`boa.environment.Env` instance of the :py:attr:`contract` attribute. - - .. attribute:: fn_ast - :type: vyper.ast.nodes.FunctionDef - - The Vyper AST of this function. - - .. property:: assembly - :type: list[str] - - The function's runtime bytecode as a list of mnemonics. - - .. property:: bytecode - :type: bytes - - The function's runtime bytecode in bytes form. - - .. property:: fn_signature - :type: vyper.ast.signatures.function_signature.FunctionSignature - - The internal Vyper representation of the function's signature. - - .. property:: opcodes - :type: str - - The function's runtime bytecode as a string of mnemonics. - - .. property:: ir - :type: vyper.codegen.ir_node.IRnode - - The internal representation of the function (a.k.a. VenomIR). - -.. class:: VyperInternalFunction - - Internal contract functions are exposed by wrapping it with a dummy external contract function, appending the wrapper's ast at the top of the contract and then generating bytecode to run internal methods (as external methods). Therefore, they share the same API as :py:class:`boa.vyper.contract.VyperFunction`. Internal functions can be accessed using the `internal` namespace of a :py:class:`VyperContract`. - - .. code-block:: python - - >>> import boa - >>> src = """ - ... @internal - ... def main(a: uint256) -> uint256: - ... return 1 + a - ... """ - >>> contract = boa.loads(src) - >>> contract.internal.main(68) - 69 - - -Exceptions ----------- - -.. currentmodule:: boa - -.. exception:: BoaError - - Raised when an error occurs during contract execution. diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 80c393b9..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,47 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = "titanoboa" -copyright = "2022, Charles Cooper" -author = "Charles Cooper" -release = "0.1.6" - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = [ - "sphinx.ext.autosectionlabel", - "sphinx.ext.extlinks", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", -] - -templates_path = ["_templates"] -exclude_patterns = [] - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = "sphinx_rtd_theme" -html_static_path = ["_static"] - -# -- Options for autosectionlabel extension ---------------------------------- -# https://www.sphinx-doc.org/en/master/usage/extensions/autosectionlabel.html - -autosectionlabel_prefix_document = True -autosectionlabel_maxdepth = 3 - -# -- Options for intersphinx extension --------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html - -intersphinx_mapping = {"pyevm": ("https://py-evm.readthedocs.io/en/latest", None)} - -# -- Options for extlinks extension ------------------------------------------ -# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html - -extlinks = {"eip": ("https://eips.ethereum.org/EIPS/eip-%s", "EIP-%s")} diff --git a/docs/source/examples.rst b/docs/source/examples.rst deleted file mode 100644 index 99032089..00000000 --- a/docs/source/examples.rst +++ /dev/null @@ -1,2 +0,0 @@ -Titanoboa By Examples -===================== diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 13d32d34..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. titanoboa documentation master file, created by - sphinx-quickstart on Wed Nov 23 02:13:24 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Titanoboa -========= - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - overview - examples - api - testing diff --git a/docs/source/overview.rst b/docs/source/overview.rst deleted file mode 100644 index e5e5cb02..00000000 --- a/docs/source/overview.rst +++ /dev/null @@ -1,40 +0,0 @@ -Overview -======== - -Titanoboa (also called ``boa``) is a `Vyper `_ interpreter designed to provide a modern, advanced and integrated development experience with: - -* pretty tracebacks -* forking -* debugging features -* opcode patching -* *and more ...* - -``titanoboa`` is not just a framework, but a library that can be used in any Python environment. It is designed to be used in jupyter notebooks, Python scripts, or tests (any Python testing framework is compatible) to provide a seamless experience and as little context-switching overhead as possible between Python and Vyper. - -Installation ------------- - -``titanoboa`` is available to install from `PyPI `_. - -.. code-block:: bash - - pip install titanoboa - -Alternatively, the latest in-development version of ``titanoboa`` can be installed from `GitHub `_. - -.. code-block:: bash - - pip install git+https://github.com/vyperlang/titanoboa#egg=titanoboa - -If you are using `Poetry `_ as a dependency manager: - -.. code-block:: bash - - poetry add titanoboa - -If you want to use a specific version you can customize the dependency in your `pyproject.toml` file like this: - -.. code-block:: toml - - [tool.poetry.dependencies] - titanoboa = { git = "https://github.com/vyperlang/titanoboa.git", rev = } diff --git a/docs/source/testing.rst b/docs/source/testing.rst deleted file mode 100644 index 7a595a5f..00000000 --- a/docs/source/testing.rst +++ /dev/null @@ -1,226 +0,0 @@ -Testing with Titanoboa -====================== - -Titanoboa integrates natively with `pytest `_ and `hypothesis `_. Nothing special is needed to enable these, as the plugins for these packages will be loaded automatically. By default, isolation is enabled for tests - that is, any changes to the EVM state inside the test case will automatically be rolled back after the test case completes. - -Since ``titanoboa`` is framework-agnostic any other testing framework should work as well. - - -Gas Profiling ------------------------ - -Titanoboa has native gas profiling tools that store and generate statistics upon calling a contract. When enabled, gas costs are stored per call in ``global_profile().call_profiles`` and ``global_profile().line_profiles`` dictionaries. -To enable gas profiling, - -1. decorate tests with ``@pytest.mark.gas_profile``, or -2. run pytest with ``--gas-profile``, e.g. ``pytest tests/unitary --gas-profile`` - -If ``--gas-profile`` is selected, to ignore gas profiling for specific tests, decorate the test with ``@pytest.mark.ignore_gas_profiling``. - -.. code-block:: python - - @pytest.mark.profile - def test_profile(): - - source_code = """ - @external - @view - def foo(a: uint256 = 0): - x: uint256 = a - """ - contract = boa.loads(source_code, name="FooContract") - contract.foo() - -.. code-block:: console - - ┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━┳━━━━━┓ - ┃ Contract ┃ Address ┃ Computation ┃ Count ┃ Mean ┃ Median ┃ Stdev ┃ Min ┃ Max ┃ - ┑━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━╇━━━━━┩ - β”‚ FooContract β”‚ 0x0000000000000000000000000000000000000066 β”‚ foo β”‚ 1 β”‚ 88 β”‚ 88 β”‚ 0 β”‚ 88 β”‚ 88 β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”˜ - - - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┓ - ┃ Contract ┃ Computation ┃ Count ┃ Mean ┃ Median ┃ Stdev ┃ Min ┃ Max ┃ - ┑━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━┩ - β”‚ Path: β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - β”‚ Name: FooContract β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - β”‚ Address: 0x0000000000000000000000000000000000000066 β”‚ β”‚ Count β”‚ Mean β”‚ Median β”‚ Stdev β”‚ Min β”‚ Max β”‚ - β”‚ ---------------------------------------------------- β”‚ -------------------------------------------------------------------------- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ ----- β”‚ - β”‚ Function: foo β”‚ 4: def foo(a: uint256 = 0): β”‚ 1 β”‚ 73 β”‚ 73 β”‚ 0 β”‚ 73 β”‚ 73 β”‚ - β”‚ β”‚ 5: x: uint256 = a β”‚ 1 β”‚ 15 β”‚ 15 β”‚ 0 β”‚ 15 β”‚ 15 β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜ - -.. note:: - Note that if a specific fixture is called in two separate tests, pytest will re-instantiate it. Meaning, if a Contract - is deployed in a fixture, calling the fixture on tests in two separate files can lead to two deployments of that Contract, - and hence two separate addresses in the profile table. - -.. warning:: - Profiling does not work with pytest-xdist plugin at the moment. - -Coverage --------------- - -.. warning:: - Coverage is not yet supported when using fast mode. - -Titanoboa offers coverage through the `coverage.py `_ package. - -To use, add the following to ``.coveragerc``: - -.. code-block:: - - [run] - plugins = boa.coverage - -(for more information see https://coverage.readthedocs.io/en/latest/config.html) - -Then, run with ``coverage run ...`` - -To run with pytest, it can be invoked in either of two ways, - -.. code-block:: - - coverage run -m pytest ... - -or, - -.. code-block:: - - pytest --cov= --cov-branch ... - -`pytest-cov `_ is a wrapper around ``coverage.py`` for using with pytest; using it is recommended because it smooths out some quirks of using ``coverage.py`` with pytest. - -Finally, ``coverage.py`` saves coverage data to a file named ``.coverage`` in the directory it is run in. To view the formatted coverage data, you typically want to use ``coverage report`` or ``coverage html``. See more options at https://coverage.readthedocs.io/en/latest/cmd.html. - -Coverage is experimental and there may be odd corner cases! If so, please report them on github or in the ``#titanoboa-interpreter`` channel of the `Vyper discord `_. - -Fuzzing Strategies ------------------ - -Titanoboa offers custom `hypothesis `_ strategies for testing. These can be used to generate EVM-compliant random inputs for tests. - -Native Import Syntax --------------------- - -Titanoboa supports the native Python import syntax for Vyper contracts. This means that you can import Vyper contracts in any Python script as if you were importing a Python module. - -For example, if you have a contract ``contracts/Foo.vy``: - -.. code-block:: vyper - - x: public(uint256) - - def __init__(x_initial: uint256): - self.x = x_initial - -You can import it in a Python script ``tests/bar.py`` like this - - -.. code-block:: python - - from contracts import Foo - - my_contract = Foo(42) # This will create a new instance of the contract - - my_contract.x() # Do anything with the contract as you normally would - -Internally this will use the ``importlib`` module to load the file and create a ``ContractFactory``. - - -.. note:: - - For this to work ``boa`` must be imported first. - - Due to limitations in the Python import system, only imports of the form ``import Foo`` or ``from import Foo`` will work and it is not possible to use ``import ``. - - -Fast Mode ---------- - -Titanoboa has a fast mode that can be enabled by using ``boa.env.enable_fast_mode()``. - -This mode performs a number of optimizations by patching some py-evm objects to speed up the execution of unit tests. - -.. warning:: - Fast mode is experimental and may break other features of boa (like coverage). - -ipython Vyper Cells -------------------- - -Titanoboa supports ipython Vyper cells. This means that you can write Vyper code in a ipython/Jupyter Notebook environment and execute it as if it was a Python cell (the contract will be compiled instead, and a ``ContractFactory`` will be returned). - -You can use Jupyter to execute titanoboa code in network mode from your browser using any wallet, using your wallet to sign transactions and call the RPC. -For a full example, please see `this example Jupyter notebook <../../examples/jupyter_browser_signer.ipynb>`_. - -.. code-block:: python - - In [1]: import boa; boa.env.fork(url="") - - In [2]: %load_ext boa.ipython - - In [3]: %%vyper Test - ...: interface HasName: - ...: def name() -> String[32]: view - ...: - ...: @external - ...: def get_name_of(addr: HasName) -> String[32]: - ...: return addr.name() - Out[3]: - - In [4]: c = Test.deploy() - - In [5]: c.get_name_of("0xD533a949740bb3306d119CC777fa900bA034cd52") - Out[5]: 'Curve DAO Token' - -Accessing non-public/external members -------------------------------------- - -Titanoboa allows access to non-public/external members of a contract. This is useful for testing internal functions or variables without having to expose them to the outside world. - -Given a vyper module ``foo.vy`` in the same folder as your python code: - -.. code-block:: vyper - - x: uint256 - y: immutable(uint256) - - def __init__(y_initial: uint256): - self.x = 42 - self.y = y_initial - - @internal - @pure - def _bar() -> uint256: - return 111 - -``internal`` functions can be accessed by calling the function from the ``internal`` attribute of the contract. - -.. code-block:: python - - import foo - - my_contract = foo(1234) - - my_contract.internal._bar() # Call the internal function _bar (returns 111) - -Private storage variables can be accessed by calling the variable from the ``_storage`` attribute of the contract: - -.. code-block:: python - - import foo - - my_contract = foo(1234) - - my_contract._storage.x.get() # Access the private storage variable x (returns 42) - -Similarly private immutable variables can be accessed by calling the variable from the ``_immutable`` attribute of the contract: - -.. code-block:: python - - import foo - - my_contract = foo(1234) - - my_contract._immutable.y # Access the private immutable variable y diff --git a/docs/template.md b/docs/template.md new file mode 100644 index 00000000..71d42f4f --- /dev/null +++ b/docs/template.md @@ -0,0 +1,87 @@ +# Templates + +Templates used throughout the documentation. + +--- + +## Custom Admonitions + +!!!vyper + lorem ipsum dolor sit amet + +!!!titanoboa + lorem ipsum dolor sit amet + +!!!moccasin + lorem ipsum dolor sit amet + +!!!python + lorem ipsum dolor sit amet + +--- + + +## Function Documentation Template + +Basic template for documenting functions. The link to the source code needs to be added manually. If no link is given, the GitHub emoji with the embedded link will not be rendered. +### `load` +!!! function "`load`" + + + **Signature** + + ```python + load( + fp: str, + *args: Any, + **kwargs: Any + ) -> VyperContract | VyperBlueprint + ``` + + --- + + **Description** + + Compile source from disk and return a deployed instance of the contract. + + --- + + **Parameters** + + - `fp`: The contract source code file path. + - `args`: Contract constructor arguments. + - `kwargs`: Keyword arguments to pass to the [`boa.loads`](api/load_contracts.md#loads) function. + + --- + + **Returns** + + A [`VyperContract`](api/vyper_contract/overview.md) or [`VyperBlueprint`](api/vyper_blueprint/overview.md) instance. + + --- + + **Examples** + + === "Deployment" + + ```python + >>> import boa + >>> boa.load("Foo.vy") + + ``` + + ```python + >>> import boa + >>> from vyper.compiler.settings import OptimizationLevel, Settings + >>> boa.load("Foo.vy", compiler_args={"settings": Settings(optimize=OptimizationLevel.CODESIZE)}) + + ``` + + === "Foo.vy" + + ```vyper + # Foo.vy + @external + def addition(a: uint256, b: uint256) -> uint256: + return a + b + ``` \ No newline at end of file diff --git a/docs/tutorials/debug.md b/docs/tutorials/debug.md new file mode 100644 index 00000000..24fbd91a --- /dev/null +++ b/docs/tutorials/debug.md @@ -0,0 +1,3 @@ +# Debug and Print like a snek + + diff --git a/docs/tutorials/install.md b/docs/tutorials/install.md new file mode 100644 index 00000000..963f1e63 --- /dev/null +++ b/docs/tutorials/install.md @@ -0,0 +1,58 @@ +

Installing Titanoboa

+ +Titanoboa requires Python 3.9 or later to function properly. + +## Install Using Moccasin + +!!!moccasin + [Moccasin](https://github.com/cyfrin/moccasin) is a CLI tool that wraps Boa, providing a smoother and more feature-rich development experience. If you are accustomed to frameworks like Foundry, you’ll likely want to install Moccasin. Refer to the [Moccasin documentation](https://cyfrin.github.io/moccasin/) for more details. + +If you have installed Moccasin and used `mox init` to set up your project, Titanoboa is already included as a dependency. + +--- + +## Install Using pip, poetry, or uv + +Titanoboa is available on PyPI, so you can install it using pip, poetry, or uv. The latest release can be installed with: + +=== "pip" + + ```console + pip install titanoboa + ``` + +=== "poetry" + + ```console + poetry add titanoboa + ``` + +=== "uv" + + ```console + uv add titanoboa + ``` + +--- + +## Install Latest Development Version + +To install the latest development version, use: + +=== "pip" + + ```console + pip install git+https://github.com/vyperlang/titanoboa.git@ + ``` + +=== "poetry" + + ```console + poetry add git+https://github.com/vyperlang/titanoboa.git@ + ``` + +=== "uv" + + ```console + uv add https://github.com/vyperlang/titanoboa.git --rev + ``` \ No newline at end of file diff --git a/docs/tutorials/pytest.md b/docs/tutorials/pytest.md new file mode 100644 index 00000000..27e6f12f --- /dev/null +++ b/docs/tutorials/pytest.md @@ -0,0 +1,89 @@ +# Writing unit tests pytest + +Titanoboa integrates natively with [pytest](https://docs.pytest.org/) and [hypothesis](https://hypothesis.readthedocs.io/en/latest/quickstart.html). Nothing special is needed to enable these, as the plugins for these packages will be loaded automatically. By default, isolation is enabled for tests - that is, any changes to the EVM state inside the test case will automatically be rolled back after the test case completes. + +Since `titanoboa` is framework-agnostic any other testing framework should work as well. + +Let's cover the basics of testing with boa and pytest. + +Let's start with a simple example contract `Example.vy`: + +!!!vyper + + ```vyper + foo: public(uint256) + + @external + def set_foo(foo: uint256): + self.foo = foo + ``` + +We want to test that the `set_foo` function works correctly. That is, given an input, it should set the `foo` variable to the input value. + +In the same folder as `Example.vy`, we create a file `test_example.py`: + +We first create a [pytest fixture](https://docs.pytest.org/en/8.3.x/how-to/fixtures.html) that will deploy the contract: +!!!python + + ```python + import pytest + import boa + + @pytest.fixture + def example(): + return boa.load("Example.vy") + ``` + +We can then write a test for the `set_foo` function, we can use `example` fixture to get an instance of the contract: + +!!!python + + ```python + def test_set_foo(example): + example.set_foo(50) + assert example.foo() == 50 + ``` + +We can run the test by calling `pytest`: + +!!! example "Bash" + + ```bash + > pytest test_example.py + ============================= test session starts ============================== + ... + collected 1 item + + test_example.py::test_set_foo PASSED + + ============================== 1 passed in 0.01s =============================== + ``` + + + +## Titanoboa Plugin + +Titanoboa offers a pytest plugin that automatically resets the environment after each test. +This is useful to isolate each test and avoid side effects between them. +By using fixtures, pytest is able to correctly deploy the necessary contracts to run each specific test. + +However, this can give errors when testing in network/fork mode. +Most importantly, some RPCs will not support the `evm_snapshot` and `evm_revert` methods, which are used to reset the state after each test. + +To disable the plugin, you may add the `ignore_isolation` marker. + +!!! python + ```python + import pytest + + # this will ignore the isolation for all tests in this file + pytestmark = pytest.mark.ignore_isolation + + # this will ignore the isolation for this specific test + @pytest.mark.ignore_isolation + def test_set_foo(example): + example.set_foo(50) + assert example.foo() == 50 + ``` + +See more details in the [environment explanation](../explain/singleton_env.md#anchor--auto-revert). diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..1bd23ba8 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,123 @@ +site_name: Titanoboa Documentation +repo_name: titanoboa +repo_url: https://github.com/vyperlang/titanoboa +strict: true + +theme: + name: material + logo: assets/images/titanoboa-symbol-col.svg + icon: + repo: simple/github + palette: + scheme: default + primary: "#9F4CF2" + features: + - content.code.copy + - content.code.annotate + - content.tabs.link + - navigation.sections + + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.highlight: + anchor_linenums: true + + +nav: + - Overview: index.md + - Getting Started: + - Installing titanoboa: tutorials/install.md + - Writing unit tests with pytest: tutorials/pytest.md + - Debugging contracts: tutorials/debug.md + - Guides: + - Scripting: + - Interactive Vyper Notebooks: guides/scripting/ipython_vyper_cells.md + - Native Import Syntax: guides/scripting/native_import_syntax.md + - Testing: + - Accessing Private Members: guides/testing/private_members.md + - Computing Test Coverage: guides/testing/coverage.md + - Stateful Testing with Hypothesis: guides/testing/fuzzing_strategies.md + - Gas profiling: guides/testing/gas_profiling.md + - Forge Analogues: guides/forge.md + - API Reference: + - boa: + - Loading Contracts: api/load_contracts.md + - Testing and Forking: api/testing.md + - Verify Deployed Contracts: + - get_verifier: api/verify/get_verifier.md + - set_verifier: api/verify/set_verifier.md + - verify: api/verify/verify.md + - Overview: api/verify/overview.md + - EVM and Precompiles: + - register_precompile: api/pyevm/register_precompile.md + - deregister_precomile: api/pyevm/deregister_precompile.md + - patch_opcode: api/pyevm/patch_opcode.md + - Exceptions: + - BoaError: api/exceptions/boa_error.md + - Cache: api/cache.md + - Environment: + - Pick your environment: api/env/singleton.md + - Env: api/env/env.md + - NetworkEnv: api/env/network_env.md + - BrowserEnv: api/env/browser_env.md + - VyperContract: + - Overview: api/vyper_contract/overview.md + - eval: api/vyper_contract/eval.md + - deployer: api/vyper_contract/deployer.md + - at: api/vyper_contract/at.md + - marshal_to_python: api/vyper_contract/marshal_to_python.md + - stack_trace: api/vyper_contract/stack_trace.md + - trace_source: api/vyper_contract/trace_source.md + - get_logs: api/vyper_contract/get_logs.md + - decode_log: api/vyper_contract/decode_log.md + - inject_function: api/vyper_contract/inject_function.md + - VyperDeployer: + - Overview: api/vyper_deployer/overview.md + - deploy: api/vyper_deployer/deploy.md + - deploy_as_blueprint: api/vyper_deployer/deploy_as_blueprint.md + - stomp: api/vyper_deployer/stomp.md + - at: api/vyper_deployer/at.md + - standard_json: api/vyper_deployer/standard_json.md + - _constants: api/vyper_deployer/_constants.md + - __call__: api/vyper_deployer/__call__.md + - VyperBlueprint: api/vyper_blueprint/overview.md + - _BaseEVMContract: + - Overview: api/common_classes/_BaseEVMContract.md + - stack_trace: api/common_classes/stack_trace.md + - call_trace: api/common_classes/call_trace.md + - handle_error: api/common_classes/handle_error.md + - address: api/common_classes/address.md + - _BaseVyperContract: + - Overview: api/common_classes/_BaseVyperContract.md + - deployer: api/common_classes/deployer.md + - abi: api/common_classes/abi.md + - _constants: api/common_classes/_constants.md + - VyperInternalFunction: api/vyper_internal_function/overview.md + - ABIContract: api/abi_contract/overview.md + - VVMDeployer: + - Overview: api/vvm_deployer/overview.md + - __init__: api/vvm_deployer/__init__.md + - from_compiler_output: api/vvm_deployer/from_compiler_output.md + - factory: api/vvm_deployer/factory.md + - constructor: api/vvm_deployer/constructor.md + - deploy: api/vvm_deployer/deploy.md + - __call__: api/vvm_deployer/__call__.md + - at: api/vvm_deployer/at.md + - Explanations: + - Transaction context: explain/tx_context.md + - Revert reasons: explain/revert_reasons.md + - Singleton Env: explain/singleton_env.md + - Legacy Vyper Contracts: explain/vvm_contracts.md + - Caching: explain/caching.md + +extra_css: + - assets/stylesheets/extra.css + - https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;500;600;700;800;900&display=swap + +extra_javascript: + - assets/javascript/extra.js \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c0ca2774..5893ecf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ dependencies = [ # required for networked accounts "eth-account>=0.13.0", + "mkdocs-material==9.5.41 ", ] [project.optional-dependencies]