aboutsummaryrefslogtreecommitdiffstats
path: root/test/contracts
diff options
context:
space:
mode:
authorchriseth <c@ethdev.com>2015-08-20 03:54:09 +0800
committerchriseth <c@ethdev.com>2015-08-20 03:54:09 +0800
commite985b285bee2bf500d0eb75579f868d69aeefb64 (patch)
treed08fa58cbe1729e49a3ed2d86085327cd33a68e0 /test/contracts
parent49f09719445f296ebe9ffed2b004f803e34b602c (diff)
downloaddexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar
dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar.gz
dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar.bz2
dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar.lz
dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar.xz
dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.tar.zst
dexon-solidity-e985b285bee2bf500d0eb75579f868d69aeefb64.zip
Move Solidity tests.
Diffstat (limited to 'test/contracts')
-rw-r--r--test/contracts/AuctionRegistrar.cpp497
-rw-r--r--test/contracts/CMakeLists.txt5
-rw-r--r--test/contracts/FixedFeeRegistrar.cpp242
-rw-r--r--test/contracts/Wallet.cpp668
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