mirror of https://github.com/ConsenSys/mythril
Add feature extraction (#1785)
* Add push0 and push tests * Fix issue with args * Add missing files * Add feature extraction * Rename filepull/1784/head
parent
ca2dc138f8
commit
96c3ecd4b8
@ -0,0 +1,234 @@ |
|||||||
|
TRANSFER_METHODS = ["transfer", "send"] |
||||||
|
|
||||||
|
|
||||||
|
class SolidityFeatureExtractor: |
||||||
|
def __init__(self, ast): |
||||||
|
self.ast = ast |
||||||
|
|
||||||
|
def extract_features(self): |
||||||
|
function_features = {} |
||||||
|
function_nodes = self.find_function_nodes(self.ast) |
||||||
|
modifier_vars = {} |
||||||
|
for modifier_node in self.find_modifier_nodes(self.ast): |
||||||
|
modifier_vars[modifier_node["name"]] = self.find_variables_in_require( |
||||||
|
modifier_node |
||||||
|
) |
||||||
|
modifier_vars[modifier_node["name"]].update( |
||||||
|
self.find_variables_in_if(modifier_node) |
||||||
|
) |
||||||
|
|
||||||
|
print(modifier_vars) |
||||||
|
|
||||||
|
for node in function_nodes: |
||||||
|
function_name = self.get_function_name(node) |
||||||
|
contains_selfdestruct = self.contains_selfdestruct(node) |
||||||
|
contains_call = self.contains_call(node) |
||||||
|
contains_delegatecall = self.contains_delegatecall(node) |
||||||
|
contains_callcode = self.contains_callcode(node) |
||||||
|
contains_staticcall = self.contains_staticcall(node) |
||||||
|
all_require_vars = self.find_variables_in_require(node) |
||||||
|
ether_vars = self.extract_address_variable(node) |
||||||
|
print(ether_vars) |
||||||
|
for modifier in node.get("modifiers", []): |
||||||
|
all_require_vars.update(modifier_vars[modifier["modifierName"]["name"]]) |
||||||
|
is_payable = self.is_function_payable(node) |
||||||
|
has_isowner_modifier = self.has_isowner_modifier(node) |
||||||
|
contains_assert = self.contains_assert(node) |
||||||
|
function_features[function_name] = { |
||||||
|
"contains_selfdestruct": contains_selfdestruct, |
||||||
|
"contains_call": contains_call, |
||||||
|
"is_payable": is_payable, |
||||||
|
"has_owner_modifier": has_isowner_modifier, |
||||||
|
"contains_assert": contains_assert, |
||||||
|
"contains_callcode": contains_callcode, |
||||||
|
"contains_delegatecall": contains_delegatecall, |
||||||
|
"contains_staticcall": contains_staticcall, |
||||||
|
"all_require_vars": all_require_vars, |
||||||
|
"transfer_vars": ether_vars, |
||||||
|
} |
||||||
|
|
||||||
|
return function_features |
||||||
|
|
||||||
|
def find_function_nodes(self, node): |
||||||
|
if node["nodeType"] == "FunctionDefinition": |
||||||
|
yield node |
||||||
|
|
||||||
|
if "nodes" in node: |
||||||
|
for child_node in node["nodes"]: |
||||||
|
yield from self.find_function_nodes(child_node) |
||||||
|
|
||||||
|
def find_modifier_nodes(self, node): |
||||||
|
if node["nodeType"] == "ModifierDefinition": |
||||||
|
yield node |
||||||
|
|
||||||
|
if "nodes" in node: |
||||||
|
for child_node in node["nodes"]: |
||||||
|
yield from self.find_modifier_nodes(child_node) |
||||||
|
|
||||||
|
def get_function_name(self, node): |
||||||
|
return node["name"] |
||||||
|
|
||||||
|
def contains_command(self, node, command): |
||||||
|
if isinstance(node, dict): |
||||||
|
if command in node.values(): |
||||||
|
return True |
||||||
|
|
||||||
|
for value in node.values(): |
||||||
|
if isinstance(value, (dict, list)): |
||||||
|
if self.contains_command(value, command): |
||||||
|
return True |
||||||
|
|
||||||
|
elif isinstance(node, list): |
||||||
|
for item in node: |
||||||
|
if self.contains_command(item, command): |
||||||
|
return True |
||||||
|
|
||||||
|
return False |
||||||
|
|
||||||
|
def contains_call(self, node): |
||||||
|
return self.contains_command(node, "call") |
||||||
|
|
||||||
|
def is_function_payable(self, node): |
||||||
|
return node.get("stateMutability") == "payable" |
||||||
|
|
||||||
|
def has_isowner_modifier(self, node): |
||||||
|
if "modifiers" in node: |
||||||
|
for modifier in node["modifiers"]: |
||||||
|
if modifier["modifierName"]["name"].lower() in ("isowner", "onlyowner"): |
||||||
|
return True |
||||||
|
return False |
||||||
|
|
||||||
|
def contains_assert(self, node): |
||||||
|
return self.contains_command(node, "assert") |
||||||
|
|
||||||
|
def contains_selfdestruct(self, node): |
||||||
|
return self.contains_command(node, "selfdestruct") |
||||||
|
|
||||||
|
def contains_delegatecall(self, node): |
||||||
|
return self.contains_command(node, "delegatecall") |
||||||
|
|
||||||
|
def contains_callcode(self, node): |
||||||
|
return self.contains_command(node, "callcode") |
||||||
|
|
||||||
|
def contains_staticcall(self, node): |
||||||
|
return self.contains_command(node, "staticcall") |
||||||
|
|
||||||
|
def contains_require(self, node): |
||||||
|
return self.contains_command(node, "require") |
||||||
|
|
||||||
|
def extract_nodes(self, node, command, parent=None): |
||||||
|
node_list = [] |
||||||
|
if isinstance(node, dict): |
||||||
|
if command in node.values(): |
||||||
|
node_list.append((parent, node)) |
||||||
|
|
||||||
|
for key, value in node.items(): |
||||||
|
if isinstance(value, (dict, list)): |
||||||
|
node_list.extend(self.extract_nodes(value, command, parent=node)) |
||||||
|
elif isinstance(node, list): |
||||||
|
for item in node: |
||||||
|
node_list.extend(self.extract_nodes(item, command, parent=node)) |
||||||
|
return node_list |
||||||
|
|
||||||
|
def find_all_variables(self, node): |
||||||
|
variables = set() |
||||||
|
|
||||||
|
def traverse(node): |
||||||
|
if isinstance(node, dict): |
||||||
|
for key, value in node.items(): |
||||||
|
if key == "nodeType" and value == "Identifier": |
||||||
|
if "name" in node: |
||||||
|
variables.add(node["name"]) |
||||||
|
elif isinstance(value, (dict, list)): |
||||||
|
traverse(value) |
||||||
|
elif isinstance(node, list): |
||||||
|
for item in node: |
||||||
|
traverse(item) |
||||||
|
|
||||||
|
traverse(node) |
||||||
|
return variables |
||||||
|
|
||||||
|
def find_variables_in_require(self, node): |
||||||
|
|
||||||
|
nodes = self.extract_nodes(node, "require") |
||||||
|
variables = set() |
||||||
|
for parent, _ in nodes: |
||||||
|
if "arguments" in parent: |
||||||
|
arguments = parent["arguments"] |
||||||
|
for argument in arguments: |
||||||
|
variables.update(self.find_all_variables(argument)) |
||||||
|
return variables |
||||||
|
|
||||||
|
def find_variables_in_if(self, node): |
||||||
|
variables = [] |
||||||
|
|
||||||
|
def traverse(node): |
||||||
|
if "condition" in node: |
||||||
|
condition = node["condition"] |
||||||
|
if ( |
||||||
|
"leftExpression" in condition |
||||||
|
and condition["leftExpression"]["nodeType"] == "Identifier" |
||||||
|
): |
||||||
|
variables.append(condition["leftExpression"]["name"]) |
||||||
|
if ( |
||||||
|
"rightExpression" in condition |
||||||
|
and condition["rightExpression"]["nodeType"] == "Identifier" |
||||||
|
): |
||||||
|
variables.append(condition["rightExpression"]["name"]) |
||||||
|
|
||||||
|
traverse(condition) |
||||||
|
|
||||||
|
if "falseBody" in node and node["falseBody"]: |
||||||
|
traverse(node["falseBody"]) |
||||||
|
|
||||||
|
if "trueBody" in node and node["trueBody"]: |
||||||
|
if ( |
||||||
|
"nodeType" in node["trueBody"] |
||||||
|
and node["trueBody"]["nodeType"] == "Block" |
||||||
|
): |
||||||
|
statements = node["trueBody"].get("statements", []) |
||||||
|
for statement in statements: |
||||||
|
traverse(statement) |
||||||
|
else: |
||||||
|
traverse(node["trueBody"]) |
||||||
|
|
||||||
|
if "body" in node and node["body"]: |
||||||
|
if "nodeType" in node["body"] and node["body"]["nodeType"] == "Block": |
||||||
|
statements = node["body"].get("statements", []) |
||||||
|
for statement in statements: |
||||||
|
traverse(statement) |
||||||
|
else: |
||||||
|
traverse(node["body"]) |
||||||
|
|
||||||
|
traverse(node) |
||||||
|
|
||||||
|
return variables |
||||||
|
|
||||||
|
def extract_address_variable(self, node): |
||||||
|
if type(node) == int: |
||||||
|
return set([]) |
||||||
|
transfer_vars = set([]) |
||||||
|
if ( |
||||||
|
node.get("nodeType", "") == "ExpressionStatement" |
||||||
|
and node.get("expression", {}).get("nodeType") == "FunctionCall" |
||||||
|
): |
||||||
|
expression = node["expression"].get("expression", None) |
||||||
|
if expression is not None: |
||||||
|
if ( |
||||||
|
expression["nodeType"] == "MemberAccess" |
||||||
|
and expression["memberName"] in TRANSFER_METHODS |
||||||
|
): |
||||||
|
print(expression) |
||||||
|
address_variable = expression["expression"].get("name") |
||||||
|
if address_variable: |
||||||
|
transfer_vars.update(set([address_variable])) |
||||||
|
|
||||||
|
for key, value in node.items(): |
||||||
|
if isinstance(value, dict): |
||||||
|
transfer_vars.update(self.extract_address_variable(value)) |
||||||
|
|
||||||
|
elif isinstance(value, list): |
||||||
|
for item in value: |
||||||
|
transfer_vars.update(self.extract_address_variable(item)) |
||||||
|
|
||||||
|
return transfer_vars |
@ -0,0 +1,72 @@ |
|||||||
|
import pytest |
||||||
|
from pathlib import Path |
||||||
|
from mythril.mythril import MythrilDisassembler |
||||||
|
from mythril.solidity.features import SolidityFeatureExtractor |
||||||
|
from mythril.solidity.soliditycontract import SolidityContract, SolcAST |
||||||
|
|
||||||
|
TEST_FILES = Path(__file__).parent / "testdata/input_contracts" |
||||||
|
solc_binary = MythrilDisassembler._init_solc_binary("v0.5.0") |
||||||
|
|
||||||
|
test_cases = [ |
||||||
|
("suicide.sol", 1, "kill", "contains_selfdestruct", True), |
||||||
|
( |
||||||
|
"SimpleModifier.sol", |
||||||
|
1, |
||||||
|
"withdrawfunds", |
||||||
|
"all_require_vars", |
||||||
|
set(["msg", "owner"]), |
||||||
|
), |
||||||
|
("SimpleModifier.sol", 1, "withdrawfunds", "transfer_vars", set(["owner"])), |
||||||
|
("rubixi.sol", 18, "init", "contains_selfdestruct", False), |
||||||
|
("rubixi.sol", 18, "collectAllFees", "has_owner_modifier", True), |
||||||
|
("rubixi.sol", 18, "collectAllFees", "is_payable", False), |
||||||
|
( |
||||||
|
"rubixi.sol", |
||||||
|
18, |
||||||
|
"collectAllFees", |
||||||
|
"all_require_vars", |
||||||
|
set(["collectedFees", "creator"]), |
||||||
|
), |
||||||
|
( |
||||||
|
"rubixi.sol", |
||||||
|
18, |
||||||
|
"collectPercentOfFees", |
||||||
|
"all_require_vars", |
||||||
|
set(["collectedFees", "_pcent", "creator"]), |
||||||
|
), |
||||||
|
( |
||||||
|
"rubixi.sol", |
||||||
|
18, |
||||||
|
"changeMultiplier", |
||||||
|
"all_require_vars", |
||||||
|
set(["_mult", "creator"]), |
||||||
|
), |
||||||
|
("rubixi.sol", 18, "", "is_payable", True), |
||||||
|
("rubixi.sol", 18, "collectAllFees", "transfer_vars", set(["creator"])), |
||||||
|
("exceptions.sol", 8, "assert3", "contains_assert", True), |
||||||
|
("exceptions.sol", 8, "requireisfine", "all_require_vars", set(["input"])), |
||||||
|
("WalletLibrary.sol", 23, "execute", "has_owner_modifier", True), |
||||||
|
("WalletLibrary.sol", 23, "initWallet", "has_owner_modifier", False), |
||||||
|
("WalletLibrary.sol", 23, "initWallet", "all_require_vars", set(["m_numOwners"])), |
||||||
|
("WalletLibrary.sol", 23, "confirm", "all_require_vars", set(["success"])), |
||||||
|
("kcalls.sol", 3, "callSetN", "contains_call", True), |
||||||
|
("kcalls.sol", 3, "delegatecallSetN", "contains_delegatecall", True), |
||||||
|
("kcalls.sol", 3, "callcodeSetN", "contains_staticcall", True), |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"file_name, num_funcs, func_name, field, expected_value", test_cases |
||||||
|
) |
||||||
|
def test_feature_selfdestruct(file_name, num_funcs, func_name, field, expected_value): |
||||||
|
input_file = TEST_FILES / file_name |
||||||
|
name = file_name.split(".")[0] |
||||||
|
print(name, name.capitalize()) |
||||||
|
if name[0].islower(): |
||||||
|
name = name.capitalize() |
||||||
|
contract = SolidityContract(str(input_file), name=name, solc_binary=solc_binary) |
||||||
|
ms = contract.solc_json["sources"][str(input_file)]["ast"] |
||||||
|
sfe = SolidityFeatureExtractor(ms) |
||||||
|
fe = sfe.extract_features() |
||||||
|
assert len(fe) == num_funcs |
||||||
|
assert fe[func_name][field] == expected_value |
@ -0,0 +1,19 @@ |
|||||||
|
pragma solidity ^0.5.0; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
contract SimpleModifier { |
||||||
|
|
||||||
|
address payable public owner; |
||||||
|
|
||||||
|
modifier onlyOwner() { |
||||||
|
require(msg.sender == owner); |
||||||
|
_; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function withdrawfunds() public onlyOwner { |
||||||
|
owner.send(address(this).balance); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,398 @@ |
|||||||
|
//sol Wallet |
||||||
|
// Multi-sig, daily-limited account proxy/wallet. |
||||||
|
// @authors: |
||||||
|
// Gav Wood <g@ethdev.com> |
||||||
|
// 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.5.0; |
||||||
|
|
||||||
|
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) public returns (bool); |
||||||
|
|
||||||
|
function hasConfirmed(bytes32 _operation, address _owner) external 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 calldata _data) external returns (bytes32 o_hash); |
||||||
|
function confirm(bytes32 _h) public 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() external payable { |
||||||
|
// just being sent some cash? |
||||||
|
if (msg.value > 0) |
||||||
|
emit 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[] memory _owners, uint _required) public 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; |
||||||
|
PendingState memory pending = m_pending[_operation]; |
||||||
|
if (pending.ownersDone & ownerIndexBit > 0) { |
||||||
|
pending.yetNeeded++; |
||||||
|
pending.ownersDone -= ownerIndexBit; |
||||||
|
emit Revoke(msg.sender, _operation); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Replaces an owner `_from` with another `_to`. |
||||||
|
function changeOwner(address _from, address _to) onlymanyowners(keccak256(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; |
||||||
|
emit OwnerChanged(_from, _to); |
||||||
|
} |
||||||
|
|
||||||
|
function addOwner(address _owner) onlymanyowners(keccak256(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; |
||||||
|
emit OwnerAdded(_owner); |
||||||
|
} |
||||||
|
|
||||||
|
function removeOwner(address _owner) onlymanyowners(keccak256(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 |
||||||
|
emit OwnerRemoved(_owner); |
||||||
|
} |
||||||
|
|
||||||
|
function changeRequirement(uint _newRequired) onlymanyowners(keccak256(msg.data)) external { |
||||||
|
if (_newRequired > m_numOwners) return; |
||||||
|
m_required = _newRequired; |
||||||
|
clearPending(); |
||||||
|
emit RequirementChanged(_newRequired); |
||||||
|
} |
||||||
|
|
||||||
|
// Gets an owner by 0-indexed position (using numOwners as the count) |
||||||
|
function getOwner(uint ownerIndex) external view returns (address) { |
||||||
|
return address(m_owners[ownerIndex + 1]); |
||||||
|
} |
||||||
|
|
||||||
|
function isOwner(address _addr) public view returns (bool) { |
||||||
|
return m_ownerIndex[uint(_addr)] > 0; |
||||||
|
} |
||||||
|
|
||||||
|
function hasConfirmed(bytes32 _operation, address _owner) external view returns (bool) { |
||||||
|
PendingState memory 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) public 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(keccak256(msg.data)) external { |
||||||
|
m_dailyLimit = _newLimit; |
||||||
|
} |
||||||
|
// resets the amount already spent today. needs many of the owners to confirm. |
||||||
|
function resetSpentToday() onlymanyowners(keccak256(msg.data)) external { |
||||||
|
m_spentToday = 0; |
||||||
|
} |
||||||
|
|
||||||
|
// throw unless the contract is not yet initialized. |
||||||
|
modifier only_uninitialized { require(m_numOwners == 0); _; } |
||||||
|
|
||||||
|
// constructor - just pass on the owner array to the multiowned and |
||||||
|
// the limit to daylimit |
||||||
|
function initWallet(address[] memory _owners, uint _required, uint _daylimit) public only_uninitialized { |
||||||
|
initDaylimit(_daylimit); |
||||||
|
initMultiowned(_owners, _required); |
||||||
|
} |
||||||
|
|
||||||
|
// kills the contract sending everything to `_to`. |
||||||
|
function kill(address payable _to) onlymanyowners(keccak256(msg.data)) external { |
||||||
|
selfdestruct(_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 calldata _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 == address(0)) { |
||||||
|
created = create(_value, _data); |
||||||
|
} else { |
||||||
|
(bool success, bytes memory data) = _to.call.value(_value)(_data); |
||||||
|
require(success); |
||||||
|
} |
||||||
|
emit SingleTransact(msg.sender, _value, _to, _data, created); |
||||||
|
} else { |
||||||
|
// determine our operation hash. |
||||||
|
o_hash = keccak256(abi.encode(msg.data, block.number)); |
||||||
|
// store if it's new |
||||||
|
if (m_txs[o_hash].to == address(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)) { |
||||||
|
emit ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function create(uint _value, bytes memory _code) internal returns (address o_addr) { |
||||||
|
uint256 o_size; |
||||||
|
assembly { |
||||||
|
o_addr := create(_value, add(_code, 0x20), mload(_code)) |
||||||
|
o_size := extcodesize(o_addr) |
||||||
|
} |
||||||
|
require(o_size != 0); |
||||||
|
} |
||||||
|
|
||||||
|
// 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) public onlymanyowners(_h) returns (bool o_success) { |
||||||
|
if (m_txs[_h].to != address(0) || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) { |
||||||
|
address created; |
||||||
|
if (m_txs[_h].to == address(0)) { |
||||||
|
created = create(m_txs[_h].value, m_txs[_h].data); |
||||||
|
} else { |
||||||
|
(bool success, bytes memory data) = m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); |
||||||
|
require(success); |
||||||
|
} |
||||||
|
|
||||||
|
emit 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 false; |
||||||
|
|
||||||
|
PendingState memory 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) { |
||||||
|
emit 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 view 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 _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 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; |
||||||
|
} |
@ -1,7 +1,7 @@ |
|||||||
pragma solidity ^0.5.0; |
pragma solidity ^0.5.0; |
||||||
|
|
||||||
|
|
||||||
contract D { |
contract Kcalls { |
||||||
uint public n; |
uint public n; |
||||||
address public sender; |
address public sender; |
||||||
|
|
Loading…
Reference in new issue