Skip to content

Commit

Permalink
Add 4byte db support.
Browse files Browse the repository at this point in the history
  • Loading branch information
lmy375 committed Feb 9, 2022
1 parent 6a089bc commit acabe99
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 2 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,23 @@ Documented commands (type help <topic>):
========================================
balance bye contract erc20 exit help nonce number quit storage
# print current block number
peth > number
14158806
# print balance
peth > balance 0xdAC17F958D2ee523a2206206994597C13D831ec7
1 Wei( 0.0000 Ether)
# print nonce
peth > nonce 0xdAC17F958D2ee523a2206206994597C13D831ec7
1
# print specified slot of storage
peth > storage 0xdAC17F958D2ee523a2206206994597C13D831ec7 1
0x000000000000000000000000000000000000000000000000008d7b18430396d4
# print contract information of Etherscan.
peth > contract 0xdAC17F958D2ee523a2206206994597C13D831ec7
SourceCode : pragma solidity ^0.4.17;
ABI : [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string ...
Expand Down Expand Up @@ -151,13 +160,60 @@ peth > contract 0xdAC17F958D2ee523a2206206994597C13D831ec7
event Transfer(address from,address to,uint256 value)
event Pause()
event Unpause()
# print ERC20 information (Call ERC20 view methods)
peth > erc20 0xdAC17F958D2ee523a2206206994597C13D831ec7
totalSupply() -> (uint256) => 39823315849942740
name() -> (string) => Tether USD
symbol() -> (string) => USDT
decimals() -> (uint8) => 6
# Call ERC20 method
peth > erc20 0xdAC17F958D2ee523a2206206994597C13D831ec7 balanceOf 0xdAC17F958D2ee523a2206206994597C13D831ec7
644096679570
# Query 4byte database.
peth > 4byte 0x70a08231
passphrase_calculate_transfer(uint64,address)
branch_passphrase_public(uint256,bytes8)
balanceOf(address)
# Extract selector dispatching code and print signatures.
peth > abi4byte 0xdAC17F958D2ee523a2206206994597C13D831ec7
0x6fdde03 name(), message_hour(uint256,int8,uint16,bytes32)
0x753c30c deprecate(address)
0x95ea7b3 approve(address,uint256), sign_szabo_bytecode(bytes16,uint128)
0xe136b19 deprecated()
0xecb93c0 addBlackList(address)
0x18160ddd totalSupply(), voting_var(address,uint256,int128,int128)
0x23b872dd transferFrom(address,address,uint256), gasprice_bit_ether(int128)
0x26976e3f upgradedAddress()
0x27e235e3 balances(address)
0x313ce567 decimals(), available_assert_time(uint16,uint64)
0x35390714 maximumFee()
0x3eaaf86b _totalSupply()
0x3f4ba83a unpause()
0x59bf1abe getBlackListStatus(address)
0x5c658165 allowed(address,address)
0x5c975abb paused()
0x70a08231 balanceOf(address), branch_passphrase_public(uint256,bytes8), passphrase_calculate_transfer(uint64,address)
0x8456cb59 pause()
0x893d20e8 getOwner()
0x8da5cb5b owner(), ideal_warn_timed(uint256,uint128)
0x95d89b41 symbol(), link_classic_internal(uint64,int64)
0xa9059cbb transfer(address,uint256), many_msg_babbage(bytes1), transfer(bytes4[9],bytes5[6],int48[11]), func_2093253501(bytes)
0xc0324c77 setParams(uint256,uint256)
0xcc872b66 issue(uint256)
0xdb006a75 redeem(uint256)
0xdd62ed3e allowance(address,address), remove_good(uint256[],bytes8,bool), _func_5437782296(address,address)
0xdd644f72 basisPointsRate()
0xe47d6060 isBlackListed(address)
0xe4997dc5 removeBlackList(address)
0xe5b5019a MAX_UINT()
0xf2fde38b transferOwnership(address)
0xf3bdc228 destroyBlackFunds(address)
peth > exit
bye!
```
129 changes: 129 additions & 0 deletions code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import codecs
from typing import Dict, List, Optional

from opcodes import OpCode

class Instruction(object):
def __init__(self, op: OpCode, opnd: Optional[int] = None, pc: int = 0) -> None:
self.pc = pc
self.op = op
self.opnd = opnd

def disasm(self) -> str:
if self.op.operand_size == 0:
return self.op.mnemonic
else:
return self.op.mnemonic + " " + hex(self.opnd)

def asm(self) -> bytes:
opnd_size = self.op.operand_size
if opnd_size == 0:
return self.op.code.to_bytes(1, "big")
else:
return self.op.code.to_bytes(1, "big") + self.opnd.to_bytes(
opnd_size, "big"
)

def __repr__(self) -> str:
return "%s %s %s" % (self.pc, self.asm().hex(), self.disasm())

@property
def instruction_size(self) -> int:
return 1 + self.op.operand_size


class Code(object):
def __init__(self, code) -> None:
self.set_code(code)
self.pc = 0
self._instructions_cache: Dict[int, Instruction] = {}
self.instructions: List[Instruction] = []

def set_code(self, code):
if type(code) is str and code.startswith("0x"):
self.code = bytearray(codecs.decode(bytearray(code[2:], "ascii"), "hex"))
elif type(code) is Code:
self.code = bytearray(code.code)
else:
self.code = bytearray(code)

def get_op(self) -> Optional[OpCode]:
if self.pc >= len(self.code):
return None
op = OpCode.from_code(self.code[self.pc])
return op

def patch_bytes(self, offset, data):
size = len(data)
self.code[offset : offset + size] = data
for pc in range(offset, offset + size):
if pc in self._instructions_cache:
self._instructions_cache.pop(pc)

def patch_asm(self, offset, asm):
data = Code.asm(asm)
self.patch_bytes(offset, data)

def next_instruction(self) -> Optional[Instruction]:
ins = self._instructions_cache.get(self.pc, None)
if ins:
self.pc += ins.instruction_size
return ins

op = self.get_op()
if op is None:
return None

ins_pc = self.pc
self.pc += 1
size = op.operand_size
opnd = int.from_bytes(self.code[self.pc : self.pc + size], "big")
self.pc += size
ins = Instruction(op, opnd, ins_pc)
self._instructions_cache[ins_pc] = ins
return ins

def get_instructions(self, force=True):
if self.instructions and not force:
return self.instructions

while True:
ins = self.next_instruction()
if ins is None:
break
self.instructions.append(ins)
return self.instructions

@property
def size(self):
return len(self.code)

@classmethod
def from_asm(self, asm: str):
lines = asm.strip().splitlines()
code = b""
for line in lines:
ops = line.split()
op = OpCode.from_mnemonic(ops[0])
if op.operand_size:
opnd = ops[1]
if opnd.startswith("0x"):
opnd = int(opnd, 16)
else:
opnd = int(opnd)
ins = Instruction(op, opnd)
else:
ins = Instruction(op)
code += ins.asm()
return Code(code)

@staticmethod
def asm(asm: str) -> bytes:
return Code.from_asm(asm).code

@staticmethod
def disasm(code: bytes) -> str:
asm = ""
for ins in Code(code).get_instructions():
asm += ins.disasm() + "\n"
return asm
5 changes: 5 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@
"ftm": [
"https://rpc.ankr.com/fantom",
"https://api.ftmscan.com/api?"
],
"metis": [
"https://andromeda.metis.io/?owner=1088",
"https://andromeda-explorer.metis.io/api?"
]

}
42 changes: 42 additions & 0 deletions console.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import json

from sigs import ERC20Signatures
from utils import get_4byte_sig
from code import Code
from opcodes import OpCode

class PethConsole(cmd.Cmd):

Expand All @@ -28,6 +31,45 @@ def onecmd(self, line):
print("Error: ", e)
return False # don't stop

def do_4byte(self, arg):
"""
4byte <hex_sig> : query text signature in 4byte database.
"""
if not arg:
print("4byte <hex_sig> :query text signature in 4byte database.")
return

sigs = get_4byte_sig(arg)
if sigs:
print('\n'.join(sigs))
else:
print("Not found in 4byte.directory.")

def do_abi4byte(self, arg):
"""
abi4byte <addr> : disassemble the code and print all signatures.
"""
addr = self.web3.toChecksumAddress(arg)
bytes_code = bytes(self.web3.eth.get_code(addr))
code = Code(bytes_code)

while True:
ins = code.next_instruction()

# Only search the first basic block.
if ins.op.is_jumpdest:
break

if ins.op is OpCode.PUSH4:
if ins.opnd == 0xffffffff:
continue

sig = hex(ins.opnd)
sigs = get_4byte_sig(sig)
sigs = sigs[::-1]
print(sig, ', '.join(sigs))


def do_balance(self, arg):
"""
balance <address> : Get the balance of address.
Expand Down
Loading

0 comments on commit acabe99

Please sign in to comment.