From d3ad937462baf1c7f906c6b611742abb56583776 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Tue, 10 Jul 2018 18:03:25 +0200 Subject: [PATCH 01/23] Load all contracts from file --- mythril/ether/soliditycontract.py | 8 ++++++++ mythril/mythril.py | 15 +++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/mythril/ether/soliditycontract.py b/mythril/ether/soliditycontract.py index 919b3d30..698d3627 100644 --- a/mythril/ether/soliditycontract.py +++ b/mythril/ether/soliditycontract.py @@ -27,6 +27,14 @@ class SourceCodeInfo: self.code = code +def get_contracts_from_file(input_file, solc_args=None): + data = get_solc_json(input_file, solc_args=solc_args) + for key, contract in data['contracts'].items(): + filename, name = key.split(":") + if filename == input_file and len(contract['bin-runtime']): + yield SolidityContract(input_file, name, solc_args) + + class SolidityContract(ETHContract): def __init__(self, input_file, name=None, solc_args=None): diff --git a/mythril/mythril.py b/mythril/mythril.py index 0a85366b..1119d951 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -19,7 +19,7 @@ import platform from mythril.ether import util from mythril.ether.ethcontract import ETHContract -from mythril.ether.soliditycontract import SolidityContract +from mythril.ether.soliditycontract import SolidityContract, get_contracts_from_file from mythril.rpc.client import EthJsonRpc from mythril.ipc.client import EthIpc from mythril.rpc.exceptions import ConnectionError @@ -284,18 +284,21 @@ class Mythril(object): # import signatures from solidity source with open(file, encoding="utf-8") as f: self.sigs.import_from_solidity_source(f.read()) + if contract_name is not None: + contract = SolidityContract(file, contract_name, solc_args=self.solc_args) + self.contracts.append(contract) + contracts.append(contract) + else: + for contract in get_contracts_from_file(file, solc_args=self.solc_args): + self.contracts.append(contract) + contracts.append(contract) - contract = SolidityContract(file, contract_name, solc_args=self.solc_args) - logging.info("Analyzing contract %s:%s" % (file, contract.name)) except FileNotFoundError: raise CriticalError("Input file not found: " + file) except CompilerError as e: raise CriticalError(e) except NoContractFoundError: logging.info("The file " + file + " does not contain a compilable contract.") - else: - self.contracts.append(contract) - contracts.append(contract) # Save updated function signatures self.sigs.write() # dump signatures to disk (previously opened file or default location) From 6b65408c09227f61b81479f8a399aa5716d4936c Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 16 Jul 2018 14:49:31 +0200 Subject: [PATCH 02/23] Implement call fixes --- mythril/laser/ethereum/instructions.py | 35 +++++++++++++++----------- mythril/laser/ethereum/state.py | 6 ++--- mythril/laser/ethereum/svm.py | 5 +++- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 1cabdb97..1f1eb584 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -623,8 +623,10 @@ class Instruction: @instruction def mstore_(self, global_state): state = global_state.mstate - - op0, value = state.stack.pop(), state.stack.pop() + try: + op0, value = state.stack.pop(), state.stack.pop() + except IndexError: + raise StackUnderflowException() try: mstart = util.get_concrete_int(op0) @@ -840,17 +842,17 @@ class Instruction: except AttributeError: logging.debug("Return with symbolic length or offset. Not supported") - #TODO: return 1 return_value = BitVec("retval_" + global_state.environment.active_function_name, 256) - state.stack.append(return_value) - if not global_state.call_stack: return [] - global_state.mstate.pc = global_state.call_stack.pop() + new_global_state = deepcopy(global_state.call_stack.pop()) + new_global_state.node = global_state.node + new_global_state.mstate.stack.append(return_value) + # TODO: copy memory - return [global_state] + return [new_global_state] @instruction def suicide_(self, global_state): @@ -870,12 +872,12 @@ class Instruction: @instruction def stop_(self, global_state): - state = global_state.mstate - state.stack.append(BitVecVal(0, 256)) if len(global_state.call_stack) is 0: return [] - global_state.mstate.pc = global_state.call_stack.pop() - return [global_state] + new_global_state = deepcopy(global_state.call_stack.pop()) + new_global_state.node = global_state.node + new_global_state.mstate.stack.append(BitVecVal(0, 256)) + return [new_global_state] @instruction def call_(self, global_state): @@ -928,7 +930,6 @@ class Instruction: global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] - global_state.call_stack.append(instr['address']) callee_environment = Environment(callee_account, BitVecVal(int(environment.active_account.address, 16), 256), call_data, @@ -937,9 +938,11 @@ class Instruction: environment.origin, calldata_type=call_data_type) new_global_state = GlobalState(global_state.accounts, callee_environment, global_state.node, MachineState(gas)) + new_global_state.call_stack.append(global_state) + new_global_state.mstate.pc = -1 new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) - return [global_state] + return [new_global_state] @instruction def callcode_(self, global_state): @@ -955,7 +958,6 @@ class Instruction: global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] - global_state.call_stack.append(instr['address']) environment = deepcopy(environment) @@ -964,6 +966,8 @@ class Instruction: environment.calldata = call_data new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas)) + new_global_state.call_stack.append(global_state) + new_global_state.mstate.pc = -1 new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) @@ -983,7 +987,6 @@ class Instruction: global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] - global_state.call_stack.append(instr['address']) environment = deepcopy(environment) environment = deepcopy(environment) @@ -992,6 +995,8 @@ class Instruction: environment.calldata = call_data new_global_state = GlobalState(global_state.accounts, environment, global_state.node, MachineState(gas)) + new_global_state.call_stack.append(global_state) + new_global_state.mstate.pc = -1 new_global_state.mstate.depth = global_state.mstate.depth + 1 new_global_state.mstate.constraints = copy(global_state.mstate.constraints) diff --git a/mythril/laser/ethereum/state.py b/mythril/laser/ethereum/state.py index 932eae65..206bd084 100644 --- a/mythril/laser/ethereum/state.py +++ b/mythril/laser/ethereum/state.py @@ -137,12 +137,12 @@ class GlobalState: self.op_code = "" - def __copy__(self): - accounts = copy(self.accounts) + accounts = self.accounts environment = copy(self.environment) mstate = deepcopy(self.mstate) - return GlobalState(accounts, environment, self.node, mstate) + call_stack = copy(self.call_stack) + return GlobalState(accounts, environment, self.node, mstate, call_stack=call_stack) #TODO: remove this, as two instructions are confusing def get_current_instruction(self): diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index c7dc2927..f693cd9e 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -88,7 +88,10 @@ class LaserEVM: def execute_state(self, global_state): instructions = global_state.environment.code.instruction_list op_code = instructions[global_state.mstate.pc]['opcode'] - self.instructions_covered[global_state.mstate.pc] = True + + # Only count coverage for the main contract + if len(global_state.call_stack) == 0: + self.instructions_covered[global_state.mstate.pc] = True self._execute_pre_hook(op_code, global_state) new_global_states = Instruction(op_code, self.dynamic_loader).evaluate(global_state) From d24740b0ae65bec54801d86ff31dbbdb17709f7f Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 16 Jul 2018 14:56:04 +0200 Subject: [PATCH 03/23] the call instruction adds the return value to the stack --- mythril/laser/ethereum/instructions.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 1f1eb584..532ceebb 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -849,7 +849,6 @@ class Instruction: new_global_state = deepcopy(global_state.call_stack.pop()) new_global_state.node = global_state.node - new_global_state.mstate.stack.append(return_value) # TODO: copy memory return [new_global_state] @@ -876,13 +875,13 @@ class Instruction: return [] new_global_state = deepcopy(global_state.call_stack.pop()) new_global_state.node = global_state.node - new_global_state.mstate.stack.append(BitVecVal(0, 256)) return [new_global_state] @instruction def call_(self, global_state): instr = global_state.get_current_instruction() environment = global_state.environment + global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) try: callee_address, callee_account, call_data, value, call_data_type, gas, memory_out_offset, memory_out_size = get_call_parameters(global_state, self.dynamic_loader, True) @@ -891,14 +890,12 @@ class Instruction: "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(e) ) # TODO: decide what to do in this case - global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] if 0 < int(callee_address, 16) < 5: logging.info("Native contract called: " + callee_address) if call_data == [] and call_data_type == CalldataType.SYMBOLIC: logging.debug("CALL with symbolic data not supported") - global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] try: @@ -906,7 +903,6 @@ class Instruction: mem_out_sz = memory_out_size.as_long() except AttributeError: logging.debug("CALL with symbolic start or offset not supported") - global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] global_state.mstate.mem_extend(mem_out_start, mem_out_sz) @@ -920,14 +916,12 @@ class Instruction: "(" + str(call_data) + ")", 256) - global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] for i in range(min(len(data), mem_out_sz)): # If more data is used then it's chopped off global_state.mstate.memory[mem_out_start + i] = data[i] # TODO: maybe use BitVec here constrained to 1 - global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] callee_environment = Environment(callee_account, From 7782ef20f537f01259755bac5c4e895050b859a7 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 16 Jul 2018 15:23:38 +0200 Subject: [PATCH 04/23] Add retval after pops --- mythril/laser/ethereum/instructions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 532ceebb..d9556f33 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -881,7 +881,6 @@ class Instruction: def call_(self, global_state): instr = global_state.get_current_instruction() environment = global_state.environment - global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) try: callee_address, callee_account, call_data, value, call_data_type, gas, memory_out_offset, memory_out_size = get_call_parameters(global_state, self.dynamic_loader, True) @@ -891,6 +890,7 @@ class Instruction: ) # TODO: decide what to do in this case return [global_state] + global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) if 0 < int(callee_address, 16) < 5: logging.info("Native contract called: " + callee_address) From 66cf95e298922d8d6f80beeebc844b36a1c6f7c9 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 16 Jul 2018 15:42:14 +0200 Subject: [PATCH 05/23] Add retvals to global states --- mythril/laser/ethereum/instructions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index d9556f33..87b71617 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -889,6 +889,7 @@ class Instruction: "Could not determine required parameters for call, putting fresh symbol on the stack. \n{}".format(e) ) # TODO: decide what to do in this case + global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) @@ -915,7 +916,6 @@ class Instruction: global_state.mstate.memory[mem_out_start+i] = BitVec(contract_list[call_address_int - 1]+ "(" + str(call_data) + ")", 256) - return [global_state] for i in range(min(len(data), mem_out_sz)): # If more data is used then it's chopped off @@ -952,6 +952,7 @@ class Instruction: global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] + global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) environment = deepcopy(environment) @@ -981,6 +982,7 @@ class Instruction: global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) return [global_state] + global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) environment = deepcopy(environment) environment = deepcopy(environment) From 37a5995ee3ba768df37f6c953b8c37488277850e Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 16 Jul 2018 21:38:45 +0200 Subject: [PATCH 06/23] Only taint check the current contract --- mythril/laser/ethereum/svm.py | 9 ++++++--- mythril/laser/ethereum/taint_analysis.py | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index f693cd9e..7760c831 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -129,9 +129,12 @@ class LaserEVM: if edge_type == JumpType.RETURN: new_node.flags |= NodeFlags.CALL_RETURN elif edge_type == JumpType.CALL: - if 'retval' in str(state.mstate.stack[-1]): - new_node.flags |= NodeFlags.CALL_RETURN - else: + try: + if 'retval' in str(state.mstate.stack[-1]): + new_node.flags |= NodeFlags.CALL_RETURN + else: + new_node.flags |= NodeFlags.FUNC_ENTRY + except IndexError: new_node.flags |= NodeFlags.FUNC_ENTRY address = state.environment.code.instruction_list[state.mstate.pc - 1]['address'] diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index e370944d..d601d31e 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -101,16 +101,29 @@ class TaintRunner: # List of (Node, TaintRecord, index) current_nodes = [(node, init_record, state_index)] + contract_name = node.contract_name for node, record, index in current_nodes: records = TaintRunner.execute_node(node, record, index) + result.add_records(records) - children = [statespace.nodes[edge.node_to] for edge in statespace.edges if edge.node_from == node.uid] + children = TaintRunner.children(node, statespace, contract_name) for child in children: - current_nodes.append((child, records[-1], 0)) + current_nodes.append((child, records[-1], 0)) return result + @staticmethod + def children(node, statespace, contract_name): + direct_children = [statespace.nodes[edge.node_to] for edge in statespace.edges if edge.node_from == node.uid] + children = [] + for child in direct_children: + if child.contract_name == contract_name: + children.append(child) + else: + children += TaintRunner.children(child, statespace, contract_name) + return children + @staticmethod def execute_node(node, last_record, state_index=0): """ From b3897fa0b945cea47a7a91182fce9a737406541a Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Mon, 16 Jul 2018 21:48:51 +0200 Subject: [PATCH 07/23] Check on environment --- mythril/laser/ethereum/instructions.py | 1 - mythril/laser/ethereum/taint_analysis.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 87b71617..ab09c293 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -984,7 +984,6 @@ class Instruction: global_state.mstate.stack.append(BitVec("retval_" + str(instr['address']), 256)) - environment = deepcopy(environment) environment = deepcopy(environment) environment.code = callee_account.code diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index d601d31e..bd53b08c 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -101,27 +101,27 @@ class TaintRunner: # List of (Node, TaintRecord, index) current_nodes = [(node, init_record, state_index)] - contract_name = node.contract_name + environment = node.states[0].environment for node, record, index in current_nodes: records = TaintRunner.execute_node(node, record, index) result.add_records(records) - children = TaintRunner.children(node, statespace, contract_name) + children = TaintRunner.children(node, statespace, environment) for child in children: current_nodes.append((child, records[-1], 0)) return result @staticmethod - def children(node, statespace, contract_name): + def children(node, statespace, environment): direct_children = [statespace.nodes[edge.node_to] for edge in statespace.edges if edge.node_from == node.uid] children = [] for child in direct_children: - if child.contract_name == contract_name: + if child.states[0].environment == environment: children.append(child) else: - children += TaintRunner.children(child, statespace, contract_name) + children += TaintRunner.children(child, statespace, environment) return children @staticmethod From 621c8b95dfc55e9f253a5f137e3fbab81e0049b6 Mon Sep 17 00:00:00 2001 From: Joran Honig Date: Tue, 17 Jul 2018 11:39:43 +0200 Subject: [PATCH 08/23] Check for length bin runtime --- mythril/ether/soliditycontract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/ether/soliditycontract.py b/mythril/ether/soliditycontract.py index 919b3d30..aca65f95 100644 --- a/mythril/ether/soliditycontract.py +++ b/mythril/ether/soliditycontract.py @@ -48,7 +48,7 @@ class SolidityContract(ETHContract): for key, contract in data['contracts'].items(): filename, _name = key.split(":") - if filename == input_file and name == _name: + if filename == input_file and name == _name and len(contract['bin-runtime']): name = name code = contract['bin-runtime'] creation_code = contract['bin'] From 2738b4230469cbafae8b7b0a86b9d53bd90ff7fe Mon Sep 17 00:00:00 2001 From: p0n1 Date: Tue, 17 Jul 2018 23:32:09 +0800 Subject: [PATCH 09/23] mythril & cli: fix missing strategy param --- mythril/interfaces/cli.py | 4 ++-- mythril/mythril.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 6210060c..b9a910e0 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -185,7 +185,7 @@ def main(): exit_with_error(args.outform, "input files do not contain any valid contracts") if args.graph: - html = mythril.graph_html(mythril.contracts[0], address=address, + html = mythril.graph_html(strategy=args.strategy, contract=mythril.contracts[0], address=address, enable_physics=args.enable_physics, phrackify=args.phrack, max_depth=args.max_depth) @@ -212,7 +212,7 @@ def main(): if not mythril.contracts: exit_with_error(args.outform, "input files do not contain any valid contracts") - statespace = mythril.dump_statespace(mythril.contracts[0], address=address, max_depth=args.max_depth) + statespace = mythril.dump_statespace(strategy=args.strategy, contract=mythril.contracts[0], address=address, max_depth=args.max_depth) try: with open(args.statespace_json, "w") as f: diff --git a/mythril/mythril.py b/mythril/mythril.py index 3534eab0..831d8c5f 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -303,16 +303,16 @@ class Mythril(object): return address, contracts - def dump_statespace(self, contract, address=None, max_depth=12): + def dump_statespace(self, strategy, contract, address=None, max_depth=12): - sym = SymExecWrapper(contract, address, + sym = SymExecWrapper(contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth) return get_serializable_statespace(sym) - def graph_html(self, contract, address, max_depth=12, enable_physics=False, phrackify=False): - sym = SymExecWrapper(contract, address, + def graph_html(self, strategy, contract, address, max_depth=12, enable_physics=False, phrackify=False): + sym = SymExecWrapper(contract, address, strategy, dynloader=DynLoader(self.eth) if self.dynld else None, max_depth=max_depth) return generate_graph(sym, physics=enable_physics, phrackify=phrackify) From 42efe5167d1eef4485f58864b1be68ab174b76ee Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Wed, 18 Jul 2018 23:01:15 +0530 Subject: [PATCH 10/23] v0.18.9 Update version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a9ed25da..ec606d3e 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ import os # Package version (vX.Y.Z). It must match git tag being used for CircleCI # deployment; otherwise the build will failed. -VERSION = "v0.18.8" +VERSION = "v0.18.9" class VerifyVersionCommand(install): From 897b0a66f6cafdaeaec882d80b68776866149702 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Thu, 19 Jul 2018 00:34:52 +0200 Subject: [PATCH 11/23] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cf3ed0e..09c4fe55 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup) ## Usage -Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). +Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). For support, visit the [Gitter channel](https://gitter.im/ConsenSys/mythril) or [Telegram group](https://t.me/mythril_tool). ## Publications and Videos From f93ca39512a6f7945313be3c2570ece86b54a5cf Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Thu, 19 Jul 2018 13:04:54 +0200 Subject: [PATCH 12/23] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09c4fe55..2d70ec89 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup) ## Usage -Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). For support, visit the [Gitter channel](https://gitter.im/ConsenSys/mythril) or [Telegram group](https://t.me/mythril_tool). +Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). For support, visit the [Gitter channel](https://gitter.im/ConsenSys/mythril) or [Telegram group](https://t.me/joinchat/GFWubxKo_G6eB7wI_kZ-LA). ## Publications and Videos From 8c750905f1df390400639ee6fd27bc6c0a1694f4 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Thu, 19 Jul 2018 13:19:02 +0200 Subject: [PATCH 13/23] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d70ec89..09c4fe55 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ See the [Wiki](https://github.com/ConsenSys/mythril/wiki/Installation-and-Setup) ## Usage -Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). For support, visit the [Gitter channel](https://gitter.im/ConsenSys/mythril) or [Telegram group](https://t.me/joinchat/GFWubxKo_G6eB7wI_kZ-LA). +Documentation has moved to the [Wiki page](https://github.com/ConsenSys/mythril/wiki). For support, visit the [Gitter channel](https://gitter.im/ConsenSys/mythril) or [Telegram group](https://t.me/mythril_tool). ## Publications and Videos From 69393d6880907b6c3501043be3a67f674b6a2cb9 Mon Sep 17 00:00:00 2001 From: Konrad Weiss Date: Thu, 19 Jul 2018 15:13:48 +0200 Subject: [PATCH 14/23] Addin _ to codecopy function Missing _ will not find the appropriate function when handling the codecopy instruction --- mythril/laser/ethereum/instructions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index ab09c293..2db3adf7 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -515,7 +515,7 @@ class Instruction: return [global_state] @instruction - def codecopy(self, global_state): + def codecopy_(self, global_state): # FIXME: not implemented state = global_state.mstate start, s1, size = state.stack.pop(), state.stack.pop(), state.stack.pop() From 23bf26299bcffb8b38bfe2961ba5aac0185a2b97 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Sat, 21 Jul 2018 22:02:40 +0200 Subject: [PATCH 15/23] Formatting, remove unused imports & function --- mythril/disassembler/disassembly.py | 4 ++-- mythril/ether/ethcontract.py | 13 ------------- mythril/leveldb/client.py | 11 ++++++----- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index a20d4ffe..9f8aff89 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -1,4 +1,4 @@ -from mythril.ether import asm,util +from mythril.ether import asm, util from mythril.support.signatures import SignatureDb import logging @@ -38,7 +38,7 @@ class Disassembly(object): func_name = "_function_" + func_hash try: - offset = self.instruction_list[i+2]['argument'] + offset = self.instruction_list[i + 2]['argument'] jump_target = int(offset, 16) self.func_to_addr[func_name] = jump_target diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index 197376b0..7c012f25 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -70,16 +70,3 @@ class ETHContract(persistent.Persistent): continue return eval(str_eval.strip()) - - -class InstanceList(persistent.Persistent): - - def __init__(self): - self.addresses = [] - self.balances = [] - pass - - def add(self, address, balance=0): - self.addresses.append(address) - self.balances.append(balance) - self._p_changed = True diff --git a/mythril/leveldb/client.py b/mythril/leveldb/client.py index 5f1e4221..bbed33a4 100644 --- a/mythril/leveldb/client.py +++ b/mythril/leveldb/client.py @@ -1,13 +1,11 @@ -import plyvel import binascii import rlp -import hashlib import logging from ethereum import utils from ethereum.block import BlockHeader, Block -from mythril.leveldb.state import State, Account +from mythril.leveldb.state import State from mythril.leveldb.eth_db import ETH_DB -from mythril.ether.ethcontract import ETHContract, InstanceList +from mythril.ether.ethcontract import ETHContract # Per https://github.com/ethereum/go-ethereum/blob/master/core/database_util.go # prefixes and suffixes for keys in geth @@ -16,7 +14,8 @@ bodyPrefix = b'b' # bodyPrefix + num (uint64 big endian) + hash -> block b numSuffix = b'n' # headerPrefix + num (uint64 big endian) + numSuffix -> hash blockHashPrefix = b'H' # blockHashPrefix + hash -> num (uint64 big endian) # known geth keys -headHeaderKey = b'LastBlock' # head (latest) header hash +headHeaderKey = b'LastBlock' # head (latest) header hash + def _formatBlockNumber(number): ''' @@ -24,12 +23,14 @@ def _formatBlockNumber(number): ''' return utils.zpad(utils.int_to_big_endian(number), 8) + def _encode_hex(v): ''' encodes hash as hex ''' return '0x' + utils.encode_hex(v) + class EthLevelDB(object): ''' Go-Ethereum LevelDB client class From 3e868427b25db2d267a7f2777bb24ece3536f68c Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Sat, 21 Jul 2018 20:36:14 +0000 Subject: [PATCH 16/23] Optimize search --- mythril/disassembler/disassembly.py | 3 ++- mythril/ether/ethcontract.py | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mythril/disassembler/disassembly.py b/mythril/disassembler/disassembly.py index 9f8aff89..2f7aec4c 100644 --- a/mythril/disassembler/disassembly.py +++ b/mythril/disassembler/disassembly.py @@ -7,7 +7,7 @@ class Disassembly(object): def __init__(self, code): self.instruction_list = asm.disassemble(util.safe_decode(code)) - self.xrefs = [] + self.func_hashes = [] self.func_to_addr = {} self.addr_to_func = {} self.bytecode = code @@ -24,6 +24,7 @@ class Disassembly(object): for i in jmptable_indices: func_hash = self.instruction_list[i]['argument'] + self.func_hashes.append(func_hash) try: # tries local cache, file and optional online lookup # may return more than one function signature. since we cannot probe for the correct one we'll use the first diff --git a/mythril/ether/ethcontract.py b/mythril/ether/ethcontract.py index 7c012f25..8e19c746 100644 --- a/mythril/ether/ethcontract.py +++ b/mythril/ether/ethcontract.py @@ -31,12 +31,12 @@ class ETHContract(persistent.Persistent): def get_easm(self): - return Disassembly(self.code).get_easm() + return self.disassembly.get_easm() def matches_expression(self, expression): - easm_code = self.get_easm() str_eval = '' + easm_code = None matches = re.findall(r'func#([a-zA-Z0-9\s_,(\\)\[\]]+)#', expression) @@ -58,6 +58,9 @@ class ETHContract(persistent.Persistent): m = re.match(r'^code#([a-zA-Z0-9\s,\[\]]+)#', token) if (m): + if easm_code is None: + easm_code = self.get_easm() + code = m.group(1).replace(",", "\\n") str_eval += "\"" + code + "\" in easm_code" continue @@ -65,7 +68,7 @@ class ETHContract(persistent.Persistent): m = re.match(r'^func#([a-fA-F0-9]+)#$', token) if (m): - str_eval += "\"" + m.group(1) + "\" in easm_code" + str_eval += "\"" + m.group(1) + "\" in self.disassembly.func_hashes" continue From 372bc9e6a673a5582363fc4332bc729a20dca56c Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 22 Jul 2018 07:05:22 -0400 Subject: [PATCH 17/23] Add -V and --version myth CLI option --- mythril/interfaces/cli.py | 9 ++++++++- mythril/version.py | 3 +++ setup.py | 9 +++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 mythril/version.py diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index b9a910e0..efda3b53 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# -*- coding: UTF-8 -*- +# -*- coding: utf-8 -*- """mythril.py: Bug hunting on the Ethereum blockchain http://www.github.com/ConsenSys/mythril @@ -14,6 +14,7 @@ import argparse from mythril.exceptions import CriticalError from mythril.mythril import Mythril +from mythril.version import VERSION def exit_with_error(format, message): @@ -31,6 +32,8 @@ def main(): commands = parser.add_argument_group('commands') commands.add_argument('-g', '--graph', help='generate a control flow graph') + commands.add_argument('-V', '--version', action='store_true', + help='print the Mythril version number and exit') commands.add_argument('-x', '--fire-lasers', action='store_true', help='detect vulnerabilities, use with -c, -a or solidity file(s)') commands.add_argument('-t', '--truffle', action='store_true', @@ -84,6 +87,10 @@ def main(): args = parser.parse_args() + if args.version: + print("Mythril version {}".format(VERSION)) + sys.exit() + # -- args sanity checks -- # Detect unsupported combinations of command line args diff --git a/mythril/version.py b/mythril/version.py new file mode 100644 index 00000000..c0d1b140 --- /dev/null +++ b/mythril/version.py @@ -0,0 +1,3 @@ +# This file is suitable for sourcing inside POSIX shell, e.g. bash as +# well as for importing into Python +VERSION = "v0.18.9" diff --git a/setup.py b/setup.py index ec606d3e..dd8d79ec 100755 --- a/setup.py +++ b/setup.py @@ -1,13 +1,18 @@ from setuptools import setup, find_packages from setuptools.command.install import install +from pathlib import Path import sys import os +# To make lint checkers happy we set VERSION here, but +# it is redefined by the exec below +VERSION = None + # Package version (vX.Y.Z). It must match git tag being used for CircleCI # deployment; otherwise the build will failed. -VERSION = "v0.18.9" - +version_path = (Path(__file__).parent / 'mythril' / 'version.py').absolute() +exec(open(version_path, 'r').read()) class VerifyVersionCommand(install): """Custom command to verify that the git tag matches our version""" From 203edcf1ff25556f04db37dfb8c0734f2e5358f8 Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 22 Jul 2018 07:10:39 -0400 Subject: [PATCH 18/23] Make code dual Python/POSIX --- mythril/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/version.py b/mythril/version.py index c0d1b140..e606aa4c 100644 --- a/mythril/version.py +++ b/mythril/version.py @@ -1,3 +1,3 @@ # This file is suitable for sourcing inside POSIX shell, e.g. bash as # well as for importing into Python -VERSION = "v0.18.9" +VERSION="v0.18.9" # NOQA From 0fdc27339b79dc9a9ba7a944e66d7d4e102865ff Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Sun, 22 Jul 2018 18:17:13 +0530 Subject: [PATCH 19/23] mythril/analysis/modules/ether_send.py --- mythril/analysis/modules/ether_send.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mythril/analysis/modules/ether_send.py b/mythril/analysis/modules/ether_send.py index d6bf850c..b7a4dce0 100644 --- a/mythril/analysis/modules/ether_send.py +++ b/mythril/analysis/modules/ether_send.py @@ -27,15 +27,14 @@ def execute(statespace): state = call.state address = state.get_current_instruction()['address'] - if ("callvalue" in str(call.value)): + if "callvalue" in str(call.value): logging.debug("[ETHER_SEND] Skipping refund function") continue # We're only interested in calls that send Ether - if call.value.type == VarType.CONCRETE: - if call.value.val == 0: - continue + if call.value.type == VarType.CONCRETE and call.value.val == 0: + continue interesting = False @@ -52,14 +51,14 @@ def execute(statespace): else: m = re.search(r'storage_([a-z0-9_&^]+)', str(call.to)) - if (m): + if m: idx = m.group(1) description += "a non-zero amount of Ether is sent to an address taken from storage slot " + str(idx) + ".\n" func = statespace.find_storage_write(state.environment.active_account.address, idx) - if (func): + if func: description += "There is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function `" + func + "`.\n" interesting = True else: @@ -74,7 +73,7 @@ def execute(statespace): index = 0 - while(can_solve and index < len(node.constraints)): + while can_solve and index < len(node.constraints): constraint = node.constraints[index] index += 1 @@ -82,14 +81,14 @@ def execute(statespace): m = re.search(r'storage_([a-z0-9_&^]+)', str(constraint)) - if (m): + if m: constrained = True idx = m.group(1) func = statespace.find_storage_write(state.environment.active_account.address, idx) - if (func): + if func: description += "\nThere is a check on storage index " + str(idx) + ". This storage slot can be written to by calling the function `" + func + "`." else: logging.debug("[ETHER_SEND] No storage writes to index " + str(idx)) @@ -98,7 +97,7 @@ def execute(statespace): # CALLER may also be constrained to hardcoded address. I.e. 'caller' and some integer - elif (re.search(r"caller", str(constraint)) and re.search(r'[0-9]{20}', str(constraint))): + elif re.search(r"caller", str(constraint)) and re.search(r'[0-9]{20}', str(constraint)): constrained = True can_solve = False break @@ -116,7 +115,8 @@ def execute(statespace): debug = "SOLVER OUTPUT:\n" + solver.pretty_print_model(model) - issue = Issue(call.node.contract_name, call.node.function_name, address, "Ether send", "Warning", description, debug) + issue = Issue(call.node.contract_name, call.node.function_name, address, "Ether send", "Warning", + description, debug) issues.append(issue) except UnsatError: From c1db9485047441321334760603743c61ff7fc1f3 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Sun, 22 Jul 2018 15:00:50 +0200 Subject: [PATCH 20/23] Adds sonar-scanner to the build flow --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 13e9d790..0a295470 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,6 +57,10 @@ jobs: command: python3 setup.py install working_directory: /home/mythril + - run: + name: Sonar analysis + command: if [ -z "CIRCLE_PR_NUMBER" ]; then sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.sources=/home/mythril -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$(SONAR_LOGIN); fi + - run: name: Integration tests command: if [ -z "$CIRCLE_PR_NUMBER" ]; then ./run-integration-tests.sh; fi From 53d8b60021562ce4bd330b88807630881f4e3e45 Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Sun, 22 Jul 2018 15:39:37 +0200 Subject: [PATCH 21/23] Fix of sonar command --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0a295470..14321c29 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,7 @@ jobs: - run: name: Sonar analysis - command: if [ -z "CIRCLE_PR_NUMBER" ]; then sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.sources=/home/mythril -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$(SONAR_LOGIN); fi + command: if [ -z "$CIRCLE_PR_NUMBER" ]; then if [ -z "$CIRCLE_TAG" ]; then sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.branch.name=$CIRCLE_BRANCH -Dsonar.projectBaseDir=/home/mythril -Dsonar.sources=. -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN; fi; fi - run: name: Integration tests From e4308f87cc8c6f96028e53764f43f16381f11f9b Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Sun, 22 Jul 2018 16:51:45 +0200 Subject: [PATCH 22/23] Changes source directory of sonar analysis --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 14321c29..67003764 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,7 @@ jobs: - run: name: Sonar analysis - command: if [ -z "$CIRCLE_PR_NUMBER" ]; then if [ -z "$CIRCLE_TAG" ]; then sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.branch.name=$CIRCLE_BRANCH -Dsonar.projectBaseDir=/home/mythril -Dsonar.sources=. -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN; fi; fi + command: if [ -z "$CIRCLE_PR_NUMBER" ]; then if [ -z "$CIRCLE_TAG" ]; then sonar-scanner -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.organization=$SONAR_ORGANIZATION -Dsonar.branch.name=$CIRCLE_BRANCH -Dsonar.projectBaseDir=/home/mythril -Dsonar.sources=mythril -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN; fi; fi - run: name: Integration tests From def580de7da58d1374e08eddcc36f450ac867050 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Sun, 22 Jul 2018 21:23:04 +0200 Subject: [PATCH 23/23] Add real-world examples from https://blog.sigmaprime.io/solidity-security.html --- solidity_examples/BECToken.sol | 299 +++++++++++++ solidity_examples/WalletLibrary.sol | 396 ++++++++++++++++++ solidity_examples/ether_send.sol | 31 -- solidity_examples/etherstore.sol | 21 + solidity_examples/hashforether.sol | 12 + solidity_examples/timelock.sol | 21 + .../{underflow.sol => token.sol} | 6 +- 7 files changed, 753 insertions(+), 33 deletions(-) create mode 100644 solidity_examples/BECToken.sol create mode 100644 solidity_examples/WalletLibrary.sol delete mode 100644 solidity_examples/ether_send.sol create mode 100644 solidity_examples/etherstore.sol create mode 100644 solidity_examples/hashforether.sol create mode 100644 solidity_examples/timelock.sol rename solidity_examples/{underflow.sol => token.sol} (78%) diff --git a/solidity_examples/BECToken.sol b/solidity_examples/BECToken.sol new file mode 100644 index 00000000..b03a20e5 --- /dev/null +++ b/solidity_examples/BECToken.sol @@ -0,0 +1,299 @@ +pragma solidity ^0.4.16; + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + function mul(uint256 a, uint256 b) internal constant returns (uint256) { + uint256 c = a * b; + assert(a == 0 || c / a == b); + return c; + } + + function div(uint256 a, uint256 b) internal constant returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint256 a, uint256 b) internal constant returns (uint256) { + assert(b <= a); + return a - b; + } + + function add(uint256 a, uint256 b) internal constant returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + uint256 public totalSupply; + function balanceOf(address who) public constant returns (uint256); + function transfer(address to, uint256 value) public returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); +} + +/** + * @title Basic token + * @dev Basic version of StandardToken, with no allowances. + */ +contract BasicToken is ERC20Basic { + using SafeMath for uint256; + + mapping(address => uint256) balances; + + /** + * @dev transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + */ + function transfer(address _to, uint256 _value) public returns (bool) { + require(_to != address(0)); + require(_value > 0 && _value <= balances[msg.sender]); + + // SafeMath.sub will throw if there is not enough balance. + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _owner The address to query the the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address _owner) public constant returns (uint256 balance) { + return balances[_owner]; + } +} + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20 is ERC20Basic { + function allowance(address owner, address spender) public constant returns (uint256); + function transferFrom(address from, address to, uint256 value) public returns (bool); + function approve(address spender, uint256 value) public returns (bool); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * @dev https://github.com/ethereum/EIPs/issues/20 + * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + */ +contract StandardToken is ERC20, BasicToken { + + mapping (address => mapping (address => uint256)) internal allowed; + + + /** + * @dev Transfer tokens from one address to another + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + */ + function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { + require(_to != address(0)); + require(_value > 0 && _value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + Transfer(_from, _to, _value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param _spender The address which will spend the funds. + * @param _value The amount of tokens to be spent. + */ + function approve(address _spender, uint256 _value) public returns (bool) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } +} + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address public owner; + + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + function Ownable() { + owner = msg.sender; + } + + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) onlyOwner public { + require(newOwner != address(0)); + OwnershipTransferred(owner, newOwner); + owner = newOwner; + } + +} + +/** + * @title Pausable + * @dev Base contract which allows children to implement an emergency stop mechanism. + */ +contract Pausable is Ownable { + event Pause(); + event Unpause(); + + bool public paused = false; + + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!paused); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + */ + modifier whenPaused() { + require(paused); + _; + } + + /** + * @dev called by the owner to pause, triggers stopped state + */ + function pause() onlyOwner whenNotPaused public { + paused = true; + Pause(); + } + + /** + * @dev called by the owner to unpause, returns to normal state + */ + function unpause() onlyOwner whenPaused public { + paused = false; + Unpause(); + } +} + +/** + * @title Pausable token + * + * @dev StandardToken modified with pausable transfers. + **/ + +contract PausableToken is StandardToken, Pausable { + + function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) { + return super.transfer(_to, _value); + } + + function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool) { + return super.transferFrom(_from, _to, _value); + } + + function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) { + return super.approve(_spender, _value); + } + + function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) { + uint cnt = _receivers.length; + uint256 amount = uint256(cnt) * _value; + require(cnt > 0 && cnt <= 20); + require(_value > 0 && balances[msg.sender] >= amount); + + balances[msg.sender] = balances[msg.sender].sub(amount); + for (uint i = 0; i < cnt; i++) { + balances[_receivers[i]] = balances[_receivers[i]].add(_value); + Transfer(msg.sender, _receivers[i], _value); + } + return true; + } +} + +/** + * @title Bec Token + * + * @dev Implementation of Bec Token based on the basic standard token. + */ +contract BecToken is PausableToken { + /** + * Public variables of the token + * 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 = "BeautyChain"; + string public symbol = "BEC"; + string public version = '1.0.0'; + uint8 public decimals = 18; + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + */ + function BecToken() { + totalSupply = 7000000000 * (10**(uint256(decimals))); + balances[msg.sender] = totalSupply; // Give the creator all initial tokens + } + + function () { + //if ether is sent to this address, send it back. + revert(); + } +} \ No newline at end of file diff --git a/solidity_examples/WalletLibrary.sol b/solidity_examples/WalletLibrary.sol new file mode 100644 index 00000000..90294219 --- /dev/null +++ b/solidity_examples/WalletLibrary.sol @@ -0,0 +1,396 @@ +//sol Wallet +// Multi-sig, daily-limited account proxy/wallet. +// @authors: +// Gav Wood +// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a +// single, or, crucially, each of a number of, designated owners. +// usage: +// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by +// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the +// interior is executed. + +pragma solidity ^0.4.9; + +contract WalletEvents { + // EVENTS + + // this contract only has six types of events: it can accept a confirmation, in which case + // we record owner and operation (hash) alongside it. + event Confirmation(address owner, bytes32 operation); + event Revoke(address owner, bytes32 operation); + + // some others are in the case of an owner changing. + event OwnerChanged(address oldOwner, address newOwner); + event OwnerAdded(address newOwner); + event OwnerRemoved(address oldOwner); + + // the last one is emitted if the required signatures change + event RequirementChanged(uint newRequirement); + + // Funds has arrived into the wallet (record how much). + event Deposit(address _from, uint value); + // Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going). + event SingleTransact(address owner, uint value, address to, bytes data, address created); + // Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going). + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); +} + +contract WalletAbi { + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external; + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) external; + + function addOwner(address _owner) external; + + function removeOwner(address _owner) external; + + function changeRequirement(uint _newRequired) external; + + function isOwner(address _addr) constant returns (bool); + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool); + + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) external; + + function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash); + function confirm(bytes32 _h) returns (bool o_success); +} + +contract WalletLibrary is WalletEvents { + // TYPES + + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + // MODIFIERS + + // simple single-sig function modifier. + modifier onlyowner { + if (isOwner(msg.sender)) + _; + } + // multi-sig function modifier: the operation must have an intrinsic hash in order + // that later attempts can be realised as the same underlying operation and + // thus count as confirmations. + modifier onlymanyowners(bytes32 _operation) { + if (confirmAndCheck(_operation)) + _; + } + + // METHODS + + // gets called when no other function matches + function() payable { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function initMultiowned(address[] _owners, uint _required) only_uninitialized { + m_numOwners = _owners.length + 1; + m_owners[1] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = 1; + for (uint i = 0; i < _owners.length; ++i) + { + m_owners[2 + i] = uint(_owners[i]); + m_ownerIndex[uint(_owners[i])] = 2 + i; + } + m_required = _required; + } + + // Revokes a prior confirmation of the given operation + function revoke(bytes32 _operation) external { + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + uint ownerIndexBit = 2**ownerIndex; + var pending = m_pending[_operation]; + if (pending.ownersDone & ownerIndexBit > 0) { + pending.yetNeeded++; + pending.ownersDone -= ownerIndexBit; + Revoke(msg.sender, _operation); + } + } + + // Replaces an owner `_from` with another `_to`. + function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_to)) return; + uint ownerIndex = m_ownerIndex[uint(_from)]; + if (ownerIndex == 0) return; + + clearPending(); + m_owners[ownerIndex] = uint(_to); + m_ownerIndex[uint(_from)] = 0; + m_ownerIndex[uint(_to)] = ownerIndex; + OwnerChanged(_from, _to); + } + + function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + if (isOwner(_owner)) return; + + clearPending(); + if (m_numOwners >= c_maxOwners) + reorganizeOwners(); + if (m_numOwners >= c_maxOwners) + return; + m_numOwners++; + m_owners[m_numOwners] = uint(_owner); + m_ownerIndex[uint(_owner)] = m_numOwners; + OwnerAdded(_owner); + } + + function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external { + uint ownerIndex = m_ownerIndex[uint(_owner)]; + if (ownerIndex == 0) return; + if (m_required > m_numOwners - 1) return; + + m_owners[ownerIndex] = 0; + m_ownerIndex[uint(_owner)] = 0; + clearPending(); + reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot + OwnerRemoved(_owner); + } + + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + + // Gets an owner by 0-indexed position (using numOwners as the count) + function getOwner(uint ownerIndex) external constant returns (address) { + return address(m_owners[ownerIndex + 1]); + } + + function isOwner(address _addr) constant returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) { + var pending = m_pending[_operation]; + uint ownerIndex = m_ownerIndex[uint(_owner)]; + + // make sure they're an owner + if (ownerIndex == 0) return false; + + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + return !(pending.ownersDone & ownerIndexBit == 0); + } + + // constructor - stores initial daily limit and records the present day's index. + function initDaylimit(uint _limit) only_uninitialized { + m_dailyLimit = _limit; + m_lastDay = today(); + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external { + m_dailyLimit = _newLimit; + } + // resets the amount already spent today. needs many of the owners to confirm. + function resetSpentToday() onlymanyowners(sha3(msg.data)) external { + m_spentToday = 0; + } + + // throw unless the contract is not yet initialized. + modifier only_uninitialized { if (m_numOwners > 0) throw; _; } + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized { + initDaylimit(_daylimit); + initMultiowned(_owners, _required); + } + + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) external { + suicide(_to); + } + + // Outside-visible transact entry point. Executes transaction immediately if below daily spend limit. + // If not, goes into multisig process. We provide a hash on return to allow the sender to provide + // shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value + // and _data arguments). They still get the option of using them if they want, anyways. + function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) { + // first, take the opportunity to check that we're under the daily limit. + if ((_data.length == 0 && underLimit(_value)) || m_required == 1) { + // yes - just execute the call. + address created; + if (_to == 0) { + created = create(_value, _data); + } else { + if (!_to.call.value(_value)(_data)) + throw; + } + SingleTransact(msg.sender, _value, _to, _data, created); + } else { + // determine our operation hash. + o_hash = sha3(msg.data, block.number); + // store if it's new + if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) { + m_txs[o_hash].to = _to; + m_txs[o_hash].value = _value; + m_txs[o_hash].data = _data; + } + if (!confirm(o_hash)) { + ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); + } + } + } + + function create(uint _value, bytes _code) internal returns (address o_addr) { + assembly { + o_addr := create(_value, add(_code, 0x20), mload(_code)) + jumpi(0xdeadbeef, iszero(extcodesize(o_addr))) + } + } + + // confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order + // to determine the body of the transaction from the hash provided. + function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) { + if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) { + address created; + if (m_txs[_h].to == 0) { + created = create(m_txs[_h].value, m_txs[_h].data); + } else { + if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data)) + throw; + } + + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created); + delete m_txs[_h]; + return true; + } + } + + // INTERNAL METHODS + + function confirmAndCheck(bytes32 _operation) internal returns (bool) { + // determine what index the present sender is: + uint ownerIndex = m_ownerIndex[uint(msg.sender)]; + // make sure they're an owner + if (ownerIndex == 0) return; + + var pending = m_pending[_operation]; + // if we're not yet working on this operation, switch over and reset the confirmation status. + if (pending.yetNeeded == 0) { + // reset count of confirmations needed. + pending.yetNeeded = m_required; + // reset which owners have confirmed (none) - set our bitmap to 0. + pending.ownersDone = 0; + pending.index = m_pendingIndex.length++; + m_pendingIndex[pending.index] = _operation; + } + // determine the bit to set for this owner. + uint ownerIndexBit = 2**ownerIndex; + // make sure we (the message sender) haven't confirmed this operation previously. + if (pending.ownersDone & ownerIndexBit == 0) { + Confirmation(msg.sender, _operation); + // ok - check if count is enough to go ahead. + if (pending.yetNeeded <= 1) { + // enough confirmations: reset and run interior. + delete m_pendingIndex[m_pending[_operation].index]; + delete m_pending[_operation]; + return true; + } + else + { + // not enough: record that this owner in particular confirmed. + pending.yetNeeded--; + pending.ownersDone |= ownerIndexBit; + } + } + } + + function reorganizeOwners() private { + uint free = 1; + while (free < m_numOwners) + { + while (free < m_numOwners && m_owners[free] != 0) free++; + while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--; + if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0) + { + m_owners[free] = m_owners[m_numOwners]; + m_ownerIndex[m_owners[free]] = free; + m_owners[m_numOwners] = 0; + } + } + } + + // checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and + // returns true. otherwise just returns false. + function underLimit(uint _value) internal onlyowner returns (bool) { + // reset the spend limit if we're on a different day to last time. + if (today() > m_lastDay) { + m_spentToday = 0; + m_lastDay = today(); + } + // check to see if there's enough left - if so, subtract and return true. + // overflow protection // dailyLimit check + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; + } + return false; + } + + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + + function clearPending() internal { + uint length = m_pendingIndex.length; + + for (uint i = 0; i < length; ++i) { + delete m_txs[m_pendingIndex[i]]; + + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; + } + + delete m_pendingIndex; + } + + // FIELDS + address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe; + + // the number of owners that must confirm the same operation before it is run. + uint public m_required; + // pointer used to find a free slot in m_owners + uint public m_numOwners; + + uint public m_dailyLimit; + uint public m_spentToday; + uint public m_lastDay; + + // list of owners + uint[256] m_owners; + + uint constant c_maxOwners = 250; + // index on the list of owners to allow reverse lookup + mapping(uint => uint) m_ownerIndex; + // the ongoing operations. + mapping(bytes32 => PendingState) m_pending; + bytes32[] m_pendingIndex; + + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; +} diff --git a/solidity_examples/ether_send.sol b/solidity_examples/ether_send.sol deleted file mode 100644 index a814332b..00000000 --- a/solidity_examples/ether_send.sol +++ /dev/null @@ -1,31 +0,0 @@ -contract Crowdfunding { - - mapping(address => uint) public balances; - address public owner; - uint256 INVEST_MIN = 1 ether; - uint256 INVEST_MAX = 10 ether; - - modifier onlyOwner() { - require(msg.sender == owner); - _; - } - - function crowdfunding() { - owner = msg.sender; - } - - function withdrawfunds() onlyOwner { - msg.sender.transfer(this.balance); - } - - function invest() public payable { - require(msg.value > INVEST_MIN && msg.value < INVEST_MAX); - - balances[msg.sender] += msg.value; - } - - function getBalance() public constant returns (uint) { - return balances[msg.sender]; - } - -} diff --git a/solidity_examples/etherstore.sol b/solidity_examples/etherstore.sol new file mode 100644 index 00000000..38a9177c --- /dev/null +++ b/solidity_examples/etherstore.sol @@ -0,0 +1,21 @@ +contract EtherStore { + + uint256 public withdrawalLimit = 1 ether; + mapping(address => uint256) public lastWithdrawTime; + mapping(address => uint256) public balances; + + function depositFunds() public payable { + balances[msg.sender] += msg.value; + } + + function withdrawFunds (uint256 _weiToWithdraw) public { + require(balances[msg.sender] >= _weiToWithdraw); + // limit the withdrawal + require(_weiToWithdraw <= withdrawalLimit); + // limit the time allowed to withdraw + require(now >= lastWithdrawTime[msg.sender] + 1 weeks); + require(msg.sender.call.value(_weiToWithdraw)()); + balances[msg.sender] -= _weiToWithdraw; + lastWithdrawTime[msg.sender] = now; + } + } \ No newline at end of file diff --git a/solidity_examples/hashforether.sol b/solidity_examples/hashforether.sol new file mode 100644 index 00000000..a90538ea --- /dev/null +++ b/solidity_examples/hashforether.sol @@ -0,0 +1,12 @@ +contract HashForEther { + + function withdrawWinnings() { + // Winner if the last 8 hex characters of the address are 0. + require(uint32(msg.sender) == 0); + _sendWinnings(); + } + + function _sendWinnings() { + msg.sender.transfer(this.balance); + } +} \ No newline at end of file diff --git a/solidity_examples/timelock.sol b/solidity_examples/timelock.sol new file mode 100644 index 00000000..33ddb4fe --- /dev/null +++ b/solidity_examples/timelock.sol @@ -0,0 +1,21 @@ +contract TimeLock { + + mapping(address => uint) public balances; + mapping(address => uint) public lockTime; + + function deposit() public payable { + balances[msg.sender] += msg.value; + lockTime[msg.sender] = now + 1 weeks; + } + + function increaseLockTime(uint _secondsToIncrease) public { + lockTime[msg.sender] += _secondsToIncrease; + } + + function withdraw() public { + require(balances[msg.sender] > 0); + require(now > lockTime[msg.sender]); + balances[msg.sender] = 0; + msg.sender.transfer(balances[msg.sender]); + } +} \ No newline at end of file diff --git a/solidity_examples/underflow.sol b/solidity_examples/token.sol similarity index 78% rename from solidity_examples/underflow.sol rename to solidity_examples/token.sol index 6ecef5df..19239427 100644 --- a/solidity_examples/underflow.sol +++ b/solidity_examples/token.sol @@ -1,4 +1,6 @@ -contract Under { +pragma solidity ^0.4.18; + +contract Token { mapping(address => uint) balances; uint public totalSupply; @@ -7,7 +9,7 @@ contract Under { balances[msg.sender] = totalSupply = _initialSupply; } - function sendeth(address _to, uint _value) public returns (bool) { + function transfer(address _to, uint _value) public returns (bool) { require(balances[msg.sender] - _value >= 0); balances[msg.sender] -= _value; balances[_to] += _value;