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

added abi_contract.py - Contract Abi decoding utility functions #69

Closed

Conversation

tintinweb
Copy link
Member

@tintinweb tintinweb commented May 8, 2018

What was wrong?

All fine, just adding functionality for convenience.
This PR provides the missing piece when trying to decode an arbitrary transaction input stream with a json abi definition. So all you do is create a ContractAbi() object providing the abi declaration in json format and then call either ContractAbi.describe_constructor(b_constructor_arguments) or ContractAbi.describe_input(b_inputbytes). I am using this functionality in my etherchain library and cli to decode transactions/arguments and dump contract source code.

Here's a short example. For a complete example scroll below the cute animal picture.

ca = ContractAbi(json.loads(json_abi_str))
print(ca.describe_input(b'797aaf...3d7707'))  # function confirm ((bytes32) _h = b'\x98\xd7\x90\xd3\x13>\x00I!Vi\xe0\x9bU\xa0\xb5\x9dXl\x95\xf9L-V\xb2\x81 @\x13=w\x07') returns ((bool) )

How was it fixed?

added new module abi_contract.py providing the missing functionality not changing any existing code.

Cute Animal Picture

Cute animal picture

full example

Decode inputs to contract at 0xab7c74abc0c4d48d1bdad5dcb26153fc8780f83e to a more human friendly notation (see decoded constructor args and one of the input transactions; details see code below)

  constructor None ((address[]) _owners = ('0x2903cadbe271e057edef157340b52a5898d7424f', '0xba7ca1bcf210c1b37cf5818816c4a819c3040ea7', '0x14cd6536d449e3f6878f2d6859e1ff92ae0990e6', '0x0c24441e42277445e38e02dfc3494577c05ba46b'), (uint256) _required = 2, (uint256) _daylimit = 1000000000000000000) returns ()
  function confirm ((bytes32) _h = b'\x98\xd7\x90\xd3\x13>\x00I!Vi\xe0\x9bU\xa0\xb5\x9dXl\x95\xf9L-V\xb2\x81 @\x13=w\x07') returns ((bool) )
from eth_abi.abi_contract import ContractAbi
import json

