Skip to content

Commit

Permalink
Merge pull request #13 from Aviksaikat/feat/read/storage
Browse files Browse the repository at this point in the history
feat: added option to read contract storage from slot
  • Loading branch information
Aviksaikat authored Jul 21, 2024
2 parents 2aee426 + 7cf8714 commit a75a77c
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 32 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

All notable changes to this project will be documented in this file.

## [0.0.5] - 2024-07-14
## [0.0.6] - 2024-07-21

### 🚀 Features

- Functions with multiple parameters can be called using the call method
- Added option to read contract storage from slot using the read method

### 🐛 Bug Fixes

Expand All @@ -15,6 +16,7 @@ All notable changes to this project will be documented in this file.
### ⚙️ Miscellaneous Tasks

- Pyblish workflow updated
- Demo.tape & gitignore updated

## [0.0.4] - 2024-07-13

Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ To call a view function with the signature `call_this_view_function(uint256)(str
```bash
# function which takes a single input parameter
ape_utils call --function-sig "call_this_view_function(uint256)(string)" --address "0x80E097a70cacA11EB71B6401FB12D48A1A61Ef54" --args '[6147190]' --network :sepolia:infura
# or
ape utils call -s "call_this_view_function(uint256)(string)" -a "0x80E097a70cacA11EB71B6401FB12D48A1A61Ef54" -ag '[6147190]' --network :sepolia:infura
```
#### Calling a view function with multiple parameter
Expand Down Expand Up @@ -101,6 +103,20 @@ ape_utils encode --signature "call_this_view_function(uint256 arg1)" 1234
ape_utils decode --signature "call_this_view_function(uint256 arg1)" "0x1e4f420d00000000000000000000000000000000000000000000000000000000000004d2"
```
#### Don't want beautiful print statements ?
You can now pass the `--raw` flag to each option to print the output data only
```sh
ape_utils encode --signature 'isLastFloor(uint256 arg1)' 1234 --raw
```
#### Read storage slots of a contract
```sh
ape utils read --address "0xDbB18e367E4A2A36A9F2AF7af8b3c743938deCF2" --slot 1 --network :sepolia
```
![working](media/working.png)
## Development
Expand Down
6 changes: 6 additions & 0 deletions demo.tape
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ Enter 1
Sleep 3s
Type "ape_utils decode --signature 'call_this_view_function(uint256 arg1)' '0x1e4f420d00000000000000000000000000000000000000000000000000000000000004d2'"
Enter 1
Sleep 2s
Type "ape utils read --address '0xDbB18e367E4A2A36A9F2AF7af8b3c743938deCF2' --slot 1 --network :sepolia"
Enter 1
Sleep 8s
Type "ape_utils encode --signature 'call_this_view_function(uint256 arg1)' 1234 --raw"
Enter 1
Sleep 10s
Binary file modified media/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified media/help.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/ape_utils/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = "0.0.5"
version = "0.0.6"
113 changes: 84 additions & 29 deletions src/ape_utils/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
call_view_function,
decode_calldata,
encode_calldata,
read_storage,
)

install()
Expand Down Expand Up @@ -76,115 +77,169 @@ def cli() -> None:
@click.command(cls=ConnectedProviderCommand)
@click.option(
"--function-sig",
"-s",
required=True,
help="The function signature (e.g., function_name(input param type)(output param type)).",
)
@click.option("--address", required=True, help="The address of the smart contract.")
@click.option("--args", required=True, help="The arguments for the function call.", cls=PythonLiteralOption)
@click.option("--address", "-a", required=True, help="The address of the smart contract.")
@click.option("--args", "-ag", required=True, help="The arguments for the function call.", cls=PythonLiteralOption)
@click.option("--raw", "-r", is_flag=True, help="Print raw data without colorful output or additional text.")
@network_option(default="ethereum:local:node", required=True)
def call_view_function_from_cli(function_sig: str, address: str, args: str, provider: Node) -> None:
def call_view_function_from_cli(function_sig: str, address: str, args: str, provider: Node, raw: bool) -> None: # noqa: FBT001
"""
Calls a view function on the blockchain given a function signature and address.
Using ape's native network parsing.
"""
try:
# console.print(provider.web3.provider)
# console.print(dir(provider.web3))
parsed_args = list(args)
# console.print(f"{parsed_args=}")
output = call_view_function(function_sig, address, parsed_args, provider)
console.print(f"[blue bold]Output: [green]{output}")
if raw:
console.print(output)
else:
console.print(f"[blue bold]Output: [green]{output}")
except Exception as e:
console.print(f"Error: [red]{e!s}")
if raw:
console.print(e)
else:
console.print(f"Error: [red]{e!s}")
raise e


@click.command(cls=ConnectedProviderCommand)
@click.option("--address", "-a", required=True, help="The address of the smart contract.")
@click.option("--slot", required=True, type=int, help="The storage slot to read from the contract.")
@click.option("--raw", "-r", is_flag=True, help="Print raw data without colorful output or additional text.")
@network_option(default="ethereum:local:node", required=True)
def read_storage_from_cli(address: str, slot: int, provider: Node, raw: bool) -> None: # noqa: ARG001, FBT001
"""
Reads storage from a given address and storage slot on the blockchain.
"""
try:
data = read_storage(address, slot)
if raw:
console.print(data)
else:
console.print(f"[blue bold]Storage Data: [green]{data}")
except Exception as e:
if raw:
console.print(e)
else:
console.print(f"Error: [red]{e!s}")
raise e


@click.command(cls=rclick.RichCommand)
@click.option(
"--signature",
"-s",
help="The function signature (e.g., function_name(input param type)).",
required=True,
)
@click.argument("args", nargs=-1, type=str)
def abi_encode(signature: str, args: Any) -> None:
@click.option("--raw", "-r", is_flag=True, help="Print raw data without colorful output or additional text.")
def abi_encode(signature: str, args: Any, raw: bool) -> None: # noqa: FBT001
"""
Encodes calldata for a function given its signature and arguments excluding the selector.
"""
try:
calldata = abi_encode_calldata(signature, *args)
console.print(f"[blue bold]Encoded Calldata: [green]{calldata.hex()}")
if raw:
console.print(calldata.hex())
else:
console.print(f"[blue bold]Encoded Calldata: [green]{calldata.hex()}")
except Exception as e:
console.print(f"Error: [red]{e!s}")
# TODO: Raise if debug mode is enabled
# raise e
if raw:
console.print(e)
else:
console.print(f"Error: [red]{e!s}")


@click.command(cls=rclick.RichCommand)
@click.option(
"--signature",
"-s",
help="The function signature (e.g., function_name(input param type)).",
required=True,
)
@click.argument("calldata", type=str)
def abi_decode(signature: str, calldata: str) -> None:
@click.option("--raw", "-r", is_flag=True, help="Print raw data without colorful output or additional text.")
def abi_decode(signature: str, calldata: str, raw: bool) -> None: # noqa: FBT001
"""
Decodes calldata for a function given its signature and calldata string.
"""
try:
decoded_data = abi_decode_calldata(signature, calldata)
# * print the ouput in a single line
console.print("[blue bold]Decoded Data: ", end="")
pprint(decoded_data)
if raw:
console.print(decoded_data)
else:
console.print("[blue bold]Decoded Data: ", end="")
pprint(decoded_data)
except Exception as e:
console.print(f"Error: [red]{e!s}")
if raw:
console.print(e)
else:
console.print(f"Error: [red]{e!s}")


