From 6a4c648a54175bf76ee67cfdbe397a5e036431f7 Mon Sep 17 00:00:00 2001 From: banteg Date: Fri, 28 Jan 2022 14:09:04 +0300 Subject: [PATCH 1/3] feat: add state override support to eth_call --- brownie/network/contract.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/brownie/network/contract.py b/brownie/network/contract.py index 4519d8b9b..0dd6f5e8e 100644 --- a/brownie/network/contract.py +++ b/brownie/network/contract.py @@ -1296,11 +1296,15 @@ def __repr__(self) -> str: def __len__(self) -> int: return len(self.methods) - def __call__(self, *args: Tuple) -> Any: + def __call__( + self, *args: Tuple, block_identifier: Union[int, str, bytes] = None, override: Dict = None + ) -> Any: fn = self._get_fn_from_args(args) - return fn(*args) # type: ignore + return fn(*args, block_identifier=block_identifier, override=override) # type: ignore - def call(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None) -> Any: + def call( + self, *args: Tuple, block_identifier: Union[int, str, bytes] = None, override: Dict = None + ) -> Any: """ Call the contract method without broadcasting a transaction. @@ -1317,13 +1321,16 @@ def call(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None) -> A block number or hash that the call is executed at. If not given, the latest block used. Raises `ValueError` if this value is too far in the past and you are not using an archival node. + override : dict, optional + A mapping from addresses to balance, nonce, code, state, stateDiff + overrides for the context of the call. Returns ------- Contract method return value(s). """ fn = self._get_fn_from_args(args) - return fn.call(*args, block_identifier=block_identifier) + return fn.call(*args, block_identifier=block_identifier, override=override) def transact(self, *args: Tuple) -> TransactionReceiptType: """ @@ -1464,7 +1471,9 @@ def info(self) -> None: print(f"{self.abi['name']}({_inputs(self.abi)})") _print_natspec(self.natspec) - def call(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None) -> Any: + def call( + self, *args: Tuple, block_identifier: Union[int, str, bytes] = None, override: Dict = None + ) -> Any: """ Call the contract method without broadcasting a transaction. @@ -1477,6 +1486,9 @@ def call(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None) -> A block number or hash that the call is executed at. If not given, the latest block used. Raises `ValueError` if this value is too far in the past and you are not using an archival node. + override : dict, optional + A mapping from addresses to balance, nonce, code, state, stateDiff + overrides for the context of the call. Returns ------- @@ -1490,7 +1502,7 @@ def call(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None) -> tx.update({"to": self._address, "data": self.encode_input(*args)}) try: - data = web3.eth.call({k: v for k, v in tx.items() if v}, block_identifier) + data = web3.eth.call({k: v for k, v in tx.items() if v}, block_identifier, override) except ValueError as e: raise VirtualMachineError(e) from None @@ -1676,7 +1688,9 @@ class ContractCall(_ContractMethod): Bytes4 method signature. """ - def __call__(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None) -> Any: + def __call__( + self, *args: Tuple, block_identifier: Union[int, str, bytes] = None, override: Dict = None + ) -> Any: """ Call the contract method without broadcasting a transaction. @@ -1689,6 +1703,9 @@ def __call__(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None A block number or hash that the call is executed at. If not given, the latest block used. Raises `ValueError` if this value is too far in the past and you are not using an archival node. + override : dict, optional + A mapping from addresses to balance, nonce, code, state, stateDiff + overrides for the context of the call. Returns ------- @@ -1696,7 +1713,7 @@ def __call__(self, *args: Tuple, block_identifier: Union[int, str, bytes] = None """ if not CONFIG.argv["always_transact"] or block_identifier is not None: - return self.call(*args, block_identifier=block_identifier) + return self.call(*args, block_identifier=block_identifier, override=override) args, tx = _get_tx(self._owner, args) tx.update({"gas_price": 0, "from": self._owner or accounts[0]}) From d2fa987933548b5bbf483f2413b5a701f4b77874 Mon Sep 17 00:00:00 2001 From: banteg Date: Fri, 28 Jan 2022 14:45:31 +0300 Subject: [PATCH 2/3] docs: add docs about eth_call override --- docs/api-network.rst | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/api-network.rst b/docs/api-network.rst index 5421deb04..d28629942 100644 --- a/docs/api-network.rst +++ b/docs/api-network.rst @@ -1065,12 +1065,13 @@ Contract Internal Attributes ContractCall ------------ -.. py:class:: brownie.network.contract.ContractCall(*args, block_identifier=None) +.. py:class:: brownie.network.contract.ContractCall(*args, block_identifier=None, override=None) Calls a non state-changing contract method without broadcasting a transaction, and returns the result. ``args`` must match the required inputs for the method. * ``args``: Input arguments for the call. The expected inputs are shown in the method's ``__repr__`` value. * ``block_identifier``: A block number or hash that the call is executed at. If ``None``, the latest block is used. Raises `ValueError` if this value is too far in the past and you are not using an archival node. + * ``override``: A mapping from addresses to balance, nonce, code, state, stateDiff overrides for the context of the call. Inputs and return values are formatted via methods in the :ref:`convert` module. Multiple values are returned inside a :func:`ReturnValue `. @@ -1081,6 +1082,8 @@ ContractCall >>> Token[0].allowance(accounts[0], accounts[2]) 0 + For override see :ref:`ContractTx.call` docs. + ContractCall Attributes *********************** @@ -1197,12 +1200,14 @@ ContractTx Attributes ContractTx Methods ****************** -.. py:classmethod:: ContractTx.call(*args, block_identifier=None) + +.. py:classmethod:: ContractTx.call(*args, block_identifier=None, override=None) Calls the contract method without broadcasting a transaction, and returns the result. * ``args``: Input arguments for the call. The expected inputs are shown in the method's ``__repr__`` value. * ``block_identifier``: A block number or hash that the call is executed at. If ``None``, the latest block is used. Raises `ValueError` if this value is too far in the past and you are not using an archival node. + * ``override``: A mapping from addresses to balance, nonce, code, state, stateDiff overrides for the context of the call. Inputs and return values are formatted via methods in the :ref:`convert` module. Multiple values are returned inside a :func:`ReturnValue `. @@ -1211,6 +1216,30 @@ ContractTx Methods >>> Token[0].transfer.call(accounts[2], 10000, {'from': accounts[0]}) True + .. _override: + + The override argument allows replacing balance, nonce and code associated with an address, as well as overwriting individual storage slot value. + See `Geth docs `_ for more details. + + For example, you can query an exchange rate of an imbalanced Curve pool if it had a different A parameter: + + .. code-block:: python + + >>> for A in [300, 1000, 2000]: + override = { + "0x5a6A4D54456819380173272A5E8E9B9904BdF41B": { + "stateDiff": { + "0x0000000000000000000000000000000000000000000000000000000000000009": hex(A * 100), + } + } + } + result = pool.get_dy_underlying(0, 1, 1e18, override=override) + print(A, result.to("ether")) + + 300 0.884657790783695579 + 1000 0.961374099348799411 + 2000 0.979998831913646748 + .. py:classmethod:: ContractTx.decode_input(hexstr) Decodes hexstring input data for this method. From d08b7cd6bbccccbadd8025a6180a55309dfe1a4f Mon Sep 17 00:00:00 2001 From: banteg Date: Fri, 28 Jan 2022 14:54:01 +0300 Subject: [PATCH 3/3] chore: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a32d078a8..547432bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Force files to be opened as UTF-8 - Added a new solidity compiler setting `use_latest_patch` in brownie-config.yaml to use the latest patch version of a compiler based on the pragma version of the contract. - Add cli flag `-r` for raising exceptions to the caller instead of doing a system exit. +- Add `override` argument to contract methods which allows changing the state before the call, including overwriting balance, nonce, code, and storage of any address. ## [1.17.2](https://github.com/eth-brownie/brownie/tree/v1.17.2) - 2021-12-04 ### Changed