diff --git a/TestHelper.cpp b/TestHelper.cpp index f7da0238e..733ccb6d0 100644 --- a/TestHelper.cpp +++ b/TestHelper.cpp @@ -344,6 +344,18 @@ void ImportTest::exportTest(bytes const& _output, State const& _statePost) m_TestObject["out"] = (_output.size() > 4096 && !Options::get().fulloutput) ? "#" + toString(_output.size()) : toHex(_output, 2, HexPrefix::Add); + // compare expected output with post output + if (m_TestObject.count("expectOut") > 0) + { + std::string warning = "Check State: Error! Unexpected output: " + m_TestObject["out"].get_str() + " Expected: " + m_TestObject["expectOut"].get_str(); + if (Options::get().checkState) + BOOST_CHECK_MESSAGE((m_TestObject["out"].get_str() == m_TestObject["expectOut"].get_str()), warning); + else + BOOST_WARN_MESSAGE((m_TestObject["out"].get_str() == m_TestObject["expectOut"].get_str()), warning); + + m_TestObject.erase(m_TestObject.find("expectOut")); + } + // export logs m_TestObject["logs"] = exportLog(_statePost.pending().size() ? _statePost.log(0) : LogEntries()); diff --git a/libsolidity/Assembly.cpp b/libsolidity/Assembly.cpp index ccc4bf811..fd4bbcf6d 100644 --- a/libsolidity/Assembly.cpp +++ b/libsolidity/Assembly.cpp @@ -105,8 +105,8 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr n = make_shared("source"); AssemblyItems items = compileContract(sourceCode); vector locations = - vector(11, SourceLocation(2, 75, n)) + - vector(12, SourceLocation(20, 72, n)) + + vector(17, SourceLocation(2, 75, n)) + + vector(14, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector(4, SourceLocation(58, 67, n)) + vector(3, SourceLocation(20, 72, n)); diff --git a/libsolidity/SolidityEndToEndTest.cpp b/libsolidity/SolidityEndToEndTest.cpp index 89ed81e23..f12abd48e 100644 --- a/libsolidity/SolidityEndToEndTest.cpp +++ b/libsolidity/SolidityEndToEndTest.cpp @@ -566,16 +566,16 @@ BOOST_AUTO_TEST_CASE(strings) BOOST_AUTO_TEST_CASE(empty_string_on_stack) { - char const* sourceCode = "contract test {\n" - " function run(bytes0 empty, uint8 inp) returns(uint16 a, bytes0 b, bytes4 c) {\n" - " var x = \"abc\";\n" - " var y = \"\";\n" - " var z = inp;\n" - " a = z; b = y; c = x;" - " }\n" - "}\n"; + char const* sourceCode = R"( + contract test { + function run() external returns(bytes2 ret) { + var y = ""; + ret = y; + } + } + )"; compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("run(bytes0,uint8)", string(), byte(0x02)) == encodeArgs(0x2, string(""), string("abc\0"))); + BOOST_CHECK(callContractFunction("run()") == encodeArgs(byte(0x00))); } BOOST_AUTO_TEST_CASE(inc_dec_operators) @@ -2396,7 +2396,7 @@ BOOST_AUTO_TEST_CASE(event_really_lots_of_data) callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(10, 4, 15) + FixedHash<4>(dev::sha3("deposit()")).asBytes()); + BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 4) + FixedHash<4>(dev::sha3("deposit()")).asBytes()); BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(uint256,bytes,uint256)"))); } @@ -2420,7 +2420,7 @@ BOOST_AUTO_TEST_CASE(event_really_lots_of_data_from_storage) callContractFunction("deposit()"); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(10, 3, 15) + asBytes("ABC")); + BOOST_CHECK(m_logs[0].data == encodeArgs(10, 0x60, 15, 3) + asBytes("ABC")); BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(uint256,bytes,uint256)"))); } @@ -2531,6 +2531,27 @@ BOOST_AUTO_TEST_CASE(sha3_with_bytes) BOOST_CHECK(callContractFunction("foo()") == encodeArgs(true)); } +BOOST_AUTO_TEST_CASE(iterated_sha3_with_bytes) +{ + char const* sourceCode = R"( + contract c { + bytes data; + function foo() returns (bytes32) + { + data.length = 3; + data[0] = "x"; + data[1] = "y"; + data[2] = "z"; + return sha3("b", sha3(data), "a"); + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("foo()") == encodeArgs( + u256(dev::sha3(bytes{'b'} + dev::sha3("xyz").asBytes() + bytes{'a'})) + )); +} + BOOST_AUTO_TEST_CASE(generic_call) { char const* sourceCode = R"**( @@ -3786,30 +3807,6 @@ BOOST_AUTO_TEST_CASE(packed_storage_structs_delete) BOOST_CHECK(m_state.storage(m_contractAddress).empty()); } -BOOST_AUTO_TEST_CASE(packed_storage_structs_with_bytes0) -{ - char const* sourceCode = R"( - contract C { - struct str { uint8 a; bytes0 b; uint8 c; } - uint8 a; - bytes0 x; - uint8 b; - str data; - function test() returns (bool) { - a = 2; - b = 3; - data.a = 4; - data.c = 5; - delete x; - delete data.b; - return a == 2 && b == 3 && data.a == 4 && data.c == 5; - } - } - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); -} - BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_first) { char const* sourceCode = R"( @@ -4209,6 +4206,32 @@ BOOST_AUTO_TEST_CASE(failing_send) BOOST_REQUIRE(callContractFunction("callHelper(address)", c_helperAddress) == encodeArgs(true, 20)); } +BOOST_AUTO_TEST_CASE(reusing_memory) +{ + // Invoke some features that use memory and test that they do not interfere with each other. + char const* sourceCode = R"( + contract Helper { + uint public flag; + function Helper(uint x) { + flag = x; + } + } + contract Main { + mapping(uint => uint) map; + function f(uint x) returns (uint) { + map[x] = x; + return (new Helper(uint(sha3(this.g(map[x]))))).flag(); + } + function g(uint a) returns (uint) + { + return map[a]; + } + } + )"; + compileAndRun(sourceCode, 0, "Main"); + BOOST_REQUIRE(callContractFunction("f(uint256)", 0x34) == encodeArgs(dev::sha3(dev::toBigEndian(u256(0x34))))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/libsolidity/SolidityNameAndTypeResolution.cpp b/libsolidity/SolidityNameAndTypeResolution.cpp index 73bbcb162..3948a4a23 100644 --- a/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/libsolidity/SolidityNameAndTypeResolution.cpp @@ -558,16 +558,6 @@ BOOST_AUTO_TEST_CASE(function_external_call_not_allowed_conversion) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } -// todo delete when implemented -BOOST_AUTO_TEST_CASE(arrays_in_internal_functions) -{ - char const* text = R"( - contract Test { - function foo(address[] addresses) {} - })"; - BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); -} - BOOST_AUTO_TEST_CASE(function_internal_allowed_conversion) { char const* text = R"( @@ -1579,7 +1569,6 @@ BOOST_AUTO_TEST_CASE(test_fromElementaryTypeName) BOOST_CHECK(*Type::fromElementaryTypeName(Token::UInt256) == *make_shared(256, IntegerType::Modifier::Unsigned)); BOOST_CHECK(*Type::fromElementaryTypeName(Token::Byte) == *make_shared(1)); - BOOST_CHECK(*Type::fromElementaryTypeName(Token::Bytes0) == *make_shared(0)); BOOST_CHECK(*Type::fromElementaryTypeName(Token::Bytes1) == *make_shared(1)); BOOST_CHECK(*Type::fromElementaryTypeName(Token::Bytes2) == *make_shared(2)); BOOST_CHECK(*Type::fromElementaryTypeName(Token::Bytes3) == *make_shared(3)); @@ -1666,16 +1655,6 @@ BOOST_AUTO_TEST_CASE(local_const_variable) BOOST_CHECK_THROW(parseTextAndResolveNames(text), ParserError); } -BOOST_AUTO_TEST_CASE(bytes0_array) -{ - char const* text = R"( - contract Foo { - bytes0[] illegalArray; - } - )"; - BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); -} - BOOST_AUTO_TEST_CASE(overloaded_function_cannot_resolve) { char const* sourceCode = R"( diff --git a/libsolidity/SolidityWallet.cpp b/libsolidity/SolidityWallet.cpp new file mode 100644 index 000000000..09820b87b --- /dev/null +++ b/libsolidity/SolidityWallet.cpp @@ -0,0 +1,491 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2015 + * Tests for a (comparatively) complex multisig wallet contract. + */ + +#include +#include +#include +#include +#include + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +static char const* walletCode = R"DELIMITER( +//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. +contract multiowned { + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + // this contract only has five 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); + // constructor is given number of sigs required to do protected "onlymanyowners" transactions + // as well as the selection of addresses capable of confirming them. + function multiowned() { + m_required = 1; + m_numOwners = 1; + m_owners[m_numOwners] = uint(msg.sender); + m_ownerIndex[uint(msg.sender)] = m_numOwners; + } + // 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 (confirmed(_operation)) + _ + } + // 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); + } + } + function confirmed(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; + } + } + } + // 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 reorganizeOwners() private returns (bool) { + 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; + } + } + } + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + if (m_pendingIndex[i] != 0) + delete m_pending[m_pendingIndex[i]]; + delete m_pendingIndex; + } + function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external { + if (_newRequired > m_numOwners) return; + m_required = _newRequired; + clearPending(); + RequirementChanged(_newRequired); + } + function isOwner(address _addr) returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + // the number of owners that must confirm the same operation before it is run. + uint m_required; + // pointer used to find a free slot in m_owners + uint m_numOwners; + // 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; +} + +// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable) +// on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method +// uses is specified in the modifier. +contract daylimit is multiowned { + // constructor - just records the present day's index. + function daylimit() { + 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; + } + // (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today. + function resetSpentToday() onlymanyowners(sha3(msg.data)) external { + m_spentToday = 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. + if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) { + m_spentToday += _value; + return true; + } + return false; + } + // simple modifier for daily limit. + modifier limitedDaily(uint _value) { + if (underLimit(_value)) + _ + } + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + uint m_spentToday; + uint m_dailyLimit; + uint m_lastDay; +} +// interface contract for multisig proxy contracts; see below for docs. +contract multisig { + event Deposit(address from, uint value); + event SingleTransact(address owner, uint value, address to, bytes data); + event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data); + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); + function changeOwner(address _from, address _to) external; + function execute(address _to, uint _value, bytes _data) external returns (bytes32); + function confirm(bytes32 _h) returns (bool); +} +// usage: +// bytes32 h = Wallet(w).from(oneOwner).transact(to, value, data); +// Wallet(w).from(anotherOwner).confirm(h); +contract Wallet is multisig, multiowned, daylimit { + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + // logged events: + // 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); + // constructor - just pass on the owner arra to the multiowned. + event Created(); + function Wallet() { + Created(); + } + // kills the contract sending everything to `_to`. + function kill(address _to) onlymanyowners(sha3(msg.data)) external { + suicide(_to); + } + // gets called when no other function matches + function() { + // just being sent some cash? + if (msg.value > 0) + Deposit(msg.sender, msg.value); + } + // Outside-visible transact entry point. Executes transacion 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) onlyowner external returns (bytes32 _r) { + // first, take the opportunity to check that we're under the daily limit. + if (underLimit(_value)) { + SingleTransact(msg.sender, _value, _to, _data); + // yes - just execute the call. + _to.call.value(_value)(_data); + return 0; + } + // determine our operation hash. + _r = sha3(msg.data); + if (!confirm(_r) && m_txs[_r].to == 0) { + m_txs[_r].to = _to; + m_txs[_r].value = _value; + m_txs[_r].data = _data; + ConfirmationNeeded(_r, msg.sender, _value, _to, _data); + } + } + // 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) { + if (m_txs[_h].to != 0) { + m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data); + MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data); + delete m_txs[_h]; + return true; + } + } + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + delete m_txs[m_pendingIndex[i]]; + super.clearPending(); + } + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; +} +)DELIMITER"; + +static unique_ptr s_compiledWallet; + +class WalletTestFramework: public ExecutionFramework +{ +protected: + void deployWallet(u256 const& _value = 0) + { + if (!s_compiledWallet) + { + m_optimize = true; + m_compiler.reset(false, m_addStandardSources); + m_compiler.addSource("", walletCode); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); + s_compiledWallet.reset(new bytes(m_compiler.getBytecode("Wallet"))); + } + sendMessage(*s_compiledWallet, true, _value); + BOOST_REQUIRE(!m_output.empty()); + } +}; + +/// This is a test suite that tests optimised code! +BOOST_FIXTURE_TEST_SUITE(SolidityWallet, WalletTestFramework) + +BOOST_AUTO_TEST_CASE(creation) +{ + deployWallet(200); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(m_sender, h256::AlignRight)) == encodeArgs(true)); + BOOST_REQUIRE(callContractFunction("isOwner(address)", ~h256(m_sender, h256::AlignRight)) == encodeArgs(false)); +} + +BOOST_AUTO_TEST_CASE(add_owners) +{ + deployWallet(200); + Address originalOwner = m_sender; + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true)); + // now let the new owner add someone + m_sender = Address(0x12); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true)); + // and check that a non-owner cannot add a new owner + m_sender = Address(0x50); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x20)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x20)) == encodeArgs(false)); + // finally check that all the owners are there + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(originalOwner, h256::AlignRight)) == encodeArgs(true)); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true)); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true)); +} + +BOOST_AUTO_TEST_CASE(change_owners) +{ + deployWallet(200); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true)); + BOOST_REQUIRE(callContractFunction("changeOwner(address,address)", h256(0x12), h256(0x13)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(false)); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true)); +} + +BOOST_AUTO_TEST_CASE(remove_owner) +{ + deployWallet(200); + // add 10 owners + for (unsigned i = 0; i < 10; ++i) + { + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12 + i)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true)); + } + // check they are there again + for (unsigned i = 0; i < 10; ++i) + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true)); + // remove the odd owners + for (unsigned i = 0; i < 10; ++i) + if (i % 2 == 1) + BOOST_REQUIRE(callContractFunction("removeOwner(address)", h256(0x12 + i)) == encodeArgs()); + // check the result + for (unsigned i = 0; i < 10; ++i) + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(i % 2 == 0)); + // add them again + for (unsigned i = 0; i < 10; ++i) + if (i % 2 == 1) + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12 + i)) == encodeArgs()); + // check everyone is there + for (unsigned i = 0; i < 10; ++i) + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true)); +} + +BOOST_AUTO_TEST_CASE(multisig_value_transfer) +{ + deployWallet(200); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs()); + // 4 owners, set required to 3 + BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); + // check that balance is and stays zero at destination address + h256 opHash("f916231db11c12e0142dc51f23632bc655de87c63f83fc928c443e90f7aa364a"); + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); + m_sender = Address(0x12); + BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); + m_sender = Address(0x13); + BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); + m_sender = Address(0x14); + BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash)); + // now it should go through + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 100); +} + +BOOST_AUTO_TEST_CASE(daylimit) +{ + deployWallet(200); + BOOST_REQUIRE(callContractFunction("setDailyLimit(uint256)", h256(100)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs()); + // 4 owners, set required to 3 + BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); + + // try to send tx over daylimit + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); + m_sender = Address(0x12); + BOOST_REQUIRE( + callContractFunction("execute(address,uint256,bytes)", h256(0x05), 150, 0x60, 0x00) != + encodeArgs(u256(0)) + ); + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); + // try to send tx under daylimit by stranger + m_sender = Address(0x77); + BOOST_REQUIRE( + callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == + encodeArgs(u256(0)) + ); + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0); + // now send below limit by owner + m_sender = Address(0x12); + BOOST_REQUIRE( + callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == + encodeArgs(u256(0)) + ); + BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 90); +} + +//@todo test data calls + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/libsolidity/solidityExecutionFramework.h b/libsolidity/solidityExecutionFramework.h index e81b4d7b5..44590b1c8 100644 --- a/libsolidity/solidityExecutionFramework.h +++ b/libsolidity/solidityExecutionFramework.h @@ -148,6 +148,8 @@ protected: { m_state.addBalance(m_sender, _value); // just in case eth::Executive executive(m_state, eth::LastHashes(), 0); + eth::ExecutionResult res; + executive.setResultRecipient(res); eth::Transaction t = _isCreation ? eth::Transaction(_value, m_gasPrice, m_gas, _data, 0, KeyPair::create().sec()) : @@ -176,7 +178,7 @@ protected: m_state.noteSending(m_sender); executive.finalize(); m_gasUsed = executive.gasUsed(); - m_output = executive.out().toVector(); + m_output = std::move(res.output); // FIXME: Looks like Framework needs ExecutiveResult embedded m_logs = executive.logs(); }