@click.command(cls=rclick.RichCommand)
@click.option(
"--signature",
"-s",
help="The function signature (e.g., function_name(input param type)).",
required=True,
)
@click.argument("args", nargs=-1, type=str)
def encode(signature: str, args: Any) -> None:
@click.option("--raw", "-r", is_flag=True, help="Print raw data without colorful output or additional text.")
def encode(signature: str, args: Any, raw: bool) -> None: # noqa: FBT001
"""
Encodes calldata for a function given its signature and arguments Including the selector.
"""
try:
calldata = encode_calldata(signature, *args)
console.print(f"[blue bold]Encoded Calldata: [green]{calldata.hex()}")
if raw:
console.print(calldata.hex())
else:
console.print(f"[blue bold]Encoded Calldata: [green]{calldata.hex()}")
except Exception as e:
console.print(f"Error: [red]{e!s}")
# TODO: Raise if debug mode is enabled
# raise e
if raw:
console.print(e)
else:
console.print(f"Error: [red]{e!s}")


@click.command(cls=rclick.RichCommand)
@click.option(
"--signature",
"-s",
help="The function signature (e.g., function_name(input param type)).",
required=True,
)
@click.argument("calldata", type=str)
def decode(signature: str, calldata: str) -> None:
@click.option("--raw", "-r", is_flag=True, help="Print raw data without colorful output or additional text.")
def decode(signature: str, calldata: str, raw: bool) -> None: # noqa: FBT001
"""
Decodes calldata for a function given its signature and calldata string.
"""
try:
decoded_data = decode_calldata(signature, calldata)
# * print the ouput in a single line
console.print("[blue bold]Decoded Data: ", end="")
pprint(decoded_data)
if raw:
console.print(decoded_data)
else:
console.print("[blue bold]Decoded Data: ", end="")
pprint(decoded_data)
except Exception as e:
console.print(f"Error: [red]{e!s}")
if raw:
console.print(e)
else:
console.print(f"Error: [red]{e!s}")


# * Add commands to the CLI group
cli.add_command(call_view_function_from_cli, name="call")
cli.add_command(abi_encode, name="abi_encode")
cli.add_command(abi_decode, name="abi_decode")
cli.add_command(encode, name="encode")
cli.add_command(decode, name="decode")
cli.add_command(read_storage_from_cli, name="read")

if __name__ == "__main__":
call_view_function_from_cli()
44 changes: 43 additions & 1 deletion src/ape_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import ape
import ethpm_types
from ape.types import HexBytes
from ape import networks
from ape.types import AddressType, HexBytes
from ape_node.provider import Node
from eth_utils import keccak
from ethpm_types import MethodABI
Expand Down Expand Up @@ -182,6 +183,47 @@ def decode_calldata(signature: str, encoded_data: str) -> Union[dict, Any]:
return ape.networks.ethereum.decode_calldata(method_abi, encoded_data_bytes)


def read_storage(address: Union[AddressType, str], slot: int) -> Any:
"""
Gets the raw value of a storage slot of a contract.
This function interacts with the blockchain to read the storage data of a
specified contract address at a given storage slot. It uses the provider
from the Ape framework to fetch the storage data.
Args:
address (Union[AddressType, str]): The address of the smart contract whose storage
data is to be read. This should be a valid Ethereum address.
slot (int): The storage slot number from which to read the data. This
should be a non-negative integer representing the position in the
contract's storage.
Returns:
Any: The data stored at the specified storage slot of the given address.
The return type can vary depending on the data stored at the slot.
Example:
>>> address = "0x1234567890abcdef1234567890abcdef12345678"
>>> slot = 5
>>> data = read_storage(address, slot)
>>> print(data)
Notes:
- This function requires a connection to an Ethereum network through
the Ape framework.
- Ensure that the address and slot provided are valid and exist in the
context of the smart contract's storage.
Raises:
ValueError: If the provided address or slot is invalid.
ConnectionError: If there is an issue connecting to the Ethereum network.
"""
data = networks.provider.get_storage(address, slot)

return data


if __name__ == "__main__":
function_sig: str = "call_this_view_function(uint256)(string)"
address: str = "0x80E097a70cacA11EB71B6401FB12D48A1A61Ef54"
Expand Down

0 comments on commit a75a77c

Please sign in to comment.