Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: added docs for multicall [APE-1311] #1617

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ef06fa2
added docs for multicall
Aviksaikat Aug 22, 2023
37a0894
Merge branch 'ApeWorX:main' into feat/Add-documentation-for-multicall
Aviksaikat Aug 23, 2023
e48fece
fix: issues causing dev messages from working on vyper 0.3.9 [APE-132…
antazoey Aug 25, 2023
9ed80f4
Merge branch 'ApeWorX:main' into feat/Add-documentation-for-multicall
Aviksaikat Aug 25, 2023
5c6e35d
made suggested chages
Aviksaikat Aug 25, 2023
ceeb7e1
feat: show plugin name in log messages [APE-1327] (#1628)
antazoey Aug 25, 2023
76181e7
Merge branch 'main' into feat/Add-documentation-for-multicall
Aviksaikat Aug 25, 2023
bd6cf06
Update networks.md (#1629)
0xJchen Aug 28, 2023
66703f0
fix: attempt fixing geth install [APE-1333] (#1634)
antazoey Aug 28, 2023
e456173
Merge branch 'ApeWorX:main' into feat/Add-documentation-for-multicall
Aviksaikat Aug 28, 2023
0e584b3
fix: pluggy (#1633)
antazoey Aug 28, 2023
92b1191
docs for multicall moved to contracts.md
Aviksaikat Aug 28, 2023
eeff46c
Merge branch 'ApeWorX:main' into feat/Add-documentation-for-multicall
Aviksaikat Aug 28, 2023
ddb63ef
fixed mdformat issue
Aviksaikat Aug 28, 2023
3b2eb07
Merge branch 'main' into feat/Add-documentation-for-multicall
antazoey Aug 29, 2023
d6f8e4e
made suggested chages
Aviksaikat Aug 29, 2023
239d3f2
docs: Update docs/userguides/contracts.md
antazoey Aug 29, 2023
371dd32
made suggested chages
Aviksaikat Aug 29, 2023
4e539e2
made suggested chages: method docs removed
Aviksaikat Aug 29, 2023
ad6d440
Merge branch 'main' into feat/Add-documentation-for-multicall
Aviksaikat Aug 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ jobs:

env:
GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GETH_VERSION: 1.12.0

steps:
- uses: actions/checkout@v3
Expand All @@ -84,23 +83,10 @@ jobs:
with:
go-version: '^1.20.1'

- name: Cache Geth
id: cache-geth
uses: actions/cache@v3
with:
path: $HOME/.local/bin
key: ${{ runner.os }}-geth-${{ env.GETH_VERSION }}

- name: Install Geth
if: steps.cache-geth.outputs.cache-hit != 'true'
run: |
mkdir -p $HOME/.local/bin
wget -O geth.tar.gz "https://github.com/ethereum/go-ethereum/archive/v$GETH_VERSION.tar.gz"
tar -zxvf geth.tar.gz
cd go-ethereum-$GETH_VERSION
make geth
cp ./build/bin/geth /usr/local/bin
geth version
uses: gacts/install-geth-tools@v1
with:
version: 1.12.2

- name: Install Dependencies
run: |
Expand Down
112 changes: 112 additions & 0 deletions docs/userguides/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,115 @@ contract = ape.Contract("0x...")
bytes_value = contract.encode_input(0, 1, 2, 4, 5)
method_id, input_dict = contract.decode_input(bytes_value)
```

## Multicall

The `ape_ethereum` core plugin comes with a `multi-call` module.
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved
Yes you're reading this right.
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved
No need to install external modules to do multicall, ape got you covered.
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved
Perform the Multicall call.
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved
This call will trigger again every time the `Call` object is called.
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved

```bash
Raises:
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved
:class:`~ape_ethereum.multicall.exceptions.UnsupportedChain`: If there is not an instance of Multicall3 deployed on the current chain at the expected address.

Args:
**call_kwargs: the kwargs to pass through to the call handler.

Returns:
Iterator[Any]: the sequence of values produced by performing each call stored by this instance.
```

### Usage
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved

Here is an example of how you can use multicall.

```py
from ape_ethereum import multicall

call = multicall.Call()

call.add(contract.myMethod, *call_args)
call.add(contract.myMethod, *call_args)
... # Add as many calls as desired
call.add(contract.myMethod, *call_args)

a, b, ..., z = call() # Performs multicall
# OR
# The return type of the multicall is a generator object. So basically this will convert the result returned by the multicall into a list
result = list(call())
```

### Practical Example
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved

This is a sample example of how you can perform multicall in a real world scenario. This piece of code will perfrom multicall on a `Uniswap V2` pool contract.
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved

```py
from ape_ethereum import multicall
from ape import project

pool = ["0xF4b8A02D4e8D76070bD7092B54D2cBbe90fa72e9","0x80067013d7F7aF4e86b3890489AcAFe79F31a4Cb"]

for pool_address in pools:
uniswap_v2_pair_contract = project.IUniswapV2Pair.at(pool_address)
call.add(uniswap_v2_pair_contract.getReserves)
multicall_result = list(call())

print(multicall_result[0])

# output
[17368643486106939361172, 31867695075486]
```

<!-- ### Encode Multicall Transaction
Encode the Multicall transaction as a ``TransactionAPI`` object, but do not execute it.
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved
Returns:
```js
:class:`~ape.api.transactions.TransactionAPI`
```

```py
from ape_ethereum import multicall

call = multicall.Call()
call.add(contract.myMethod, *call_args)
call.add(contract.myMethod, *call_args)
... # Add as many calls as desired
call.add(contract.myMethod, *call_args)

encoded_call = call.as_transaction()
``` -->

## Multicall Transaction

Create a sequence of calls to execute at once using `eth_sendTransaction` via the Multicall3 contract.
Execute the Multicall transaction.
The transaction will broadcast again every time the `Transaction` object is called.

```bash
Raises:
:class:`UnsupportedChain`: If there is not an instance of Multicall3 deployed
on the current chain at the expected address.

Args:
**txn_kwargs: the kwargs to pass through to the transaction handler.

Returns:
:class:`~ape.api.transactions.ReceiptAPI`
```

### Usage example:
Aviksaikat marked this conversation as resolved.
Show resolved Hide resolved

```py
from ape_ethereum import multicall

txn = multitxn.Transaction()
txn.add(contract.myMethod, *call_args)
txn.add(contract.myMethod, *call_args)
... # Add as many calls as desired to execute
txn.add(contract.myMethod, *call_args)
a, b, ..., z = txn(sender=my_signer) # Sends the multical transaction
# OR
result = list(txn(sender=my_signer))
```
2 changes: 1 addition & 1 deletion docs/userguides/networks.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ geth:
If you would like to connect to a URI using the `geth` provider, you can specify a URI for the provider name in the `--network` option:

```bash
ape run script --network etheruem:mainnet:https://foo.bar
ape run script --network ethereum:mainnet:https://foo.bar
```

Additionally, if you want to connect to an unknown ecosystem or network, you can use the URI by itself.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"lazyasd>=0.1.4",
"packaging>=23.0,<24",
"pandas>=1.3.0,<2",
"pluggy>=0.12,<2",
"pluggy>=1.3,<2",
"pydantic>=1.10.8,<2",
"PyGithub>=1.59,<2",
"pytest>=6.0,<8.0",
Expand Down
5 changes: 4 additions & 1 deletion src/ape/api/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ def _create_contract_from_call(
if "address" not in data:
return None, calldata

addr = data["address"]
# NOTE: Handling when providers give us odd address values.
raw_addr = HexBytes(data["address"]).hex().replace("0x", "")
zeroes = max(40 - len(raw_addr), 0) * "0"
addr = f"0x{zeroes}{raw_addr}"

try:
address = self.provider.network.ecosystem.decode_address(addr)
Expand Down
24 changes: 11 additions & 13 deletions src/ape/api/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
ContractLog,
LogFilter,
SnapshotID,
SourceTraceback,
TraceFrame,
)
from ape.utils import (
Expand Down Expand Up @@ -702,16 +703,9 @@ def _increment_call_func_coverage_hit_count(self, txn: TransactionAPI):
):
return

cov_data = self._test_runner.coverage_tracker.data
if not cov_data:
return

contract_type = self.chain_manager.contracts.get(txn.receiver)
if not contract_type:
return

contract_src = self.project_manager._create_contract_source(contract_type)
if not contract_src:
if not (contract_type := self.chain_manager.contracts.get(txn.receiver)) or not (
contract_src := self.project_manager._create_contract_source(contract_type)
):
return

method_id = txn.data[:4]
Expand Down Expand Up @@ -1571,8 +1565,7 @@ def get_virtual_machine_error(self, exception: Exception, **kwargs) -> VirtualMa
if not isinstance(err_data, dict):
return VirtualMachineError(base_err=exception, **kwargs)

err_msg = err_data.get("message")
if not err_msg:
if not (err_msg := err_data.get("message")):
return VirtualMachineError(base_err=exception, **kwargs)

if txn is not None and "nonce too low" in str(err_msg):
Expand All @@ -1593,9 +1586,14 @@ def _handle_execution_reverted(
txn: Optional[TransactionAPI] = None,
trace: Optional[Iterator[TraceFrame]] = None,
contract_address: Optional[AddressType] = None,
source_traceback: Optional[SourceTraceback] = None,
) -> ContractLogicError:
message = str(exception).split(":")[-1].strip()
params: Dict = {"trace": trace, "contract_address": contract_address}
params: Dict = {
"trace": trace,
"contract_address": contract_address,
"source_traceback": source_traceback,
}
no_reason = message == "execution reverted"

if isinstance(exception, Web3ContractLogicError) and no_reason:
Expand Down
Loading
Loading