diff options
-rw-r--r-- | TestHelper.cpp | 200 | ||||
-rw-r--r-- | TestHelper.h | 40 | ||||
-rw-r--r-- | TestUtils.cpp | 4 | ||||
-rw-r--r-- | contracts/AuctionRegistrar.cpp | 418 | ||||
-rw-r--r-- | contracts/FixedFeeRegistrar.cpp | 5 | ||||
-rw-r--r-- | contracts/Wallet.cpp | 4 | ||||
-rw-r--r-- | libsolidity/SolidityEndToEndTest.cpp | 37 | ||||
-rw-r--r-- | libsolidity/SolidityNameAndTypeResolution.cpp | 17 | ||||
-rw-r--r-- | libsolidity/solidityExecutionFramework.h | 68 |
9 files changed, 640 insertions, 153 deletions
diff --git a/TestHelper.cpp b/TestHelper.cpp index a0737859..39977975 100644 --- a/TestHelper.cpp +++ b/TestHelper.cpp @@ -25,6 +25,7 @@ #include <chrono> #include <libethcore/EthashAux.h> #include <libethereum/Client.h> +#include <libevm/ExtVMFace.h> #include <liblll/Compiler.h> #include <libevm/VMFactory.h> #include "Stats.h" @@ -61,10 +62,10 @@ void connectClients(Client& c1, Client& c2) #endif } -void mine(State& s, BlockChain const& _bc) +void mine(Block& s, BlockChain const& _bc) { std::unique_ptr<SealEngineFace> sealer(Ethash::createSealEngine()); - s.commitToMine(_bc); + s.commitToSeal(_bc); Notified<bytes> sealed; sealer->onSealGenerated([&](bytes const& sealedHeader){ sealed = sealedHeader; }); sealer->generateSeal(s.info()); @@ -94,22 +95,43 @@ struct MissingFields : virtual Exception {}; bigint const c_max256plus1 = bigint(1) << 256; -ImportTest::ImportTest(json_spirit::mObject& _o, bool isFiller): - m_statePre(OverlayDB(), eth::BaseState::Empty, Address(_o["env"].get_obj()["currentCoinbase"].get_str())), - m_statePost(OverlayDB(), eth::BaseState::Empty, Address(_o["env"].get_obj()["currentCoinbase"].get_str())), - m_TestObject(_o) +ImportTest::ImportTest(json_spirit::mObject& _o, bool isFiller, testType testTemplate): + m_statePre(OverlayDB(), eth::BaseState::Empty), + m_statePost(OverlayDB(), eth::BaseState::Empty), + m_testObject(_o) { - importEnv(_o["env"].get_obj()); - importState(_o["pre"].get_obj(), m_statePre); - importTransaction(_o["transaction"].get_obj()); - - if (!isFiller) + if (testTemplate == testType::StateTests) { - importState(_o["post"].get_obj(), m_statePost); - m_environment.sub.logs = importLog(_o["logs"].get_array()); + importEnv(_o["env"].get_obj()); + importTransaction(_o["transaction"].get_obj()); + importState(_o["pre"].get_obj(), m_statePre); + if (!isFiller) + { + if (_o.count("post")) + importState(_o["post"].get_obj(), m_statePost); + else + importState(_o["postState"].get_obj(), m_statePost); + m_logsExpected = importLog(_o["logs"].get_array()); + } } } +//executes an imported transacton on preState +bytes ImportTest::executeTest() +{ + ExecutionResult res; + eth::State tmpState = m_statePre; + + std::pair<ExecutionResult, TransactionReceipt> execOut = m_statePre.execute(m_envInfo, m_transaction); + res = execOut.first; + m_logs = execOut.second.log(); + m_statePre.commit(); + m_statePost = m_statePre; + m_statePre = tmpState; + + return res.output; +} + json_spirit::mObject& ImportTest::makeAllFieldsHex(json_spirit::mObject& _o) { static const set<string> hashes {"bloom" , "coinbase", "hash", "mixHash", "parentHash", "receiptTrie", @@ -138,105 +160,35 @@ json_spirit::mObject& ImportTest::makeAllFieldsHex(json_spirit::mObject& _o) void ImportTest::importEnv(json_spirit::mObject& _o) { - assert(_o.count("previousHash") > 0); assert(_o.count("currentGasLimit") > 0); - assert(_o.count("currentDifficulty") > 0); + assert(_o.count("currentDifficulty") > 0); + assert(_o.count("currentNumber") > 0); assert(_o.count("currentTimestamp") > 0); assert(_o.count("currentCoinbase") > 0); - assert(_o.count("currentNumber") > 0); - - RLPStream rlpStream; - rlpStream.appendList(BlockInfo::BasicFields); - - rlpStream << h256(_o["previousHash"].get_str()); - rlpStream << EmptyListSHA3; - rlpStream << Address(_o["currentCoinbase"].get_str()); - rlpStream << h256(); // stateRoot - rlpStream << EmptyTrie; // transactionTrie - rlpStream << EmptyTrie; // receiptTrie - rlpStream << LogBloom(); // bloom - rlpStream << toInt(_o["currentDifficulty"]); - rlpStream << toInt(_o["currentNumber"]); - rlpStream << toInt(_o["currentGasLimit"]); - rlpStream << 0; //gasUsed - rlpStream << toInt(_o["currentTimestamp"]); - rlpStream << std::string(); //extra data - - m_environment.currentBlock = BlockInfo(rlpStream.out(), CheckEverything, h256{}, HeaderData); - m_statePre.m_previousBlock = m_environment.previousBlock; - m_statePre.m_currentBlock = m_environment.currentBlock; + m_envInfo.setGasLimit(toInt(_o["currentGasLimit"])); + m_envInfo.setDifficulty(toInt(_o["currentDifficulty"])); + m_envInfo.setNumber(toInt(_o["currentNumber"])); + m_envInfo.setTimestamp(toInt(_o["currentTimestamp"])); + m_envInfo.setBeneficiary(Address(_o["currentCoinbase"].get_str())); + m_envInfo.setLastHashes( lastHashes( m_envInfo.number() ) ); } // import state from not fully declared json_spirit::mObject, writing to _stateOptionsMap which fields were defined in json -void ImportTest::importState(json_spirit::mObject& _o, State& _state, stateOptionsMap& _stateOptionsMap) -{ - for (auto& i: _o) - { - json_spirit::mObject o = i.second.get_obj(); - - ImportStateOptions stateOptions; - u256 balance = 0; - u256 nonce = 0; - - if (o.count("balance") > 0) - { - stateOptions.m_bHasBalance = true; - if (bigint(o["balance"].get_str()) >= c_max256plus1) - BOOST_THROW_EXCEPTION(ValueTooLarge() << errinfo_comment("State 'balance' is equal or greater than 2**256") ); - balance = toInt(o["balance"]); - } - - if (o.count("nonce") > 0) - { - stateOptions.m_bHasNonce = true; - if (bigint(o["nonce"].get_str()) >= c_max256plus1) - BOOST_THROW_EXCEPTION(ValueTooLarge() << errinfo_comment("State 'nonce' is equal or greater than 2**256") ); - nonce = toInt(o["nonce"]); - } - - Address address = Address(i.first); - - bytes code; - if (o.count("code") > 0) - { - code = importCode(o); - stateOptions.m_bHasCode = true; - } - - if (!code.empty()) - { - _state.m_cache[address] = Account(balance, Account::ContractConception); - _state.m_cache[address].setCode(std::move(code)); - } - else - _state.m_cache[address] = Account(balance, Account::NormalCreation); - - if (o.count("storage") > 0) - { - stateOptions.m_bHasStorage = true; - for (auto const& j: o["storage"].get_obj()) - _state.setStorage(address, toInt(j.first), toInt(j.second)); - } - - for (int i = 0; i < nonce; ++i) - _state.noteSending(address); - - _state.ensureCached(address, false, false); - _stateOptionsMap[address] = stateOptions; - } +void ImportTest::importState(json_spirit::mObject& _o, State& _state, AccountMaskMap& o_mask) +{ + std::string jsondata = json_spirit::write_string((json_spirit::mValue)_o, false); + _state.populateFrom(jsonToAccountMap(jsondata, &o_mask)); } void ImportTest::importState(json_spirit::mObject& _o, State& _state) { - stateOptionsMap importedMap; - importState(_o, _state, importedMap); - for (auto& stateOptionMap : importedMap) - { + AccountMaskMap mask; + importState(_o, _state, mask); + for (auto const& i: mask) //check that every parameter was declared in state object - if (!stateOptionMap.second.isAllSet()) + if (!i.second.allSet()) BOOST_THROW_EXCEPTION(MissingFields() << errinfo_comment("Import State: Missing state fields!")); - } } void ImportTest::importTransaction(json_spirit::mObject& _o) @@ -285,7 +237,7 @@ void ImportTest::importTransaction(json_spirit::mObject& _o) } } -void ImportTest::checkExpectedState(State const& _stateExpect, State const& _statePost, stateOptionsMap const _expectedStateOptions, WhenError _throw) +void ImportTest::compareStates(State const& _stateExpect, State const& _statePost, AccountMaskMap const _expectedStateOptions, WhenError _throw) { #define CHECK(a,b) \ { \ @@ -300,7 +252,7 @@ void ImportTest::checkExpectedState(State const& _stateExpect, State const& _sta CHECK(_statePost.addressInUse(a.first), "Filling Test: " << a.first << " missing expected address!"); if (_statePost.addressInUse(a.first)) { - ImportStateOptions addressOptions(true); + AccountMask addressOptions(true); if(_expectedStateOptions.size()) { try @@ -314,15 +266,15 @@ void ImportTest::checkExpectedState(State const& _stateExpect, State const& _sta } } - if (addressOptions.m_bHasBalance) + if (addressOptions.hasBalance()) CHECK((_stateExpect.balance(a.first) == _statePost.balance(a.first)), "Check State: " << a.first << ": incorrect balance " << _statePost.balance(a.first) << ", expected " << _stateExpect.balance(a.first)); - if (addressOptions.m_bHasNonce) + if (addressOptions.hasNonce()) CHECK((_stateExpect.transactionsFrom(a.first) == _statePost.transactionsFrom(a.first)), "Check State: " << a.first << ": incorrect nonce " << _statePost.transactionsFrom(a.first) << ", expected " << _stateExpect.transactionsFrom(a.first)); - if (addressOptions.m_bHasStorage) + if (addressOptions.hasStorage()) { unordered_map<u256, u256> stateStorage = _statePost.storage(a.first); for (auto const& s: _stateExpect.storage(a.first)) @@ -336,52 +288,52 @@ void ImportTest::checkExpectedState(State const& _stateExpect, State const& _sta "Check State: " << a.first << ": incorrect storage [" << s.first << "] = " << toHex(s.second) << ", expected [" << s.first << "] = " << toHex(stateStorage[s.first])); } - if (addressOptions.m_bHasCode) + if (addressOptions.hasCode()) CHECK((_stateExpect.code(a.first) == _statePost.code(a.first)), "Check State: " << a.first << ": incorrect code '" << toHex(_statePost.code(a.first)) << "', expected '" << toHex(_stateExpect.code(a.first)) << "'"); } } } -void ImportTest::exportTest(bytes const& _output, State const& _statePost) +void ImportTest::exportTest(bytes const& _output) { // export output - m_TestObject["out"] = (_output.size() > 4096 && !Options::get().fulloutput) ? "#" + toString(_output.size()) : toHex(_output, 2, HexPrefix::Add); + m_testObject["out"] = (_output.size() > 4096 && !Options::get().fulloutput) ? "#" + toString(_output.size()) : toHex(_output, 2, HexPrefix::Add); // compare expected output with post output - if (m_TestObject.count("expectOut") > 0) + if (m_testObject.count("expectOut") > 0) { - std::string warning = "Check State: Error! Unexpected output: " + m_TestObject["out"].get_str() + " Expected: " + m_TestObject["expectOut"].get_str(); + std::string warning = "Check State: Error! Unexpected output: " + m_testObject["out"].get_str() + " Expected: " + m_testObject["expectOut"].get_str(); if (Options::get().checkState) - {TBOOST_CHECK_MESSAGE((m_TestObject["out"].get_str() == m_TestObject["expectOut"].get_str()), warning);} + {TBOOST_CHECK_MESSAGE((m_testObject["out"].get_str() == m_testObject["expectOut"].get_str()), warning);} else - TBOOST_WARN_MESSAGE((m_TestObject["out"].get_str() == m_TestObject["expectOut"].get_str()), warning); + TBOOST_WARN_MESSAGE((m_testObject["out"].get_str() == m_testObject["expectOut"].get_str()), warning); - m_TestObject.erase(m_TestObject.find("expectOut")); + m_testObject.erase(m_testObject.find("expectOut")); } - // export logs - m_TestObject["logs"] = exportLog(_statePost.pending().size() ? _statePost.log(0) : LogEntries()); + // export logs + m_testObject["logs"] = exportLog(m_logs); // compare expected state with post state - if (m_TestObject.count("expect") > 0) + if (m_testObject.count("expect") > 0) { - stateOptionsMap stateMap; + eth::AccountMaskMap stateMap; State expectState(OverlayDB(), eth::BaseState::Empty); - importState(m_TestObject["expect"].get_obj(), expectState, stateMap); - checkExpectedState(expectState, _statePost, stateMap, Options::get().checkState ? WhenError::Throw : WhenError::DontThrow); - m_TestObject.erase(m_TestObject.find("expect")); + importState(m_testObject["expect"].get_obj(), expectState, stateMap); + compareStates(expectState, m_statePost, stateMap, Options::get().checkState ? WhenError::Throw : WhenError::DontThrow); + m_testObject.erase(m_testObject.find("expect")); } // export post state - m_TestObject["post"] = fillJsonWithState(_statePost); - m_TestObject["postStateRoot"] = toHex(_statePost.rootHash().asBytes()); + m_testObject["post"] = fillJsonWithState(m_statePost); + m_testObject["postStateRoot"] = toHex(m_statePost.rootHash().asBytes()); // export pre state - m_TestObject["pre"] = fillJsonWithState(m_statePre); - m_TestObject["env"] = makeAllFieldsHex(m_TestObject["env"].get_obj()); - m_TestObject["transaction"] = makeAllFieldsHex(m_TestObject["transaction"].get_obj()); + m_testObject["pre"] = fillJsonWithState(m_statePre); + m_testObject["env"] = makeAllFieldsHex(m_testObject["env"].get_obj()); + m_testObject["transaction"] = makeAllFieldsHex(m_testObject["transaction"].get_obj()); } json_spirit::mObject fillJsonWithTransaction(Transaction _txn) diff --git a/TestHelper.h b/TestHelper.h index 48eb42c5..9d2625e1 100644 --- a/TestHelper.h +++ b/TestHelper.h @@ -32,6 +32,7 @@ #include <libevm/ExtVMFace.h> #include <libtestutils/Common.h> + #ifdef NOBOOST #define TBOOST_REQUIRE(arg) if(arg == false) throw dev::Exception(); #define TBOOST_REQUIRE_EQUAL(arg1, arg2) if(arg1 != arg2) throw dev::Exception(); @@ -62,7 +63,7 @@ class State; void mine(Client& c, int numBlocks); void connectClients(Client& c1, Client& c2); -void mine(State& _s, BlockChain const& _bc); +void mine(Block& _s, BlockChain const& _bc); void mine(Ethash::BlockHeader& _bi); } @@ -122,45 +123,44 @@ namespace test } \ while (0) -struct ImportStateOptions +enum class testType { - ImportStateOptions(bool _bSetAll = false):m_bHasBalance(_bSetAll), m_bHasNonce(_bSetAll), m_bHasCode(_bSetAll), m_bHasStorage(_bSetAll) {} - bool isAllSet() {return m_bHasBalance && m_bHasNonce && m_bHasCode && m_bHasStorage;} - bool m_bHasBalance; - bool m_bHasNonce; - bool m_bHasCode; - bool m_bHasStorage; + StateTests, + BlockChainTests, + Other }; -typedef std::map<Address, ImportStateOptions> stateOptionsMap; class ImportTest { public: - ImportTest(json_spirit::mObject& _o): m_TestObject(_o) {} - ImportTest(json_spirit::mObject& _o, bool isFiller); + ImportTest(json_spirit::mObject& _o, bool isFiller, testType testTemplate = testType::StateTests); + // imports void importEnv(json_spirit::mObject& _o); static void importState(json_spirit::mObject& _o, eth::State& _state); - static void importState(json_spirit::mObject& _o, eth::State& _state, stateOptionsMap& _stateOptionsMap); + static void importState(json_spirit::mObject& _o, eth::State& _state, eth::AccountMaskMap& o_mask); void importTransaction(json_spirit::mObject& _o); static json_spirit::mObject& makeAllFieldsHex(json_spirit::mObject& _o); - void exportTest(bytes const& _output, eth::State const& _statePost); - static void checkExpectedState(eth::State const& _stateExpect, eth::State const& _statePost, stateOptionsMap const _expectedStateOptions = stateOptionsMap(), WhenError _throw = WhenError::Throw); + bytes executeTest(); + void exportTest(bytes const& _output); + static void compareStates(eth::State const& _stateExpect, eth::State const& _statePost, eth::AccountMaskMap const _expectedStateOptions = eth::AccountMaskMap(), WhenError _throw = WhenError::Throw); eth::State m_statePre; eth::State m_statePost; - eth::ExtVMFace m_environment; - eth::Transaction m_transaction; + eth::EnvInfo m_envInfo; + eth::Transaction m_transaction; + eth::LogEntries m_logs; + eth::LogEntries m_logsExpected; private: - json_spirit::mObject& m_TestObject; + json_spirit::mObject& m_testObject; }; class ZeroGasPricer: public eth::GasPricer { protected: - u256 ask(eth::State const&) const override { return 0; } + u256 ask(eth::Block const&) const override { return 0; } u256 bid(eth::TransactionPriority = eth::TransactionPriority::Medium) const override { return 0; } }; @@ -205,7 +205,7 @@ void doVMTests(json_spirit::mValue& v, bool _fillin); void doBlockchainTests(json_spirit::mValue& _v, bool _fillin); void doRlpTests(json_spirit::mValue& v, bool _fillin); -template<typename mapType> +/*template<typename mapType> void checkAddresses(mapType& _expectedAddrs, mapType& _resultAddrs) { for (auto& resultPair : _resultAddrs) @@ -216,7 +216,7 @@ void checkAddresses(mapType& _expectedAddrs, mapType& _resultAddrs) TBOOST_ERROR("Missing result address " << resultAddr); } TBOOST_CHECK((_expectedAddrs == _resultAddrs)); -} +}*/ class Options { diff --git a/TestUtils.cpp b/TestUtils.cpp index 60d5d606..0ec76386 100644 --- a/TestUtils.cpp +++ b/TestUtils.cpp @@ -101,7 +101,9 @@ void ClientBaseFixture::enumerateClients(std::function<void(Json::Value const&, { enumerateBlockchains([&callback](Json::Value const& _json, BlockChain const& _bc, State _state) -> void { - FixedClient client(_bc, _state); + cerr << "void ClientBaseFixture::enumerateClients. FixedClient now accepts block not sate!" << endl; + _state.commit(); //unused variable. remove this line + FixedClient client(_bc, eth::Block {}); callback(_json, client); }); } diff --git a/contracts/AuctionRegistrar.cpp b/contracts/AuctionRegistrar.cpp new file mode 100644 index 00000000..3832e8e0 --- /dev/null +++ b/contracts/AuctionRegistrar.cpp @@ -0,0 +1,418 @@ +/* + 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); + } + }; +}; + +} + +/// 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)); + + // 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()); +} + +//@todo: +// - reverse lookup +// - actual auction + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/contracts/FixedFeeRegistrar.cpp b/contracts/FixedFeeRegistrar.cpp index 26373499..ed2ecf0a 100644 --- a/contracts/FixedFeeRegistrar.cpp +++ b/contracts/FixedFeeRegistrar.cpp @@ -35,6 +35,9 @@ namespace solidity namespace test { +namespace +{ + static char const* registrarCode = R"DELIMITER( //sol FixedFeeRegistrar // Simple global registrar with fixed-fee reservations. @@ -130,6 +133,8 @@ protected: u256 const m_fee = u256("69000000000000000000"); }; +} + /// This is a test suite that tests optimised code! BOOST_FIXTURE_TEST_SUITE(SolidityFixedFeeRegistrar, RegistrarTestFramework) diff --git a/contracts/Wallet.cpp b/contracts/Wallet.cpp index 3c88afd9..5f9febd4 100644 --- a/contracts/Wallet.cpp +++ b/contracts/Wallet.cpp @@ -545,7 +545,7 @@ BOOST_AUTO_TEST_CASE(multisig_value_transfer) // 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("8f27f478ebcfaf28b0c354f4809ace8087000d668b89c8bc3b1b608bfdbe6654"); + 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)); @@ -596,7 +596,7 @@ BOOST_AUTO_TEST_CASE(revoke_transaction) BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); // create a transaction Address deployer = m_sender; - h256 opHash("8f27f478ebcfaf28b0c354f4809ace8087000d668b89c8bc3b1b608bfdbe6654"); + 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)); diff --git a/libsolidity/SolidityEndToEndTest.cpp b/libsolidity/SolidityEndToEndTest.cpp index fc1d2eab..ae2fc6dc 100644 --- a/libsolidity/SolidityEndToEndTest.cpp +++ b/libsolidity/SolidityEndToEndTest.cpp @@ -1151,8 +1151,10 @@ BOOST_AUTO_TEST_CASE(blockchain) " blockNumber = block.number;\n" " }\n" "}\n"; + m_envInfo.setBeneficiary(Address(0x123)); + m_envInfo.setNumber(7); compileAndRun(sourceCode, 27); - BOOST_CHECK(callContractFunctionWithValue("someInfo()", 28) == encodeArgs(28, 0, 1)); + BOOST_CHECK(callContractFunctionWithValue("someInfo()", 28) == encodeArgs(28, 0x123, 7)); } BOOST_AUTO_TEST_CASE(msg_sig) @@ -1187,12 +1189,14 @@ BOOST_AUTO_TEST_CASE(msg_sig_after_internal_call_is_same) BOOST_AUTO_TEST_CASE(now) { char const* sourceCode = "contract test {\n" - " function someInfo() returns (bool success) {\n" - " return block.timestamp == now && now > 0;\n" + " function someInfo() returns (bool equal, uint val) {\n" + " equal = block.timestamp == now;\n" + " val = now;\n" " }\n" "}\n"; + m_envInfo.setTimestamp(9); compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("someInfo()") == encodeArgs(true)); + BOOST_CHECK(callContractFunction("someInfo()") == encodeArgs(true, 9)); } BOOST_AUTO_TEST_CASE(type_conversions_cleanup) @@ -5099,6 +5103,31 @@ BOOST_AUTO_TEST_CASE(memory_structs_with_mappings) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0))); } +BOOST_AUTO_TEST_CASE(string_bytes_conversion) +{ + char const* sourceCode = R"( + contract Test { + string s; + bytes b; + function f(string _s, uint n) returns (byte) { + b = bytes(_s); + s = string(b); + return bytes(s)[n]; + } + function l() returns (uint) { return bytes(s).length; } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + BOOST_CHECK(callContractFunction( + "f(string,uint256)", + u256(0x40), + u256(2), + u256(6), + string("abcdef") + ) == encodeArgs("c")); + BOOST_CHECK(callContractFunction("l()") == encodeArgs(u256(6))); +} + BOOST_AUTO_TEST_CASE(string_as_mapping_key) { char const* sourceCode = R"( diff --git a/libsolidity/SolidityNameAndTypeResolution.cpp b/libsolidity/SolidityNameAndTypeResolution.cpp index cfc43df9..6b116f25 100644 --- a/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2149,6 +2149,23 @@ BOOST_AUTO_TEST_CASE(memory_structs_with_mappings) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } +BOOST_AUTO_TEST_CASE(string_bytes_conversion) +{ + char const* text = R"( + contract Test { + string s; + bytes b; + function h(string _s) external { bytes(_s).length; } + function i(string _s) internal { bytes(_s).length; } + function j() internal { bytes(s).length; } + function k(bytes _b) external { string(_b); } + function l(bytes _b) internal { string(_b); } + function m() internal { string(b); } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/libsolidity/solidityExecutionFramework.h b/libsolidity/solidityExecutionFramework.h index e4c4087f..3f443720 100644 --- a/libsolidity/solidityExecutionFramework.h +++ b/libsolidity/solidityExecutionFramework.h @@ -25,6 +25,7 @@ #include <string> #include <tuple> #include "../TestHelper.h" +#include <libethcore/ABI.h> #include <libethereum/State.h> #include <libethereum/Executive.h> #include <libsolidity/CompilerStack.h> @@ -44,7 +45,6 @@ public: ExecutionFramework() { g_logVerbosity = 0; - m_state.resetCurrent(); } bytes const& compileAndRunWithoutCheck( @@ -166,6 +166,69 @@ public: return encodeArgs(u256(0x20), u256(_arg.size()), _arg); } + class ContractInterface + { + public: + ContractInterface(ExecutionFramework& _framework): m_framework(_framework) {} + + protected: + template <class... Args> + bytes const& call(std::string const& _sig, Args const&... _arguments) + { + auto const& ret = m_framework.callContractFunctionWithValue(_sig, m_nextValue, _arguments...); + m_nextValue = 0; + return ret; + } + + void callString(std::string const& _name, std::string const& _arg) + { + BOOST_CHECK(call(_name + "(string)", u256(0x20), _arg.length(), _arg).empty()); + } + + void callStringAddress(std::string const& _name, std::string const& _arg1, u160 const& _arg2) + { + BOOST_CHECK(call(_name + "(string,address)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); + } + + void callStringAddressBool(std::string const& _name, std::string const& _arg1, u160 const& _arg2, bool _arg3) + { + BOOST_CHECK(call(_name + "(string,address,bool)", u256(0x60), _arg2, _arg3, _arg1.length(), _arg1).empty()); + } + + void callStringBytes32(std::string const& _name, std::string const& _arg1, h256 const& _arg2) + { + BOOST_CHECK(call(_name + "(string,bytes32)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); + } + + u160 callStringReturnsAddress(std::string const& _name, std::string const& _arg) + { + bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); + BOOST_REQUIRE(ret.size() == 0x20); + BOOST_CHECK(std::count(ret.begin(), ret.begin() + 12, 0) == 12); + return eth::abiOut<u160>(ret); + } + + std::string callAddressReturnsString(std::string const& _name, u160 const& _arg) + { + bytes const& ret = call(_name + "(address)", _arg); + BOOST_REQUIRE(ret.size() >= 0x20); + u256 len = eth::abiOut<u256>(ret); + BOOST_REQUIRE(ret.size() == (0x20 + len) / 32 * 32); + return std::string(ret.begin() + 0x20, ret.begin() + 0x20 + size_t(len)); + } + + h256 callStringReturnsBytes32(std::string const& _name, std::string const& _arg) + { + bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); + BOOST_REQUIRE(ret.size() == 0x20); + return eth::abiOut<h256>(ret); + } + + private: + u256 m_nextValue; + ExecutionFramework& m_framework; + }; + private: template <class CppFunction, class... Args> auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments) @@ -185,7 +248,7 @@ protected: void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0) { m_state.addBalance(m_sender, _value); // just in case - eth::Executive executive(m_state, eth::LastHashes(), 0); + eth::Executive executive(m_state, m_envInfo, 0); eth::ExecutionResult res; executive.setResultRecipient(res); eth::Transaction t = @@ -226,6 +289,7 @@ protected: dev::solidity::CompilerStack m_compiler; Address m_sender; Address m_contractAddress; + eth::EnvInfo m_envInfo; eth::State m_state; u256 const m_gasPrice = 100 * eth::szabo; u256 const m_gas = 100000000; |