Gav Wood
10 years ago
1 changed files with 491 additions and 0 deletions
@ -0,0 +1,491 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <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 { |
|||
// 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<bytes> 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
|
Loading…
Reference in new issue