Skip to content

Commit

Permalink
Use brownie preview() function to estimate gas
Browse files Browse the repository at this point in the history
  • Loading branch information
Uxio0 committed Sep 21, 2021
1 parent 00f7142 commit f776a0a
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 23 deletions.
35 changes: 22 additions & 13 deletions ape_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from gnosis.safe.multi_send import MultiSend, MultiSendOperation, MultiSendTx
from gnosis.safe.safe_tx import SafeTx


MULTISEND_CALL_ONLY = '0x40A2aCCbd92BCA938b02010E17A5b8929b49130D'
multisends = {
250: '0x10B62CC1E8D9a9f1Ad05BCC491A7984697c19f7E',
Expand Down Expand Up @@ -89,7 +88,7 @@ def tx_from_receipt(self, receipt: TransactionReceipt, operation: SafeOperation
"""
if safe_nonce is None:
safe_nonce = self.pending_nonce()

return self.build_multisig_tx(receipt.receiver, receipt.value, receipt.input, operation=operation.value, safe_nonce=safe_nonce)

def multisend_from_receipts(self, receipts: List[TransactionReceipt] = None, safe_nonce: int = None) -> SafeTx:
Expand All @@ -98,10 +97,10 @@ def multisend_from_receipts(self, receipts: List[TransactionReceipt] = None, saf
"""
if receipts is None:
receipts = history.from_sender(self.address)

if safe_nonce is None:
safe_nonce = self.pending_nonce()

txs = [MultiSendTx(MultiSendOperation.CALL, tx.receiver, tx.value, tx.input) for tx in receipts]
data = MultiSend(self.multisend, self.ethereum_client).build_tx_data(txs)
return self.build_multisig_tx(self.multisend, 0, data, SafeOperation.DELEGATE_CALL.value, safe_nonce=safe_nonce)
Expand All @@ -112,12 +111,12 @@ def sign_transaction(self, safe_tx: SafeTx, signer: Union[LocalAccount, str] = N
"""
if signer is None:
signer = click.prompt('signer', type=click.Choice(accounts.load()))

if isinstance(signer, str):
# Avoids a previously impersonated account with no signing capabilities
accounts.clear()
signer = accounts.load(signer)

safe_tx.sign(signer.private_key)
return safe_tx

Expand All @@ -130,10 +129,11 @@ def post_transaction(self, safe_tx: SafeTx):
"""
if not safe_tx.sorted_signers:
self.sign_transaction(safe_tx)

sender = safe_tx.sorted_signers[0]

url = urljoin(self.base_url, f'/api/v1/safes/{self.address}/multisig-transactions/')

data = {
'to': safe_tx.to,
'value': safe_tx.value,
Expand All @@ -154,13 +154,21 @@ def post_transaction(self, safe_tx: SafeTx):
if not response.ok:
raise ApiError(f'Error posting transaction: {response.content}')

def estimate_gas(self, safe_tx: SafeTx) -> int:
def estimate_gas(self, safe_tx: SafeTx, update_safe_tx_gas: bool = False) -> int:
"""
Estimate gas limit for successful execution.
Estimate gas limit for successful execution. If `update_tx_gas=True` provided SafeTx will have `safe_tx_gas`
and `base_gas` updated
"""
return self.estimate_tx_gas(safe_tx.to, safe_tx.value, safe_tx.data, safe_tx.operation)
# return self.estimate_tx_gas(safe_tx.to, safe_tx.value, safe_tx.data, safe_tx.operation)
gas_estimation = 20_000 + int(1.3 * self.preview(safe_tx, events=False, print_details=False).gas_used)
if update_safe_tx_gas:
safe_tx.safe_tx_gas = gas_estimation
safe_tx.base_gas = self.estimate_tx_base_gas(safe_tx.to, safe_tx.value, safe_tx.data, safe_tx.operation,
safe_tx.gas_token, safe_tx.safe_tx_gas)
safe_tx.signatures = b'' # As we are modifying the tx, previous signatures are not valid anymore
return gas_estimation

def preview(self, safe_tx: SafeTx, events=True, call_trace=False, reset=True, gas_limit=None):
def preview(self, safe_tx: SafeTx, events=True, call_trace=False, reset=True, gas_limit=None, print_details=True):
"""
Dry run a Safe transaction in a forked network environment.
"""
Expand Down Expand Up @@ -199,7 +207,7 @@ def preview(self, safe_tx: SafeTx, events=True, call_trace=False, reset=True, ga
receipt.info()
receipt.call_trace(True)
raise ExecutionFailure()

if events:
receipt.info()

Expand All @@ -209,7 +217,8 @@ def preview(self, safe_tx: SafeTx, events=True, call_trace=False, reset=True, ga
# Offset gas refund for clearing storage when on-chain signatures are consumed.
# https://github.com/gnosis/safe-contracts/blob/v1.1.1/contracts/GnosisSafe.sol#L140
refunded_gas = 15_000 * (threshold - 1)
click.secho(f'recommended gas limit: {receipt.gas_used + refunded_gas}', fg='green', bold=True)
if print_details:
click.secho(f'recommended gas limit: {receipt.gas_used + refunded_gas}', fg='green', bold=True)

return receipt

Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#
import os
import sys

sys.path.insert(0, os.path.abspath('..'))


Expand Down
6 changes: 3 additions & 3 deletions docs/detailed.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ Play around the same way you would do with a normal account:
.. code-block:: python
>>> from ape_safe import ApeSafe
# You can specify an ENS name here
# Specify an EthereumClient if you don't run a local node
>>> safe = ApeSafe('ychad.eth')
# Unlocked account is available as `safe.account`
>>> safe.account
<Account '0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52'>
Expand All @@ -46,7 +46,7 @@ Play around the same way you would do with a normal account:
>>> vault.depositAll()
>>> vault.balanceOf(safe.account)
2609.5479641693646
# Combine transaction history into a multisend transaction
>>> safe_tx = safe.multisend_from_receipts()
Expand Down
2 changes: 1 addition & 1 deletion docs/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ Since multisig signers are usually slow to fulfill their duties, it's common to
Batching also serves as a rudimentary zap if you are not concerned about the exactly matching in/out values and allow for some slippage.

Gnosis Safe has an excellent Transaction Builder app that allows scaffolding complex interactions.
This approach is usually faster and cheaper than deploying a bespoke contract for every transaction.
This approach is usually faster and cheaper than deploying a bespoke contract for every transaction.

Ape Safe expands on this idea. It allows you to use multisig as a regular account and then convert the transaction history into one multisend transaction and make sure it works before it hits the signers.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ readme = "readme.md"
[tool.poetry.dependencies]
python = "^3.8"
eth-brownie = "^1.16.3"
gnosis-py = "^3.2.2"
gnosis-py = "^3.3.3"

[tool.poetry.dev-dependencies]

Expand Down
9 changes: 4 additions & 5 deletions tests/test_sorting.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import pytest

from brownie.test import given, strategy
from brownie.convert.datatypes import EthAddress
from brownie.test import given, strategy


@pytest.mark.xfail
@given(strategy('uint256[]', max_value=0xffffffffffffffffffffffffffffffffffffffff))
def test_sorting_checksum(addrs):
addrs = [EthAddress(addr.to_bytes(20, 'big', signed=False)) for addr in addrs]

sort_old = sorted(addrs)
sort_new = sorted(addrs, key=lambda addr: int(addr, 16))

Expand All @@ -17,9 +17,8 @@ def test_sorting_checksum(addrs):
@given(strategy('uint256[]', max_value=0xffffffffffffffffffffffffffffffffffffffff))
def test_sorting_lower(addrs):
addrs = [EthAddress(addr.to_bytes(20, 'big', signed=False)) for addr in addrs]

sort_old = sorted(addrs, key=lambda addr: addr.lower())
sort_new = sorted(addrs, key=lambda addr: int(addr, 16))

assert sort_old == sort_new

0 comments on commit f776a0a

Please sign in to comment.