From f581588b52c68b1d610afe3597a7fd5e823d0d87 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 30 Nov 2018 18:00:01 +0000 Subject: [PATCH] Improve documentation (#8) - Improve Readme - Add library usage Improve API (#7) - Create command line arguments - Improve CFG module API Use logging (close #4) --- README.md | 50 ++++--------- evm_cfg_builder/__main__.py | 95 ++++++++++++------------ evm_cfg_builder/cfg/__init__.py | 60 ++++++++++++++-- evm_cfg_builder/cfg/basic_block.py | 20 ++++++ evm_cfg_builder/cfg/function.py | 49 +++++++++++-- examples/library.py | 12 ++++ examples/token-runtime.evm | 1 + examples/token.sol | 112 +++++++++++++++++++++++++++++ setup.py | 2 +- 9 files changed, 312 insertions(+), 89 deletions(-) create mode 100644 examples/library.py create mode 100644 examples/token-runtime.evm create mode 100644 examples/token.sol diff --git a/README.md b/README.md index 1716efa..c72ddcc 100644 --- a/README.md +++ b/README.md @@ -12,49 +12,29 @@ evm_cfg_builder is a work in progress, but it's already very useful today: `evm_cfg_builder` is a reliable foundation to build many possible program analysis tools for EVM. This library is covered by our standard bounty policy and we encourage contributions that address any known [issues](https://github.com/trailofbits/evm_cfg_builder/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc). Join us on the [Empire Hacking Slack](https://empireslacking.herokuapp.com) to discuss using or extending evm_cfg_builder. -## How to install +## Usage +### Command line +To export basic dissassembly information, run: ``` -pip install . +evm-cfg-builder mycontract.evm +``` +To export the CFG of each function (dot format), run: +``` +evm-cfg-builder mycontract.evm --export-dot my_dir ``` +dot files can be read using xdot. -## Example +### Library +See [examples/library.py](examples/library.py) for an example. -``` -$ evm-cfg-builder tests/fomo3d.evm -... +## How to install -dividendsOf(address), 7 #bbs , view -name(), 16 #bbs , view -calculateTokensReceived(uint256), 29 #bbs , view -totalSupply(), 5 #bbs , view -calculateEthereumReceived(uint256), 26 #bbs , view -onlyAmbassadors(), 5 #bbs , view -decimals(), 5 #bbs , view -administrators(bytes32), 5 #bbs , view -withdraw(), 20 #bbs -sellPrice(), 27 #bbs , view -stakingRequirement(), 5 #bbs , view -myDividends(bool), 13 #bbs , view -totalEthereumBalance(), 5 #bbs , view -balanceOf(address), 5 #bbs , view -setStakingRequirement(uint256), 7 #bbs -buyPrice(), 30 #bbs , view -setAdministrator(bytes32,bool), 7 #bbs -Hourglass(), 5 #bbs , view -myTokens(), 7 #bbs , view -symbol(), 16 #bbs , view -disableInitialStage(), 7 #bbs -transfer(address,uint256), 48 #bbs , view -setSymbol(string), 22 #bbs -setName(string), 22 #bbs -sell(uint256), 42 #bbs -exit(), 63 #bbs -buy(address), 71 #bbs , payable -reinvest(), 86 #bbs +``` +git clone https://github.com/trailofbits/evm_cfg_builder +pip install . ``` -`test_.dot` files will be generated. ## Requirements diff --git a/evm_cfg_builder/__main__.py b/evm_cfg_builder/__main__.py index feb8953..9782eb8 100644 --- a/evm_cfg_builder/__main__.py +++ b/evm_cfg_builder/__main__.py @@ -1,58 +1,65 @@ -import binascii import re import sys - +import argparse +import logging +import os +from pkg_resources import require from pyevmasm import disassemble_all from .cfg import CFG -from .cfg.function import Function -from .known_hashes import known_hashes -from .value_set_analysis import StackValueAnalysis -import time +logging.basicConfig() +logger = logging.getLogger("evm-cfg-builder") -def get_info(cfg): - cfg.add_function(Function(Function.DISPATCHER_ID, 0, cfg.basic_blocks[0])) +def output_to_dot(d, filename, functions): + if not os.path.exists(d): + os.makedirs(d) + filename = os.path.basename(filename) + filename = os.path.join(d, filename+ '_') + for function in functions: + function.output_to_dot(filename) - for function in cfg.functions: - if function.hash_id in known_hashes: - function.name = known_hashes[function.hash_id] +def parse_args(): + parser = argparse.ArgumentParser(description='evm-cfg-builder', + usage="evm-cfg-builder contract.evm [flag]") + + parser.add_argument('filename', + help='contract.evm') + + parser.add_argument('--export-dot', + help='Export the functions to .dot files in the directory', + action='store', + dest='dot_directory', + default='') + + parser.add_argument('--version', + help='displays the current version', + version=require('evm-cfg-builder')[0].version, + action='version') - for function in cfg.functions: - vsa = StackValueAnalysis( - cfg, - function.entry, - function.hash_id - ) - print(function.name) - start = time.time() - bbs = vsa.analyze() - print(int(time.time() - start)) - - function.basic_blocks = [cfg.basic_blocks[bb] for bb in bbs] - - function.check_payable() - function.check_view() - function.check_pure() - -def output_to_dot(functions): - for function in functions: - function.output_to_dot('test_') + + if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + args = parser.parse_args() + return args def main(): - filename = sys.argv[1] - - with open(filename) as f: - bytecode = f.read().replace('\n','') - cfg = CFG(binascii.unhexlify(bytecode)) - cfg.remove_metadata() - cfg.compute_basic_blocks() - cfg.compute_functions(cfg.basic_blocks[0], True) - get_info(cfg) - print('End of analysis') - for function in cfg.functions: - print(function) - output_to_dot(cfg.functions) + + l = logging.getLogger('evm-cfg-builder') + l.setLevel(logging.INFO) + args = parse_args() + + with open(args.filename) as f: + bytecode = f.read().replace('\n', '') + + cfg = CFG(bytecode) + + for function in cfg.functions: + logger.info(function) + + if args.dot_directory: + output_to_dot(args.dot_directory, args.filename, cfg.functions) if __name__ == '__main__': diff --git a/evm_cfg_builder/cfg/__init__.py b/evm_cfg_builder/cfg/__init__.py index f60ed2c..0a1b866 100644 --- a/evm_cfg_builder/cfg/__init__.py +++ b/evm_cfg_builder/cfg/__init__.py @@ -1,5 +1,9 @@ +import binascii from . import basic_block -from . import function +from .function import Function + +from ..known_hashes import known_hashes +from ..value_set_analysis import StackValueAnalysis import re from pyevmasm import disassemble_all @@ -29,12 +33,18 @@ def __update(self, new_dict): raise NotImplementedError() class CFG(object): - def __init__(self, bytecode=None, instructions=None, basic_blocks=None, functions=None): + + def __init__(self, bytecode=None, instructions=None, basic_blocks=None, functions=None, remove_metadata=True, analyze=True): self.__functions = list() self.__basic_blocks = dict() self.__instructions = dict() if bytecode is not None: + if isinstance(bytecode, str): + if bytecode.startswith('0x'): + bytecode = bytecode[2:] + bytecode = bytecode.replace('\n', '') + bytecode = binascii.unhexlify(bytecode) self.__bytecode = bytes(bytecode) if instructions is not None: self.__instructions = instructions @@ -43,6 +53,37 @@ def __init__(self, bytecode=None, instructions=None, basic_blocks=None, function if functions is not None: self.__functions = functions + if remove_metadata: + self.remove_metadata() + if analyze: + self.analyze() + + def analyze(self): + self.compute_basic_blocks() + self.compute_functions(self.basic_blocks[0], True) + self.add_function(Function(Function.DISPATCHER_ID, 0, self.basic_blocks[0], self)) + + for function in self.functions: + if function.hash_id in known_hashes: + function.name = known_hashes[function.hash_id] + + for function in self.functions: + vsa = StackValueAnalysis( + self, + function.entry, + function.hash_id + ) + bbs = vsa.analyze() + + function.basic_blocks = [self.basic_blocks[bb] for bb in bbs] + + if function.hash_id != Function.DISPATCHER_ID: + function.check_payable() + function.check_view() + function.check_pure() + + + @property def bytecode(self): return self.__bytecode @@ -57,7 +98,7 @@ def clear(self): self.__basic_blocks = dict() self.__instructions = dict() self.__bytecode = dict() - + def remove_metadata(self): ''' Init bytecode contains metadata that needs to be removed @@ -125,7 +166,8 @@ def compute_functions(self, block, is_entry_block=False): new_function = function.Function( function_hash, function_start, - self.__basic_blocks[function_start] + self.__basic_blocks[function_start], + self ) self.__functions.append(new_function) @@ -142,7 +184,7 @@ def compute_functions(self, block, is_entry_block=False): self.compute_functions(false_branch) def add_function(self, func): - assert isinstance(func, function.Function) + assert isinstance(func, Function) self.__functions.append(func) def compute_simple_edges(self, key): @@ -184,6 +226,14 @@ def compute_reachability(self, entry_point, key): if key in bb.fathers.keys(): del bb.fathers[key] + def export_basic_blocks(self): + return [bb.export() for bb in self.basic_blocks.values()] + + def export_functions(self): + return [{'name' : function.name, + 'basic_blocks' : function.export_bbs()} + for function in self.functions] + def is_jump_to_function(block): ''' Heuristic: diff --git a/evm_cfg_builder/cfg/basic_block.py b/evm_cfg_builder/cfg/basic_block.py index d9bb095..0d9afef 100644 --- a/evm_cfg_builder/cfg/basic_block.py +++ b/evm_cfg_builder/cfg/basic_block.py @@ -58,3 +58,23 @@ def false_branch(self, key): sons = [bb for bb in self.sons[key] if bb.start.pc == (self.end.pc+1)] assert(len(sons) == 1) return sons[key][0] + + def export(self): + sons = self.sons.values() + sons = [son for sublist in sons for son in sublist] + fathers = self.fathers.values() + fathers = [father for sublist in fathers for father in sublist] + return {'pc_start': self.start.pc, + 'pc_end': self.end.pc, + 'instructions': self.instructions, + 'sons': sons, + 'fathers': fathers} + + def export_from_function(self, key): + sons = self.sons.get(key, []) + fathers = self.fathers.get(key, []) + return {'pc_start': self.start.pc, + 'pc_end': self.end.pc, + 'instructions': self.instructions, + 'sons': sons, + 'fathers': fathers} diff --git a/evm_cfg_builder/cfg/function.py b/evm_cfg_builder/cfg/function.py index 8be378d..7875247 100644 --- a/evm_cfg_builder/cfg/function.py +++ b/evm_cfg_builder/cfg/function.py @@ -1,9 +1,12 @@ +import logging +logger = logging.getLogger("evm-cfg-builder") + class Function(object): DISPATCHER_ID = -2 FALLBACK_ID = -1 - def __init__(self, hash_id, start_addr, entry_basic_block): + def __init__(self, hash_id, start_addr, entry_basic_block, cfg): self._hash_id = hash_id self._start_addr = start_addr self._entry = entry_basic_block @@ -15,6 +18,11 @@ def __init__(self, hash_id, start_addr, entry_basic_block): self._name = hex(hash_id) self._basic_blocks = [] self._attributes = [] + self._cfg = cfg + + @property + def start_addr(self): + return self._start_addr @property def hash_id(self): @@ -124,13 +132,17 @@ def check_pure(self): self.add_attributes('pure') def __str__(self): - attrs = '' + attrs = '' if self.attributes: attrs = ", " + ",".join(self.attributes) return '{}, {} #bbs {}'.format(self.name, len(self.basic_blocks), attrs) def output_to_dot(self, base_filename): + if self.key == Function.DISPATCHER_ID: + self.output_dispatcher_to_dot(base_filename) + return + with open('{}{}.dot'.format(base_filename, self.name), 'w') as f: f.write('digraph{\n') for basic_block in self.basic_blocks: @@ -145,7 +157,36 @@ def output_to_dot(self, base_filename): f.write('{} -> {}\n'.format(basic_block.start.pc, son.start.pc)) elif basic_block.ends_with_jump_or_jumpi(): - print('Missing branches {}:{}'.format(self.name, - hex(basic_block.end.pc))) + logger.error('Missing branches {}:{}'.format(self.name, + hex(basic_block.end.pc))) f.write('\n}') + + def output_dispatcher_to_dot(self, base_filename): + + destinations = {function.hash_id : function.start_addr for function in self._cfg.functions} + + with open('{}{}.dot'.format(base_filename, self.name), 'w') as f: + f.write('digraph{\n') + for basic_block in self.basic_blocks: + instructions = ['{}:{}'.format(hex(ins.pc), + str(ins)) for ins in basic_block.instructions] + instructions = '\n'.join(instructions) + + f.write('{}[label="{}"]\n'.format(basic_block.start.pc, instructions)) + + if self.key in basic_block.sons: + for son in basic_block.sons[self.key]: + f.write('{} -> {}\n'.format(basic_block.start.pc, son.start.pc)) + + elif basic_block.ends_with_jump_or_jumpi(): + logger.error('Missing branches {}:{}'.format(self.name, + hex(basic_block.end.pc))) + for function in self._cfg.functions: + if function != self: + f.write('{}[label="Call {}"]\n'.format(function.start_addr, function.name)) + + f.write('\n}') + + def export_bbs(self): + return [bb.export_from_function(self.key) for bb in self.basic_blocks] diff --git a/examples/library.py b/examples/library.py new file mode 100644 index 0000000..4d97463 --- /dev/null +++ b/examples/library.py @@ -0,0 +1,12 @@ +import pprint +from evm_cfg_builder.cfg import CFG + +with open('token-runtime.evm') as f: + runtime_bytecode = f.read() + +cfg = CFG(runtime_bytecode) + +pprint.pprint(cfg.export_basic_blocks()) + +# You can also export the functions information (which includes the basic blocks) +#pprint.pprint(cfg.export_functions()) diff --git a/examples/token-runtime.evm b/examples/token-runtime.evm new file mode 100644 index 0000000..5f0c5f7 --- /dev/null +++ b/examples/token-runtime.evm @@ -0,0 +1 @@ +0x6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101a957806323b872dd146101d457806327e235e314610259578063313ce567146102b05780635c658165146102e157806370a082311461035857806395d89b41146103af578063a9059cbb1461043f578063dd62ed3e146104a4575b600080fd5b3480156100c057600080fd5b506100c961051b565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061018f600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105b9565b604051808215151515815260200191505060405180910390f35b3480156101b557600080fd5b506101be6106ab565b6040518082815260200191505060405180910390f35b3480156101e057600080fd5b5061023f600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106b1565b604051808215151515815260200191505060405180910390f35b34801561026557600080fd5b5061029a600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061094b565b6040518082815260200191505060405180910390f35b3480156102bc57600080fd5b506102c5610963565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102ed57600080fd5b50610342600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610976565b6040518082815260200191505060405180910390f35b34801561036457600080fd5b50610399600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061099b565b6040518082815260200191505060405180910390f35b3480156103bb57600080fd5b506103c46109e4565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104045780820151818401526020810190506103e9565b50505050905090810190601f1680156104315780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561044b57600080fd5b5061048a600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a82565b604051808215151515815260200191505060405180910390f35b3480156104b057600080fd5b50610505600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610bdb565b6040518082815260200191505060405180910390f35b60038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105b15780601f10610586576101008083540402835291602001916105b1565b820191906000526020600020905b81548152906001019060200180831161059457829003601f168201915b505050505081565b600081600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60005481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101580156107825750828110155b151561078d57600080fd5b82600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555082600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156108da5782600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825403925050819055505b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150509392505050565b60016020528060005260406000206000915090505481565b600460009054906101000a900460ff1681565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60058054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610a7a5780601f10610a4f57610100808354040283529160200191610a7a565b820191906000526020600020905b815481529060010190602001808311610a5d57829003601f168201915b505050505081565b600081600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410151515610ad257600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a36001905092915050565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050929150505600a165627a7a7230582076ce5edca0cb79a34f44093c5ee1fcd5fadde8c358ba33d53c447da4f1d4e7ef0029 diff --git a/examples/token.sol b/examples/token.sol new file mode 100644 index 0000000..918b6eb --- /dev/null +++ b/examples/token.sol @@ -0,0 +1,112 @@ +// from https://github.com/ConsenSys/Tokens/blob/fdf687c69d998266a95f15216b1955a4965a0a6d/contracts/eip20/ + +pragma solidity ^0.4.25; + +contract EIP20Interface { + /* This is a slight change to the ERC20 base standard. + function totalSupply() constant returns (uint256 supply); + is replaced with: + uint256 public totalSupply; + This automatically creates a getter function for the totalSupply. + This is moved to the base contract since public getter functions are not + currently recognised as an implementation of the matching abstract + function by the compiler. + */ + /// total amount of tokens + uint256 public totalSupply; + + /// @param _owner The address from which the balance will be retrieved + /// @return The balance + function balanceOf(address _owner) public view returns (uint256 balance); + + /// @notice send `_value` token to `_to` from `msg.sender` + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transfer(address _to, uint256 _value) public returns (bool success); + + /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` + /// @param _from The address of the sender + /// @param _to The address of the recipient + /// @param _value The amount of token to be transferred + /// @return Whether the transfer was successful or not + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); + + /// @notice `msg.sender` approves `_spender` to spend `_value` tokens + /// @param _spender The address of the account able to transfer the tokens + /// @param _value The amount of tokens to be approved for transfer + /// @return Whether the approval was successful or not + function approve(address _spender, uint256 _value) public returns (bool success); + + /// @param _owner The address of the account owning tokens + /// @param _spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address _owner, address _spender) public view returns (uint256 remaining); + + // solhint-disable-next-line no-simple-event-func-name + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); +} + +contract EIP20 is EIP20Interface { + + uint256 constant private MAX_UINT256 = 2**256 - 1; + mapping (address => uint256) public balances; + mapping (address => mapping (address => uint256)) public allowed; + /* + NOTE: + The following variables are OPTIONAL vanities. One does not have to include them. + They allow one to customise the token contract & in no way influences the core functionality. + Some wallets/interfaces might not even bother to look at this information. + */ + string public name; //fancy name: eg Simon Bucks + uint8 public decimals; //How many decimals to show. + string public symbol; //An identifier: eg SBX + + function EIP20( + uint256 _initialAmount, + string _tokenName, + uint8 _decimalUnits, + string _tokenSymbol + ) public { + balances[msg.sender] = _initialAmount; // Give the creator all initial tokens + totalSupply = _initialAmount; // Update total supply + name = _tokenName; // Set the name for display purposes + decimals = _decimalUnits; // Amount of decimals for display purposes + symbol = _tokenSymbol; // Set the symbol for display purposes + } + + function transfer(address _to, uint256 _value) public returns (bool success) { + require(balances[msg.sender] >= _value); + balances[msg.sender] -= _value; + balances[_to] += _value; + emit Transfer(msg.sender, _to, _value); //solhint-disable-line indent, no-unused-vars + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + uint256 allowance = allowed[_from][msg.sender]; + require(balances[_from] >= _value && allowance >= _value); + balances[_to] += _value; + balances[_from] -= _value; + if (allowance < MAX_UINT256) { + allowed[_from][msg.sender] -= _value; + } + emit Transfer(_from, _to, _value); //solhint-disable-line indent, no-unused-vars + return true; + } + + function balanceOf(address _owner) public view returns (uint256 balance) { + return balances[_owner]; + } + + function approve(address _spender, uint256 _value) public returns (bool success) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); //solhint-disable-line indent, no-unused-vars + return true; + } + + function allowance(address _owner, address _spender) public view returns (uint256 remaining) { + return allowed[_owner][_spender]; + } +} diff --git a/setup.py b/setup.py index 5193a08..4d70326 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ description='EVM cfg builder written in Python 3.', url='https://github.com/trailofbits/evm_cfg_builder', author='Trail of Bits', - version='0.0.0', + version='0.1.0', packages=find_packages(), python_requires='>=3.6', install_requires=['pyevmasm>=0.1.1'],