diff options
author | chriseth <c@ethdev.com> | 2015-08-20 03:54:09 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2015-08-20 03:54:09 +0800 |
commit | e985b285bee2bf500d0eb75579f868d69aeefb64 (patch) | |
tree | d08fa58cbe1729e49a3ed2d86085327cd33a68e0 /test/contracts | |
parent | 49f09719445f296ebe9ffed2b004f803e34b602c (diff) | |
download | dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar.gz dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar.zst dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.zip |
Move Solidity tests.
Diffstat (limited to 'test/contracts')
-rw-r--r-- | test/contracts/AuctionRegistrar.cpp | 497 | ||||
-rw-r--r-- | test/contracts/CMakeLists.txt | 5 | ||||
-rw-r--r-- | test/contracts/FixedFeeRegistrar.cpp | 242 | ||||
-rw-r--r-- | test/contracts/Wallet.cpp | 668 |
4 files changed, 1412 insertions, 0 deletions
diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp new file mode 100644 index 00000000..7c5d9fa3 --- /dev/null +++ b/test/contracts/AuctionRegistrar.cpp @@ -0,0 +1,497 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Tests for a fixed fee registrar contract. + */ + +#include <string> +#include <tuple> +#include <boost/test/unit_test.hpp> +#include <libdevcore/Hash.h> +#include <libethcore/ABI.h> +#include <test/libsolidity/solidityExecutionFramework.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ + +static char const* registrarCode = R"DELIMITER( +//sol + +contract NameRegister { + function addr(string _name) constant returns (address o_owner); + function name(address _owner) constant returns (string o_name); +} + +contract Registrar is NameRegister { + event Changed(string indexed name); + event PrimaryChanged(string indexed name, address indexed addr); + + function owner(string _name) constant returns (address o_owner); + function addr(string _name) constant returns (address o_address); + function subRegistrar(string _name) constant returns (address o_subRegistrar); + function content(string _name) constant returns (bytes32 o_content); + + function name(address _owner) constant returns (string o_name); +} + +contract AuctionSystem { + event AuctionEnded(string indexed _name, address _winner); + event NewBid(string indexed _name, address _bidder, uint _value); + + /// Function that is called once an auction ends. + function onAuctionEnd(string _name) internal; + + function bid(string _name, address _bidder, uint _value) internal { + var auction = m_auctions[_name]; + if (auction.endDate > 0 && now > auction.endDate) + { + AuctionEnded(_name, auction.highestBidder); + onAuctionEnd(_name); + delete m_auctions[_name]; + return; + } + if (msg.value > auction.highestBid) + { + // new bid on auction + auction.secondHighestBid = auction.highestBid; + auction.sumOfBids += _value; + auction.highestBid = _value; + auction.highestBidder = _bidder; + auction.endDate = now + c_biddingTime; + + NewBid(_name, _bidder, _value); + } + } + + uint constant c_biddingTime = 7 days; + + struct Auction { + address highestBidder; + uint highestBid; + uint secondHighestBid; + uint sumOfBids; + uint endDate; + } + mapping(string => Auction) m_auctions; +} + +contract GlobalRegistrar is Registrar, AuctionSystem { + struct Record { + address owner; + address primary; + address subRegistrar; + bytes32 content; + uint renewalDate; + } + + uint constant c_renewalInterval = 1 years; + uint constant c_freeBytes = 12; + + function Registrar() { + // TODO: Populate with hall-of-fame. + } + + function() { + // prevent people from just sending funds to the registrar + __throw(); + } + + function onAuctionEnd(string _name) internal { + var auction = m_auctions[_name]; + var record = m_toRecord[_name]; + if (record.owner != 0) + record.owner.send(auction.sumOfBids - auction.highestBid / 100); + else + auction.highestBidder.send(auction.highestBid - auction.secondHighestBid); + record.renewalDate = now + c_renewalInterval; + record.owner = auction.highestBidder; + Changed(_name); + } + + function reserve(string _name) external { + if (bytes(_name).length == 0) + __throw(); + bool needAuction = requiresAuction(_name); + if (needAuction) + { + if (now < m_toRecord[_name].renewalDate) + __throw(); + bid(_name, msg.sender, msg.value); + } + else + { + Record record = m_toRecord[_name]; + if (record.owner != 0) + __throw(); + m_toRecord[_name].owner = msg.sender; + Changed(_name); + } + } + + function requiresAuction(string _name) internal returns (bool) { + return bytes(_name).length < c_freeBytes; + } + + modifier onlyrecordowner(string _name) { if (m_toRecord[_name].owner == msg.sender) _ } + + function transfer(string _name, address _newOwner) onlyrecordowner(_name) { + m_toRecord[_name].owner = _newOwner; + Changed(_name); + } + + function disown(string _name) onlyrecordowner(_name) { + if (stringsEqual(m_toName[m_toRecord[_name].primary], _name)) + { + PrimaryChanged(_name, m_toRecord[_name].primary); + m_toName[m_toRecord[_name].primary] = ""; + } + delete m_toRecord[_name]; + Changed(_name); + } + + function setAddress(string _name, address _a, bool _primary) onlyrecordowner(_name) { + m_toRecord[_name].primary = _a; + if (_primary) + { + PrimaryChanged(_name, _a); + m_toName[_a] = _name; + } + Changed(_name); + } + function setSubRegistrar(string _name, address _registrar) onlyrecordowner(_name) { + m_toRecord[_name].subRegistrar = _registrar; + Changed(_name); + } + function setContent(string _name, bytes32 _content) onlyrecordowner(_name) { + m_toRecord[_name].content = _content; + Changed(_name); + } + + function stringsEqual(string storage _a, string memory _b) internal returns (bool) { + bytes storage a = bytes(_a); + bytes memory b = bytes(_b); + if (a.length != b.length) + return false; + // @todo unroll this loop + for (uint i = 0; i < a.length; i ++) + if (a[i] != b[i]) + return false; + return true; + } + + function owner(string _name) constant returns (address) { return m_toRecord[_name].owner; } + function addr(string _name) constant returns (address) { return m_toRecord[_name].primary; } + function subRegistrar(string _name) constant returns (address) { return m_toRecord[_name].subRegistrar; } + function content(string _name) constant returns (bytes32) { return m_toRecord[_name].content; } + function name(address _addr) constant returns (string o_name) { return m_toName[_addr]; } + + function __throw() internal { + // workaround until we have "throw" + uint[] x; x[1]; + } + + mapping (address => string) m_toName; + mapping (string => Record) m_toRecord; +} +)DELIMITER"; + +static unique_ptr<bytes> s_compiledRegistrar; + +class AuctionRegistrarTestFramework: public ExecutionFramework +{ +protected: + void deployRegistrar() + { + if (!s_compiledRegistrar) + { + m_optimize = true; + m_compiler.reset(false, m_addStandardSources); + m_compiler.addSource("", registrarCode); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); + s_compiledRegistrar.reset(new bytes(m_compiler.getBytecode("GlobalRegistrar"))); + } + sendMessage(*s_compiledRegistrar, true); + BOOST_REQUIRE(!m_output.empty()); + } + + using ContractInterface = ExecutionFramework::ContractInterface; + class RegistrarInterface: public ContractInterface + { + public: + RegistrarInterface(ExecutionFramework& _framework): ContractInterface(_framework) {} + void reserve(string const& _name) + { + callString("reserve", _name); + } + u160 owner(string const& _name) + { + return callStringReturnsAddress("owner", _name); + } + void setAddress(string const& _name, u160 const& _address, bool _primary) + { + callStringAddressBool("setAddress", _name, _address, _primary); + } + u160 addr(string const& _name) + { + return callStringReturnsAddress("addr", _name); + } + string name(u160 const& _addr) + { + return callAddressReturnsString("name", _addr); + } + void setSubRegistrar(string const& _name, u160 const& _address) + { + callStringAddress("setSubRegistrar", _name, _address); + } + u160 subRegistrar(string const& _name) + { + return callStringReturnsAddress("subRegistrar", _name); + } + void setContent(string const& _name, h256 const& _content) + { + callStringBytes32("setContent", _name, _content); + } + h256 content(string const& _name) + { + return callStringReturnsBytes32("content", _name); + } + void transfer(string const& _name, u160 const& _target) + { + return callStringAddress("transfer", _name, _target); + } + void disown(string const& _name) + { + return callString("disown", _name); + } + }; + + u256 const m_biddingTime = u256(7 * 24 * 3600); + u256 const m_renewalInterval = u256(365 * 24 * 3600); +}; + +} + +/// This is a test suite that tests optimised code! +BOOST_FIXTURE_TEST_SUITE(SolidityAuctionRegistrar, AuctionRegistrarTestFramework) + +BOOST_AUTO_TEST_CASE(creation) +{ + deployRegistrar(); +} + +BOOST_AUTO_TEST_CASE(reserve) +{ + // Test that reserving works for long strings + deployRegistrar(); + vector<string> names{"abcabcabcabcabc", "defdefdefdefdef", "ghighighighighighighighighighighighighighighi"}; + m_sender = Address(0x123); + + RegistrarInterface registrar(*this); + + // should not work + registrar.reserve(""); + BOOST_CHECK_EQUAL(registrar.owner(""), u160(0)); + + for (auto const& name: names) + { + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), u160(0x123)); + } +} + +BOOST_AUTO_TEST_CASE(double_reserve_long) +{ + // Test that it is not possible to re-reserve from a different address. + deployRegistrar(); + string name = "abcabcabcabcabcabcabcabcabcabca"; + m_sender = Address(0x123); + RegistrarInterface registrar(*this); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), u160(0x123)); + + m_sender = Address(0x124); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), u160(0x123)); +} + +BOOST_AUTO_TEST_CASE(properties) +{ + // Test setting and retrieving the various properties works. + deployRegistrar(); + RegistrarInterface registrar(*this); + string names[] = {"abcaeouoeuaoeuaoeu", "defncboagufra,fui", "ghagpyajfbcuajouhaeoi"}; + size_t addr = 0x9872543; + for (string const& name: names) + { + addr++; + size_t sender = addr + 10007; + m_sender = Address(sender); + // setting by sender works + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), u160(sender)); + registrar.setAddress(name, addr, true); + BOOST_CHECK_EQUAL(registrar.addr(name), u160(addr)); + registrar.setSubRegistrar(name, addr + 20); + BOOST_CHECK_EQUAL(registrar.subRegistrar(name), u160(addr + 20)); + registrar.setContent(name, h256(u256(addr + 90))); + BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(addr + 90))); + + // but not by someone else + m_sender = Address(h256(addr + 10007 - 1)); + BOOST_CHECK_EQUAL(registrar.owner(name), u160(sender)); + registrar.setAddress(name, addr + 1, true); + BOOST_CHECK_EQUAL(registrar.addr(name), u160(addr)); + registrar.setSubRegistrar(name, addr + 20 + 1); + BOOST_CHECK_EQUAL(registrar.subRegistrar(name), u160(addr + 20)); + registrar.setContent(name, h256(u256(addr + 90 + 1))); + BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(addr + 90))); + } +} + +BOOST_AUTO_TEST_CASE(transfer) +{ + deployRegistrar(); + string name = "abcaoeguaoucaeoduceo"; + m_sender = Address(0x123); + RegistrarInterface registrar(*this); + registrar.reserve(name); + registrar.setContent(name, h256(u256(123))); + registrar.transfer(name, u160(555)); + BOOST_CHECK_EQUAL(registrar.owner(name), u160(555)); + BOOST_CHECK_EQUAL(registrar.content(name), h256(u256(123))); +} + +BOOST_AUTO_TEST_CASE(disown) +{ + deployRegistrar(); + string name = "abcaoeguaoucaeoduceo"; + m_sender = Address(0x123); + RegistrarInterface registrar(*this); + registrar.reserve(name); + registrar.setContent(name, h256(u256(123))); + registrar.setAddress(name, u160(124), true); + registrar.setSubRegistrar(name, u160(125)); + BOOST_CHECK_EQUAL(registrar.name(u160(124)), name); + + // someone else tries disowning + m_sender = Address(0x128); + registrar.disown(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); + + m_sender = Address(0x123); + registrar.disown(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0); + BOOST_CHECK_EQUAL(registrar.addr(name), 0); + BOOST_CHECK_EQUAL(registrar.subRegistrar(name), 0); + BOOST_CHECK_EQUAL(registrar.content(name), h256()); + BOOST_CHECK_EQUAL(registrar.name(u160(124)), ""); +} + +BOOST_AUTO_TEST_CASE(auction_simple) +{ + deployRegistrar(); + string name = "x"; + m_sender = Address(0x123); + RegistrarInterface registrar(*this); + // initiate auction + registrar.setNextValue(8); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0); + // "wait" until auction end + m_envInfo.setTimestamp(m_envInfo.timestamp() + m_biddingTime + 10); + // trigger auction again + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); +} + +BOOST_AUTO_TEST_CASE(auction_bidding) +{ + deployRegistrar(); + string name = "x"; + m_sender = Address(0x123); + RegistrarInterface registrar(*this); + // initiate auction + registrar.setNextValue(8); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0); + // overbid self + m_envInfo.setTimestamp(m_biddingTime - 10); + registrar.setNextValue(12); + registrar.reserve(name); + // another bid by someone else + m_sender = Address(0x124); + m_envInfo.setTimestamp(2 * m_biddingTime - 50); + registrar.setNextValue(13); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0); + // end auction by first bidder (which is not highest) trying to overbid again (too late) + m_sender = Address(0x123); + m_envInfo.setTimestamp(4 * m_biddingTime); + registrar.setNextValue(20); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0x124); +} + +BOOST_AUTO_TEST_CASE(auction_renewal) +{ + deployRegistrar(); + string name = "x"; + RegistrarInterface registrar(*this); + // register name by auction + m_sender = Address(0x123); + registrar.setNextValue(8); + registrar.reserve(name); + m_envInfo.setTimestamp(4 * m_biddingTime); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); + + // try to re-register before interval end + m_sender = Address(0x222); + registrar.setNextValue(80); + m_envInfo.setTimestamp(m_envInfo.timestamp() + m_renewalInterval - 1); + registrar.reserve(name); + m_envInfo.setTimestamp(m_envInfo.timestamp() + m_biddingTime); + // if there is a bug in the renewal logic, this would transfer the ownership to 0x222, + // but if there is no bug, this will initiate the auction, albeit with a zero bid + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); + + m_envInfo.setTimestamp(m_envInfo.timestamp() + 2); + registrar.setNextValue(80); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0x123); + m_envInfo.setTimestamp(m_envInfo.timestamp() + m_biddingTime + 2); + registrar.reserve(name); + BOOST_CHECK_EQUAL(registrar.owner(name), 0x222); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/contracts/CMakeLists.txt b/test/contracts/CMakeLists.txt new file mode 100644 index 00000000..3ceda13b --- /dev/null +++ b/test/contracts/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_policy(SET CMP0015 NEW) + +aux_source_directory(. SRCS) + +add_sources(${SRCS}) diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp new file mode 100644 index 00000000..ed2ecf0a --- /dev/null +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -0,0 +1,242 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Tests for a fixed fee registrar contract. + */ + +#include <string> +#include <tuple> +#include <boost/test/unit_test.hpp> +#include <libdevcore/Hash.h> +#include <test/libsolidity/solidityExecutionFramework.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ + +static char const* registrarCode = R"DELIMITER( +//sol FixedFeeRegistrar +// Simple global registrar with fixed-fee reservations. +// @authors: +// Gav Wood <g@ethdev.com> + +contract Registrar { + event Changed(string indexed name); + + function owner(string _name) constant returns (address o_owner); + function addr(string _name) constant returns (address o_address); + function subRegistrar(string _name) constant returns (address o_subRegistrar); + function content(string _name) constant returns (bytes32 o_content); +} + +contract FixedFeeRegistrar is Registrar { + struct Record { + address addr; + address subRegistrar; + bytes32 content; + address owner; + } + + modifier onlyrecordowner(string _name) { if (m_record(_name).owner == msg.sender) _ } + + function reserve(string _name) { + Record rec = m_record(_name); + if (rec.owner == 0 && msg.value >= c_fee) { + rec.owner = msg.sender; + Changed(_name); + } + } + function disown(string _name, address _refund) onlyrecordowner(_name) { + delete m_recordData[uint(sha3(_name)) / 8]; + _refund.send(c_fee); + Changed(_name); + } + function transfer(string _name, address _newOwner) onlyrecordowner(_name) { + m_record(_name).owner = _newOwner; + Changed(_name); + } + function setAddr(string _name, address _a) onlyrecordowner(_name) { + m_record(_name).addr = _a; + Changed(_name); + } + function setSubRegistrar(string _name, address _registrar) onlyrecordowner(_name) { + m_record(_name).subRegistrar = _registrar; + Changed(_name); + } + function setContent(string _name, bytes32 _content) onlyrecordowner(_name) { + m_record(_name).content = _content; + Changed(_name); + } + + function record(string _name) constant returns (address o_addr, address o_subRegistrar, bytes32 o_content, address o_owner) { + Record rec = m_record(_name); + o_addr = rec.addr; + o_subRegistrar = rec.subRegistrar; + o_content = rec.content; + o_owner = rec.owner; + } + function addr(string _name) constant returns (address) { return m_record(_name).addr; } + function subRegistrar(string _name) constant returns (address) { return m_record(_name).subRegistrar; } + function content(string _name) constant returns (bytes32) { return m_record(_name).content; } + function owner(string _name) constant returns (address) { return m_record(_name).owner; } + + Record[2**253] m_recordData; + function m_record(string _name) constant internal returns (Record storage o_record) { + return m_recordData[uint(sha3(_name)) / 8]; + } + uint constant c_fee = 69 ether; +} +)DELIMITER"; + +static unique_ptr<bytes> s_compiledRegistrar; + +class RegistrarTestFramework: public ExecutionFramework +{ +protected: + void deployRegistrar() + { + if (!s_compiledRegistrar) + { + m_optimize = true; + m_compiler.reset(false, m_addStandardSources); + m_compiler.addSource("", registrarCode); + ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed"); + s_compiledRegistrar.reset(new bytes(m_compiler.getBytecode("FixedFeeRegistrar"))); + } + sendMessage(*s_compiledRegistrar, true); + BOOST_REQUIRE(!m_output.empty()); + } + u256 const m_fee = u256("69000000000000000000"); +}; + +} + +/// This is a test suite that tests optimised code! +BOOST_FIXTURE_TEST_SUITE(SolidityFixedFeeRegistrar, RegistrarTestFramework) + +BOOST_AUTO_TEST_CASE(creation) +{ + deployRegistrar(); +} + +BOOST_AUTO_TEST_CASE(reserve) +{ + // Test that reserving works and fee is taken into account. + deployRegistrar(); + string name[] = {"abc", "def", "ghi"}; + m_sender = Address(0x123); + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name[0])) == encodeArgs()); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[0])) == encodeArgs(h256(0x123))); + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee + 1, encodeDyn(name[1])) == encodeArgs()); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[1])) == encodeArgs(h256(0x123))); + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee - 1, encodeDyn(name[2])) == encodeArgs()); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name[2])) == encodeArgs(h256(0))); +} + +BOOST_AUTO_TEST_CASE(double_reserve) +{ + // Test that it is not possible to re-reserve from a different address. + deployRegistrar(); + string name = "abc"; + m_sender = Address(0x123); + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(h256(0x123))); + + m_sender = Address(0x124); + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(h256(0x123))); +} + +BOOST_AUTO_TEST_CASE(properties) +{ + // Test setting and retrieving the various properties works. + deployRegistrar(); + string names[] = {"abc", "def", "ghi"}; + size_t addr = 0x9872543; + for (string const& name: names) + { + addr++; + size_t sender = addr + 10007; + m_sender = Address(sender); + // setting by sender works + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(u256(sender))); + BOOST_CHECK(callContractFunction("setAddr(string,address)", u256(0x40), u256(addr), u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("addr(string)", encodeDyn(name)) == encodeArgs(addr)); + BOOST_CHECK(callContractFunction("setSubRegistrar(string,address)", u256(0x40), addr + 20, u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("subRegistrar(string)", encodeDyn(name)) == encodeArgs(addr + 20)); + BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), addr + 90, u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(addr + 90)); + // but not by someone else + m_sender = Address(h256(addr + 10007 - 1)); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(sender)); + BOOST_CHECK(callContractFunction("setAddr(string,address)", u256(0x40), addr + 1, u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("addr(string)", encodeDyn(name)) == encodeArgs(addr)); + BOOST_CHECK(callContractFunction("setSubRegistrar(string,address)", u256(0x40), addr + 20 + 1, u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("subRegistrar(string)", encodeDyn(name)) == encodeArgs(addr + 20)); + BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), addr + 90 + 1, u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(addr + 90)); + } +} + +BOOST_AUTO_TEST_CASE(transfer) +{ + deployRegistrar(); + string name = "abc"; + m_sender = Address(0x123); + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); + BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), u256(123), u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("transfer(string,address)", u256(0x40), u256(555), u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(u256(555))); + BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(u256(123))); +} + +BOOST_AUTO_TEST_CASE(disown) +{ + deployRegistrar(); + string name = "abc"; + m_sender = Address(0x123); + BOOST_REQUIRE(callContractFunctionWithValue("reserve(string)", m_fee, encodeDyn(name)) == encodeArgs()); + BOOST_CHECK(callContractFunction("setContent(string,bytes32)", u256(0x40), u256(123), u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("setAddr(string,address)", u256(0x40), u256(124), u256(name.length()), name) == encodeArgs()); + BOOST_CHECK(callContractFunction("setSubRegistrar(string,address)", u256(0x40), u256(125), u256(name.length()), name) == encodeArgs()); + + BOOST_CHECK_EQUAL(m_state.balance(Address(0x124)), 0); + BOOST_CHECK(callContractFunction("disown(string,address)", u256(0x40), u256(0x124), name.size(), name) == encodeArgs()); + BOOST_CHECK_EQUAL(m_state.balance(Address(0x124)), m_fee); + + BOOST_CHECK(callContractFunction("owner(string)", encodeDyn(name)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("content(string)", encodeDyn(name)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("addr(string)", encodeDyn(name)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("subRegistrar(string)", encodeDyn(name)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp new file mode 100644 index 00000000..5f9febd4 --- /dev/null +++ b/test/contracts/Wallet.cpp @@ -0,0 +1,668 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2015 + * Tests for a (comparatively) complex multisig wallet contract. + */ + +#include <string> +#include <tuple> +#include <boost/test/unit_test.hpp> +#include <libdevcore/Hash.h> +#include <test/libsolidity/solidityExecutionFramework.h> + +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 <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. +contract multiowned { + + // TYPES + + // struct for the status of a pending operation. + struct PendingState { + uint yetNeeded; + uint ownersDone; + uint index; + } + + // EVENTS + + // 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); + + // 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 + + // 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(address[] _owners, uint _required) { + 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); + } + + function isOwner(address _addr) returns (bool) { + return m_ownerIndex[uint(_addr)] > 0; + } + + function hasConfirmed(bytes32 _operation, address _owner) 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; + if (pending.ownersDone & ownerIndexBit == 0) { + return false; + } else { + 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 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; + } + + // FIELDS + + // 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; + + // 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 { + + // MODIFIERS + + // simple modifier for daily limit. + modifier limitedDaily(uint _value) { + if (underLimit(_value)) + _ + } + + // METHODS + + // constructor - stores initial daily limit and records the present day's index. + function daylimit(uint _limit) { + 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; + } + // (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; + } + + // INTERNAL METHODS + + // 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; + } + // determines today's index. + function today() private constant returns (uint) { return now / 1 days; } + + // FIELDS + + uint public m_dailyLimit; + uint m_spentToday; + uint m_lastDay; +} + +// interface contract for multisig proxy contracts; see below for docs. +contract multisig { + + // EVENTS + + // 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); + // 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); + // Confirmation still needed for a transaction. + event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data); + + // FUNCTIONS + + // TODO: document + 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 { + + // TYPES + + // Transaction structure to remember details of transaction lest it need be saved for a later call. + struct Transaction { + address to; + uint value; + bytes data; + } + + // METHODS + + // constructor - just pass on the owner array to the multiowned and + // the limit to daylimit + function Wallet(address[] _owners, uint _required, uint _daylimit) + multiowned(_owners, _required) daylimit(_daylimit) { + } + + // 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) external onlyowner 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, block.number); + 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; + } + } + + // INTERNAL METHODS + + function clearPending() internal { + uint length = m_pendingIndex.length; + for (uint i = 0; i < length; ++i) + delete m_txs[m_pendingIndex[i]]; + super.clearPending(); + } + + // FIELDS + + // pending transactions we have at present. + mapping (bytes32 => Transaction) m_txs; +} +)DELIMITER"; + +static unique_ptr<bytes> s_compiledWallet; + +class WalletTestFramework: public ExecutionFramework +{ +protected: + void deployWallet( + u256 const& _value = 0, + vector<u256> const& _owners = vector<u256>{}, + u256 _required = 1, + u256 _dailyLimit = 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"))); + } + bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); + sendMessage(*s_compiledWallet + args, 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(initial_owners) +{ + vector<u256> owners{ + u256("0x00000000000000000000000042c56279432962a17176998a4747d1b4d6ed4367"), + u256("0x000000000000000000000000d4d4669f5ba9f4c27d38ef02a358c339b5560c47"), + u256("0x000000000000000000000000e6716f9544a56c530d868e4bfbacb172315bdead"), + u256("0x000000000000000000000000775e18be7a50a0abb8a4e82b1bd697d79f31fe04"), + u256("0x000000000000000000000000f4dd5c3794f1fd0cdc0327a83aa472609c806e99"), + u256("0x0000000000000000000000004c9113886af165b2de069d6e99430647e94a9fff"), + u256("0x0000000000000000000000003fb1cd2cd96c6d5c0b5eb3322d807b34482481d4") + }; + deployWallet(0, owners, 4, 2); + BOOST_CHECK(callContractFunction("m_numOwners()") == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("isOwner(address)", h256(m_sender, h256::AlignRight)) == encodeArgs(true)); + for (u256 const& owner: owners) + { + BOOST_CHECK(callContractFunction("isOwner(address)", owner) == 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("6244b4fa93f73e09db0ae52750095ca0364a76b72bc01723c97011fcb876cc9e"); + 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(revoke_addOwner) +{ + deployWallet(); + 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()); + // add a new owner + Address deployer = m_sender; + h256 opHash = sha3(FixedHash<4>(dev::sha3("addOwner(address)")).asBytes() + h256(0x33).asBytes()); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(false)); + m_sender = Address(0x12); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(false)); + // revoke one confirmation + m_sender = deployer; + BOOST_REQUIRE(callContractFunction("revoke(bytes32)", opHash) == encodeArgs()); + m_sender = Address(0x13); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(false)); + m_sender = Address(0x14); + BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x33)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x33)) == encodeArgs(true)); +} + +BOOST_AUTO_TEST_CASE(revoke_transaction) +{ + 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()); + // create a transaction + Address deployer = m_sender; + h256 opHash("6244b4fa93f73e09db0ae52750095ca0364a76b72bc01723c97011fcb876cc9e"); + 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(0x12); + BOOST_REQUIRE(callContractFunction("revoke(bytes32)", opHash) == encodeArgs()); + m_sender = deployer; + 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("m_dailyLimit()") == encodeArgs(u256(0))); + BOOST_REQUIRE(callContractFunction("setDailyLimit(uint256)", h256(100)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("m_dailyLimit()") == encodeArgs(u256(100))); + 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); +} + +BOOST_AUTO_TEST_CASE(daylimit_constructor) +{ + deployWallet(200, {}, 1, 20); + BOOST_REQUIRE(callContractFunction("m_dailyLimit()") == encodeArgs(u256(20))); + BOOST_REQUIRE(callContractFunction("setDailyLimit(uint256)", h256(30)) == encodeArgs()); + BOOST_REQUIRE(callContractFunction("m_dailyLimit()") == encodeArgs(u256(30))); +} + +//@todo test data calls + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces |