/*
	This file is part of cpp-ethereum.
	cpp-ethereum is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	cpp-ethereum is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with cpp-ethereum.  If not, see .
*/
/**
 * @author Christian 
 * @date 2015
 * Tests for a (comparatively) complex multisig wallet contract.
 */
#include 
#include 
#include 
#include 
#include 
using namespace std;
namespace dev
{
namespace solidity
{
namespace test
{
static char const* walletCode = R"DELIMITER(
//sol Wallet
// Multi-sig, daily-limited account proxy/wallet.
// @authors:
// Gav Wood 
// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a
// single, or, crucially, each of a number of, designated owners.
// usage:
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
contract multiowned {
	// struct for the status of a pending operation.
	struct PendingState {
		uint yetNeeded;
		uint ownersDone;
		uint index;
	}
	// this contract only has five types of events: it can accept a confirmation, in which case
	// we record owner and operation (hash) alongside it.
	event Confirmation(address owner, bytes32 operation);
	event Revoke(address owner, bytes32 operation);
	// some others are in the case of an owner changing.
	event OwnerChanged(address oldOwner, address newOwner);
	event OwnerAdded(address newOwner);
	event OwnerRemoved(address oldOwner);
	// the last one is emitted if the required signatures change
	event RequirementChanged(uint newRequirement);
	// constructor is given number of sigs required to do protected "onlymanyowners" transactions
	// as well as the selection of addresses capable of confirming them.
	function multiowned() {
		m_required = 1;
		m_numOwners = 1;
		m_owners[m_numOwners] = uint(msg.sender);
		m_ownerIndex[uint(msg.sender)] = m_numOwners;
	}
	// simple single-sig function modifier.
	modifier onlyowner {
		if (isOwner(msg.sender))
			_
	}
	// multi-sig function modifier: the operation must have an intrinsic hash in order
	// that later attempts can be realised as the same underlying operation and
	// thus count as confirmations.
	modifier onlymanyowners(bytes32 _operation) {
		if (confirmed(_operation))
			_
	}
	// Revokes a prior confirmation of the given operation
	function revoke(bytes32 _operation) external {
		uint ownerIndex = m_ownerIndex[uint(msg.sender)];
		// make sure they're an owner
		if (ownerIndex == 0) return;
		uint ownerIndexBit = 2**ownerIndex;
		var pending = m_pending[_operation];
		if (pending.ownersDone & ownerIndexBit > 0) {
			pending.yetNeeded++;
			pending.ownersDone -= ownerIndexBit;
			Revoke(msg.sender, _operation);
		}
	}
	function confirmed(bytes32 _operation) internal returns (bool) {
		// determine what index the present sender is:
		uint ownerIndex = m_ownerIndex[uint(msg.sender)];
		// make sure they're an owner
		if (ownerIndex == 0) return;
		var pending = m_pending[_operation];
		// if we're not yet working on this operation, switch over and reset the confirmation status.
		if (pending.yetNeeded == 0) {
			// reset count of confirmations needed.
			pending.yetNeeded = m_required;
			// reset which owners have confirmed (none) - set our bitmap to 0.
			pending.ownersDone = 0;
			pending.index = m_pendingIndex.length++;
			m_pendingIndex[pending.index] = _operation;
		}
		// determine the bit to set for this owner.
		uint ownerIndexBit = 2**ownerIndex;
		// make sure we (the message sender) haven't confirmed this operation previously.
		if (pending.ownersDone & ownerIndexBit == 0) {
			Confirmation(msg.sender, _operation);
			// ok - check if count is enough to go ahead.
			if (pending.yetNeeded <= 1) {
				// enough confirmations: reset and run interior.
				delete m_pendingIndex[m_pending[_operation].index];
				delete m_pending[_operation];
				return true;
			}
			else
			{
				// not enough: record that this owner in particular confirmed.
				pending.yetNeeded--;
				pending.ownersDone |= ownerIndexBit;
			}
		}
	}
	// Replaces an owner `_from` with another `_to`.
	function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
		if (isOwner(_to)) return;
		uint ownerIndex = m_ownerIndex[uint(_from)];
		if (ownerIndex == 0) return;
		clearPending();
		m_owners[ownerIndex] = uint(_to);
		m_ownerIndex[uint(_from)] = 0;
		m_ownerIndex[uint(_to)] = ownerIndex;
		OwnerChanged(_from, _to);
	}
	function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
		if (isOwner(_owner)) return;
		clearPending();
		if (m_numOwners >= c_maxOwners)
			reorganizeOwners();
		if (m_numOwners >= c_maxOwners)
			return;
		m_numOwners++;
		m_owners[m_numOwners] = uint(_owner);
		m_ownerIndex[uint(_owner)] = m_numOwners;
		OwnerAdded(_owner);
	}
	function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
		uint ownerIndex = m_ownerIndex[uint(_owner)];
		if (ownerIndex == 0) return;
		if (m_required > m_numOwners - 1) return;
		m_owners[ownerIndex] = 0;
		m_ownerIndex[uint(_owner)] = 0;
		clearPending();
		reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
		OwnerRemoved(_owner);
	}
	function reorganizeOwners() private returns (bool) {
		uint free = 1;
		while (free < m_numOwners)
		{
			while (free < m_numOwners && m_owners[free] != 0) free++;
			while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
			if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
			{
				m_owners[free] = m_owners[m_numOwners];
				m_ownerIndex[m_owners[free]] = free;
				m_owners[m_numOwners] = 0;
			}
		}
	}
	function clearPending() internal {
		uint length = m_pendingIndex.length;
		for (uint i = 0; i < length; ++i)
			if (m_pendingIndex[i] != 0)
				delete m_pending[m_pendingIndex[i]];
		delete m_pendingIndex;
	}
	function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
		if (_newRequired > m_numOwners) return;
		m_required = _newRequired;
		clearPending();
		RequirementChanged(_newRequired);
	}
	function isOwner(address _addr) returns (bool) {
		return m_ownerIndex[uint(_addr)] > 0;
	}
	// the number of owners that must confirm the same operation before it is run.
	uint m_required;
	// pointer used to find a free slot in m_owners
	uint m_numOwners;
	// list of owners
	uint[256] m_owners;
	uint constant c_maxOwners = 250;
	// index on the list of owners to allow reverse lookup
	mapping(uint => uint) m_ownerIndex;
	// the ongoing operations.
	mapping(bytes32 => PendingState) m_pending;
	bytes32[] m_pendingIndex;
}
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
// on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method
// uses is specified in the modifier.
contract daylimit is multiowned {
	// constructor - just records the present day's index.
	function daylimit() {
		m_lastDay = today();
	}
	// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
	function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
		m_dailyLimit = _newLimit;
	}
	// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
	function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
		m_spentToday = 0;
	}
	// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
	// returns true. otherwise just returns false.
	function underLimit(uint _value) internal onlyowner returns (bool) {
		// reset the spend limit if we're on a different day to last time.
		if (today() > m_lastDay) {
			m_spentToday = 0;
			m_lastDay = today();
		}
		// check to see if there's enough left - if so, subtract and return true.
		if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
			m_spentToday += _value;
			return true;
		}
		return false;
	}
	// simple modifier for daily limit.
	modifier limitedDaily(uint _value) {
		if (underLimit(_value))
			_
	}
	// determines today's index.
	function today() private constant returns (uint) { return now / 1 days; }
	uint m_spentToday;
	uint m_dailyLimit;
	uint m_lastDay;
}
// interface contract for multisig proxy contracts; see below for docs.
contract multisig {
	event Deposit(address from, uint value);
	event SingleTransact(address owner, uint value, address to, bytes data);
	event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
	event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
	function changeOwner(address _from, address _to) external;
	function execute(address _to, uint _value, bytes _data) external returns (bytes32);
	function confirm(bytes32 _h) returns (bool);
}
// usage:
// bytes32 h = Wallet(w).from(oneOwner).transact(to, value, data);
// Wallet(w).from(anotherOwner).confirm(h);
contract Wallet is multisig, multiowned, daylimit {
	// Transaction structure to remember details of transaction lest it need be saved for a later call.
	struct Transaction {
		address to;
		uint value;
		bytes data;
	}
	// logged events:
	// Funds has arrived into the wallet (record how much).
	event Deposit(address from, uint value);
	// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
	event SingleTransact(address owner, uint value, address to, bytes data);
	// constructor - just pass on the owner arra to the multiowned.
	event Created();
	function Wallet() {
		Created();
	}
	// kills the contract sending everything to `_to`.
	function kill(address _to) onlymanyowners(sha3(msg.data)) external {
		suicide(_to);
	}
	// gets called when no other function matches
	function() {
		// just being sent some cash?
		if (msg.value > 0)
			Deposit(msg.sender, msg.value);
	}
	// Outside-visible transact entry point. Executes transacion immediately if below daily spend limit.
	// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
	// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
	// and _data arguments). They still get the option of using them if they want, anyways.
	function execute(address _to, uint _value, bytes _data) onlyowner external returns (bytes32 _r) {
		// first, take the opportunity to check that we're under the daily limit.
		if (underLimit(_value)) {
			SingleTransact(msg.sender, _value, _to, _data);
			// yes - just execute the call.
			_to.call.value(_value)(_data);
			return 0;
		}
		// determine our operation hash.
		_r = sha3(msg.data);
		if (!confirm(_r) && m_txs[_r].to == 0) {
			m_txs[_r].to = _to;
			m_txs[_r].value = _value;
			m_txs[_r].data = _data;
			ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
		}
	}
	// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
	// to determine the body of the transaction from the hash provided.
	function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
		if (m_txs[_h].to != 0) {
			m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
			MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
			delete m_txs[_h];
			return true;
		}
	}
	function clearPending() internal {
		uint length = m_pendingIndex.length;
		for (uint i = 0; i < length; ++i)
			delete m_txs[m_pendingIndex[i]];
		super.clearPending();
	}
	// pending transactions we have at present.
	mapping (bytes32 => Transaction) m_txs;
}
)DELIMITER";
static unique_ptr s_compiledWallet;
class WalletTestFramework: public ExecutionFramework
{
protected:
	void deployWallet(u256 const& _value = 0)
	{
		if (!s_compiledWallet)
		{
			m_optimize = true;
			m_compiler.reset(false, m_addStandardSources);
			m_compiler.addSource("", walletCode);
			ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed");
			s_compiledWallet.reset(new bytes(m_compiler.getBytecode("Wallet")));
		}
		sendMessage(*s_compiledWallet, true, _value);
		BOOST_REQUIRE(!m_output.empty());
	}
};
/// This is a test suite that tests optimised code!
BOOST_FIXTURE_TEST_SUITE(SolidityWallet, WalletTestFramework)
BOOST_AUTO_TEST_CASE(creation)
{
	deployWallet(200);
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(m_sender, h256::AlignRight)) == encodeArgs(true));
	BOOST_REQUIRE(callContractFunction("isOwner(address)", ~h256(m_sender, h256::AlignRight)) == encodeArgs(false));
}
BOOST_AUTO_TEST_CASE(add_owners)
{
	deployWallet(200);
	Address originalOwner = m_sender;
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true));
	// now let the new owner add someone
	m_sender = Address(0x12);
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true));
	// and check that a non-owner cannot add a new owner
	m_sender = Address(0x50);
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x20)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x20)) == encodeArgs(false));
	// finally check that all the owners are there
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(originalOwner, h256::AlignRight)) == encodeArgs(true));
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true));
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true));
}
BOOST_AUTO_TEST_CASE(change_owners)
{
	deployWallet(200);
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(true));
	BOOST_REQUIRE(callContractFunction("changeOwner(address,address)", h256(0x12), h256(0x13)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12)) == encodeArgs(false));
	BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x13)) == encodeArgs(true));
}
BOOST_AUTO_TEST_CASE(remove_owner)
{
	deployWallet(200);
	// add 10 owners
	for (unsigned i = 0; i < 10; ++i)
	{
		BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12 + i)) == encodeArgs());
		BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true));
	}
	// check they are there again
	for (unsigned i = 0; i < 10; ++i)
		BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true));
	// remove the odd owners
	for (unsigned i = 0; i < 10; ++i)
		if (i % 2 == 1)
			BOOST_REQUIRE(callContractFunction("removeOwner(address)", h256(0x12 + i)) == encodeArgs());
	// check the result
	for (unsigned i = 0; i < 10; ++i)
		BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(i % 2 == 0));
	// add them again
	for (unsigned i = 0; i < 10; ++i)
		if (i % 2 == 1)
			BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12 + i)) == encodeArgs());
	// check everyone is there
	for (unsigned i = 0; i < 10; ++i)
		BOOST_REQUIRE(callContractFunction("isOwner(address)", h256(0x12 + i)) == encodeArgs(true));
}
BOOST_AUTO_TEST_CASE(multisig_value_transfer)
{
	deployWallet(200);
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs());
	// 4 owners, set required to 3
	BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs());
	// check that balance is and stays zero at destination address
	h256 opHash("f916231db11c12e0142dc51f23632bc655de87c63f83fc928c443e90f7aa364a");
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0);
	m_sender = Address(0x12);
	BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash));
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0);
	m_sender = Address(0x13);
	BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash));
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0);
	m_sender = Address(0x14);
	BOOST_REQUIRE(callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00) == encodeArgs(opHash));
	// now it should go through
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 100);
}
BOOST_AUTO_TEST_CASE(daylimit)
{
	deployWallet(200);
	BOOST_REQUIRE(callContractFunction("setDailyLimit(uint256)", h256(100)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x12)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x13)) == encodeArgs());
	BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(0x14)) == encodeArgs());
	// 4 owners, set required to 3
	BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs());
	// try to send tx over daylimit
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0);
	m_sender = Address(0x12);
	BOOST_REQUIRE(
		callContractFunction("execute(address,uint256,bytes)", h256(0x05), 150, 0x60, 0x00) !=
		encodeArgs(u256(0))
	);
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0);
	// try to send tx under daylimit by stranger
	m_sender = Address(0x77);
	BOOST_REQUIRE(
		callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) ==
		encodeArgs(u256(0))
	);
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 0);
	// now send below limit by owner
	m_sender = Address(0x12);
	BOOST_REQUIRE(
		callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) ==
		encodeArgs(u256(0))
	);
	BOOST_CHECK_EQUAL(m_state.balance(Address(0x05)), 90);
}
//@todo test data calls
BOOST_AUTO_TEST_SUITE_END()
}
}
} // end namespaces