if __name__ == "__main__":
    # This is our example contract:
    #   https://www.etherchain.org/account/ab7c74abc0c4d48d1bdad5dcb26153fc8780f83e#code
    abi = """[
  {
    "constant": false,
    "inputs": [
      {
        "name": "_owner",
        "type": "address"
      }
    ],
    "name": "removeOwner",
    "outputs": [],
    "type": "function",
    "signature": "0x173825d9"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_addr",
        "type": "address"
      }
    ],
    "name": "isOwner",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "type": "function",
    "signature": "0x2f54bf6e"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "m_numOwners",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function",
    "signature": "0x4123cb6b"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "m_lastDay",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function",
    "signature": "0x52375093"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "version",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function",
    "signature": "0x54fd4d50"
  },
  {
    "constant": false,
    "inputs": [],
    "name": "resetSpentToday",
    "outputs": [],
    "type": "function",
    "signature": "0x5c52c2f5"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "m_spentToday",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function",
    "signature": "0x659010e7"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_owner",
        "type": "address"
      }
    ],
    "name": "addOwner",
    "outputs": [],
    "type": "function",
    "signature": "0x7065cb48"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "m_required",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function",
    "signature": "0x746c9171"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_h",
        "type": "bytes32"
      }
    ],
    "name": "confirm",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "type": "function",
    "signature": "0x797af627"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_newLimit",
        "type": "uint256"
      }
    ],
    "name": "setDailyLimit",
    "outputs": [],
    "type": "function",
    "signature": "0xb20d30a9"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      },
      {
        "name": "_data",
        "type": "bytes"
      }
    ],
    "name": "execute",
    "outputs": [
      {
        "name": "_r",
        "type": "bytes32"
      }
    ],
    "type": "function",
    "signature": "0xb61d27f6"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_operation",
        "type": "bytes32"
      }
    ],
    "name": "revoke",
    "outputs": [],
    "type": "function",
    "signature": "0xb75c7dc6"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_newRequired",
        "type": "uint256"
      }
    ],
    "name": "changeRequirement",
    "outputs": [],
    "type": "function",
    "signature": "0xba51a6df"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "_operation",
        "type": "bytes32"
      },
      {
        "name": "_owner",
        "type": "address"
      }
    ],
    "name": "hasConfirmed",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "type": "function",
    "signature": "0xc2cf7326"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_to",
        "type": "address"
      }
    ],
    "name": "kill",
    "outputs": [],
    "type": "function",
    "signature": "0xcbf0b0c0"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_from",
        "type": "address"
      },
      {
        "name": "_to",
        "type": "address"
      }
    ],
    "name": "changeOwner",
    "outputs": [],
    "type": "function",
    "signature": "0xf00d4b5d"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "m_dailyLimit",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function",
    "signature": "0xf1736d86"
  },
  {
    "inputs": [
      {
        "name": "_owners",
        "type": "address[]"
      },
      {
        "name": "_required",
        "type": "uint256"
      },
      {
        "name": "_daylimit",
        "type": "uint256"
      }
    ],
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "operation",
        "type": "bytes32"
      }
    ],
    "name": "Confirmation",
    "type": "event",
    "signature": "0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "operation",
        "type": "bytes32"
      }
    ],
    "name": "Revoke",
    "type": "event",
    "signature": "0xc7fb647e59b18047309aa15aad418e5d7ca96d173ad704f1031a2c3d7591734b"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "oldOwner",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnerChanged",
    "type": "event",
    "signature": "0xb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnerAdded",
    "type": "event",
    "signature": "0x994a936646fe87ffe4f1e469d3d6aa417d6b855598397f323de5b449f765f0c3"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "oldOwner",
        "type": "address"
      }
    ],
    "name": "OwnerRemoved",
    "type": "event",
    "signature": "0x58619076adf5bb0943d100ef88d52d7c3fd691b19d3a9071b555b651fbf418da"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "newRequirement",
        "type": "uint256"
      }
    ],
    "name": "RequirementChanged",
    "type": "event",
    "signature": "0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "from",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Deposit",
    "type": "event",
    "signature": "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "value",
        "type": "uint256"
      },
      {
        "indexed": false,
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "SingleTransact",
    "type": "event",
    "signature": "0x92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "operation",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "name": "value",
        "type": "uint256"
      },
      {
        "indexed": false,
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "MultiTransact",
    "type": "event",
    "signature": "0xe7c957c06e9a662c1a6c77366179f5b702b97651dc28eee7d5bf1dff6e40bb4a"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "operation",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "name": "initiator",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "value",
        "type": "uint256"
      },
      {
        "indexed": false,
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "ConfirmationNeeded",
    "type": "event",
    "signature": "0x1733cbb53659d713b79580f79f3f9ff215f78a7c7aa45890f3b89fc5cddfbf32"
  }
]"""
    ca = ContractAbi(json.loads(abi))

    # This is the constructor arguments:
    #   https://etherscan.io/address/0xab7c74abc0c4d48d1bdad5dcb26153fc8780f83e#code
    print(ca.describe_constructor(ContractAbi.str_to_bytes(
        "000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000002903cadbe271e057edef157340b52a5898d7424f000000000000000000000000ba7ca1bcf210c1b37cf5818816c4a819c3040ea700000000000000000000000014cd6536d449e3f6878f2d6859e1ff92ae0990e60000000000000000000000000c24441e42277445e38e02dfc3494577c05ba46b")))
    # constructor None ((address[]) _owners = ('0x2903cadbe271e057edef157340b52a5898d7424f', '0xba7ca1bcf210c1b37cf5818816c4a819c3040ea7', '0x14cd6536d449e3f6878f2d6859e1ff92ae0990e6', '0x0c24441e42277445e38e02dfc3494577c05ba46b'), (uint256) _required = 2, (uint256) _daylimit = 1000000000000000000) returns ()


    # This is one input transaction:
    #   https://www.etherchain.org/tx/1e9ed6236afb884fe7cad9a807886ba61b9e9a2fc944a991e3e8725d2158c7b2
    print(ca.describe_input(ContractAbi.str_to_bytes(
        "0x797af62798d790d3133e0049215669e09b55a0b59d586c95f94c2d56b2812040133d7707")))
    # function confirm ((bytes32) _h = b'\x98\xd7\x90\xd3\x13>\x00I!Vi\xe0\x9bU\xa0\xb5\x9dXl\x95\xf9L-V\xb2\x81 @\x13=w\x07') returns ((bool) )

@pipermerriam
Copy link
Member

pipermerriam commented May 8, 2018

Thanks for the contribution, however, this type of functionality is out of scope for this library. Please have a look at http://web3py.readthedocs.io/en/stable/contracts.html for ways that you can interact with contracts using high level abstractions like a contract object using the web3.py python library.

@tintinweb
Copy link
Member Author

That was quick @pipermerriam 👍
This PR provides an interface to decode static bytestreams based on an api declaration in json (basically the parent functionality to your decode() method). Basically input abi and a bytestream of a tx to easily decode it without having to manually specify the types. The web3.py functionality you are referring to appears to provide a way to interact with contracts onchain which is kind of different to what I propose. However I understand if this is out of scope for this project but it was something that I was missing here :)

@pipermerriam
Copy link
Member

My preference would be for eth-abi to remain low level and very simple, thus largely only exposing the following APIs

  • encode and decode functions for single types
  • encode and decode functions for full abi signatures.
  • registry for custom encoders and decoders for non-standard ABI types.

Anything above this and we start getting into contract semantics and I'd like to push that up to a higher layer. My suggestion would be to think about releasing this as it's own package built on top of eth-abi or to open up an issue outlining the functionality you think we should add and we can talk about how that API might fit into this repository.

@pipermerriam
Copy link
Member

Or, to bring this over to the web3.py codebase and open an issue there for adding this type of functionality to the Contract API.

@tintinweb
Copy link
Member Author

tintinweb commented May 8, 2018

Thank you for providing your thoughts on this.
web3.py does not seem like a good fit for me as importing the web3.py module for this simple decoding task would be an overkill. I've already released this as part of my onchain contract browsing cli and I felt like this would be something others are also looking for. The idea was to provide a higher level function to decode input without having to specify the types explicitly. I'll leave it in my fork and it will still be available in that utility I created just in case someone is looking for this. thanks for maintaining this project.

cheers
tin

pacrob pushed a commit to pacrob/eth-abi that referenced this pull request Apr 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants