Skip to content

Commit 4628c2d

Browse files
committed
v0.1.34 added state overwrite capabilities to multicall and added utilities for calculate_create_address and get_dict_slot
1 parent 870c370 commit 4628c2d

File tree

4 files changed

+67
-27
lines changed

4 files changed

+67
-27
lines changed

IceCreamSwapWeb3/AddressCalculator.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from functools import lru_cache
2+
3+
import eth_utils
4+
import rlp
5+
from eth_utils import to_bytes
6+
from web3 import Web3
7+
8+
@lru_cache(maxsize=1024)
9+
def get_dict_slot(slot: int, key: int) -> int:
10+
return int.from_bytes(Web3.solidity_keccak(["uint256", "uint256"], [key, slot]), byteorder='big')
11+
12+
13+
@lru_cache(maxsize=1024)
14+
def calculate_create_address(sender: str, nonce: int) -> str:
15+
assert len(sender) == 42
16+
sender_bytes = to_bytes(hexstr=sender)
17+
raw = rlp.encode([sender_bytes, nonce])
18+
h = eth_utils.keccak(raw)
19+
address_bytes = h[12:]
20+
return eth_utils.to_checksum_address(address_bytes)

IceCreamSwapWeb3/Multicall.py

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
from typing import Optional
44

55
import eth_abi
6-
import eth_utils
7-
import rlp
86
from eth_utils import to_bytes
97
from eth_utils.abi import get_abi_output_types, get_abi_input_types
108
from web3.contract.contract import ContractFunction, ContractConstructor
119
from web3.exceptions import ContractLogicError
10+
from web3.types import StateOverride
1211

13-
from IceCreamSwapWeb3 import Web3Advanced
12+
from .AddressCalculator import calculate_create_address
1413
from .FastChecksumAddress import to_checksum_address
14+
from .Web3Advanced import Web3Advanced
1515

1616
# load multicall abi
1717
with files("IceCreamSwapWeb3").joinpath("./abi/Multicall.abi").open('r') as f:
@@ -54,7 +54,7 @@ def __init__(
5454
abi=MULTICALL_ABI,
5555
address=to_checksum_address(self.MULTICALL_DEPLOYMENTS[self.chain_id])
5656
)
57-
self.undeployed_contract_address = self.calculate_create_address(sender=self.multicall.address, nonce=1)
57+
self.undeployed_contract_address = calculate_create_address(sender=self.multicall.address, nonce=1)
5858
else:
5959
self.multicall = self.w3.eth.contract(abi=UNDEPLOYED_MULTICALL_ABI, bytecode=UNDEPLOYED_MULTICALL_BYTECODE)
6060
self.undeployed_contract_address = self.calculate_expected_contract_address(sender=self.CALLER_ADDRESS, nonce=0)
@@ -75,30 +75,53 @@ def add_undeployed_contract_call(self, contract_func: ContractFunction):
7575
contract_func.address = 0 # self.undeployed_contract_address
7676
self.calls.append(contract_func)
7777

78-
def call(self, use_revert: Optional[bool] = None, batch_size: int = 1_000):
79-
results, _ = self.call_with_gas(use_revert=use_revert, batch_size=batch_size)
78+
def call(
79+
self,
80+
use_revert: Optional[bool] = None,
81+
batch_size: int = 1_000,
82+
state_override: Optional[StateOverride] = None
83+
):
84+
results, _ = self.call_with_gas(
85+
use_revert=use_revert,
86+
batch_size=batch_size,
87+
state_override=state_override
88+
)
8089
return results
8190

82-
def call_with_gas(self, use_revert: Optional[bool] = None, batch_size: int = 1_000):
91+
def call_with_gas(
92+
self,
93+
use_revert: Optional[bool] = None,
94+
batch_size: int = 1_000,
95+
state_override: Optional[StateOverride] = None
96+
):
97+
if state_override is not None:
98+
assert self.w3.overwrites_available
8399
if use_revert is None:
84100
use_revert = self.w3.revert_reason_available
85101

86102
calls = self.calls
87103
calls_with_calldata = self.add_calls_calldata(calls)
88104

89-
return self._inner_call(use_revert=use_revert, calls_with_calldata=calls_with_calldata, batch_size=batch_size)
105+
return self._inner_call(
106+
use_revert=use_revert,
107+
calls_with_calldata=calls_with_calldata,
108+
batch_size=batch_size,
109+
state_override=state_override
110+
)
90111

91112
def _inner_call(
92113
self,
93114
use_revert: bool,
94115
calls_with_calldata: list[tuple[ContractFunction, bytes]],
95-
batch_size: int
116+
batch_size: int,
117+
state_override: Optional[StateOverride] = None
96118
) -> tuple[list[Exception | tuple[any, ...]], list[int]]:
97119
if len(calls_with_calldata) == 0:
98120
return [], []
99121
kwargs = dict(
100122
use_revert=use_revert,
101123
batch_size=batch_size,
124+
state_override=state_override,
102125
)
103126
# make sure calls are not bigger than batch_size
104127
if len(calls_with_calldata) > batch_size:
@@ -128,15 +151,17 @@ def _inner_call(
128151
raw_returns, gas_usages = self._call_multicall(
129152
multicall_call=multicall_call,
130153
use_revert=use_revert,
131-
retry=False
154+
retry=False,
155+
state_override=state_override
132156
)
133157
except Exception as e:
134158
if len(calls_with_calldata) == 1:
135159
try:
136160
raw_returns, gas_usages = self._call_multicall(
137161
multicall_call=multicall_call,
138162
use_revert=use_revert,
139-
retry=True
163+
retry=True,
164+
state_override=state_override
140165
)
141166
except Exception as e:
142167
raw_returns = [e]
@@ -161,19 +186,10 @@ def _inner_call(
161186

162187
@staticmethod
163188
def calculate_expected_contract_address(sender: str, nonce: int):
164-
undeployed_contract_runner_address = MultiCall.calculate_create_address(sender=sender, nonce=nonce)
165-
contract_address = MultiCall.calculate_create_address(sender=undeployed_contract_runner_address, nonce=1)
189+
undeployed_contract_runner_address = calculate_create_address(sender=sender, nonce=nonce)
190+
contract_address = calculate_create_address(sender=undeployed_contract_runner_address, nonce=1)
166191
return contract_address
167192

168-
@staticmethod
169-
def calculate_create_address(sender: str, nonce: int) -> str:
170-
assert len(sender) == 42
171-
sender_bytes = to_bytes(hexstr=sender)
172-
raw = rlp.encode([sender_bytes, nonce])
173-
h = eth_utils.keccak(raw)
174-
address_bytes = h[12:]
175-
return eth_utils.to_checksum_address(address_bytes)
176-
177193
@staticmethod
178194
def add_calls_calldata(calls: list[ContractFunction]) -> list[tuple[ContractFunction, bytes]]:
179195
calls_with_calldata = []
@@ -340,7 +356,8 @@ def _call_multicall(
340356
self,
341357
multicall_call: ContractConstructor | ContractFunction,
342358
use_revert: bool,
343-
retry: bool = False
359+
retry: bool = False,
360+
state_override: Optional[StateOverride] = None
344361
):
345362
# call transaction
346363
try:
@@ -350,7 +367,7 @@ def _call_multicall(
350367
"nonce": 0,
351368
"data": multicall_call.data_in_transaction,
352369
"no_retry": not retry,
353-
})
370+
}, state_override=state_override)
354371
else:
355372
assert isinstance(multicall_call, ContractFunction)
356373
# manually encoding and decoding call because web3.py is sooooo slow...
@@ -369,7 +386,7 @@ def _call_multicall(
369386
"nonce": 0,
370387
"data": calldata,
371388
"no_retry": not retry,
372-
})
389+
}, state_override=state_override)
373390
_, multicall_result = eth_abi.decode(get_abi_output_types(multicall_call.abi), raw_response)
374391

375392
if len(multicall_result) > 0 and self.undeployed_contract_constructor is not None:
@@ -416,6 +433,8 @@ def main(
416433
node_url="https://rpc-core.icecreamswap.com",
417434
usdt_address=to_checksum_address("0x900101d06A7426441Ae63e9AB3B9b0F63Be145F1"),
418435
):
436+
from IceCreamSwapWeb3 import Web3Advanced
437+
419438
w3 = Web3Advanced(node_url=node_url)
420439

421440
with files("IceCreamSwapWeb3").joinpath("./abi/Counter.abi").open('r') as f:

IceCreamSwapWeb3/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .Web3Advanced import Web3Advanced
2-
from .FastChecksumAddress import to_checksum_address
2+
from .FastChecksumAddress import to_checksum_address
3+
from .AddressCalculator import get_dict_slot, calculate_create_address

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from setuptools import setup, find_packages
22

3-
VERSION = '0.1.33'
3+
VERSION = '0.1.34'
44
DESCRIPTION = 'IceCreamSwap Web3.py wrapper'
55
LONG_DESCRIPTION = 'IceCreamSwap Web3.py wrapper with automatic retries, multicall and other advanced functionality'
66

0 commit comments

Comments
 (0)