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; |
||||
|
||||
|
||||
contract D { |
||||
contract Kcalls { |
||||
uint public n; |
||||
address public sender; |
||||
|
Loading…
Reference in new issue