mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #5 from chriseth/addTests
Add tests from cpp-ethereum.
This commit is contained in:
commit
4c8b220257
121
test/CMakeLists.txt
Normal file
121
test/CMakeLists.txt
Normal file
@ -0,0 +1,121 @@
|
||||
cmake_policy(SET CMP0015 NEW)
|
||||
|
||||
aux_source_directory(. SRC_LIST)
|
||||
|
||||
macro (add_sources)
|
||||
file (RELATIVE_PATH _relPath "${CMAKE_SOURCE_DIR}/test" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
foreach (_src ${ARGN})
|
||||
if (_relPath)
|
||||
list (APPEND SRC "${_relPath}/${_src}")
|
||||
else()
|
||||
list (APPEND SRC "${_src}")
|
||||
endif()
|
||||
endforeach()
|
||||
if (_relPath)
|
||||
# propagate SRCS to parent directory
|
||||
set (SRC ${SRC} PARENT_SCOPE)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
add_subdirectory(fuzzTesting)
|
||||
add_subdirectory(libdevcore)
|
||||
add_subdirectory(libdevcrypto)
|
||||
add_subdirectory(libethcore)
|
||||
add_subdirectory(libethereum)
|
||||
add_subdirectory(libevm)
|
||||
add_subdirectory(libnatspec)
|
||||
add_subdirectory(libp2p)
|
||||
add_subdirectory(external-dependencies)
|
||||
|
||||
if (JSCONSOLE)
|
||||
add_subdirectory(libjsengine)
|
||||
endif()
|
||||
|
||||
if (SOLIDITY)
|
||||
add_subdirectory(libsolidity)
|
||||
add_subdirectory(contracts)
|
||||
endif ()
|
||||
if (JSONRPC)
|
||||
add_subdirectory(libweb3jsonrpc)
|
||||
endif ()
|
||||
add_subdirectory(libwhisper)
|
||||
|
||||
set(SRC_LIST ${SRC_LIST} ${SRC})
|
||||
|
||||
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS})
|
||||
include_directories(BEFORE ..)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
include_directories(${CRYPTOPP_INCLUDE_DIRS})
|
||||
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS})
|
||||
|
||||
if (JSCONSOLE)
|
||||
include_directories(${V8_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
# search for test names and create ctest tests
|
||||
enable_testing()
|
||||
foreach(file ${SRC_LIST})
|
||||
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/${file} test_list_raw REGEX "BOOST_.*TEST_(SUITE|CASE)")
|
||||
set(TestSuite "DEFAULT")
|
||||
foreach(test_raw ${test_list_raw})
|
||||
string(REGEX REPLACE ".*TEST_(SUITE|CASE)\\(([^ ,\\)]*).*" "\\1 \\2" test ${test_raw})
|
||||
if(test MATCHES "^SUITE .*")
|
||||
string(SUBSTRING ${test} 6 -1 TestSuite)
|
||||
elseif(test MATCHES "^CASE .*")
|
||||
string(SUBSTRING ${test} 5 -1 TestCase)
|
||||
add_test(NAME ${TestSuite}/${TestCase} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND testeth -t ${TestSuite}/${TestCase})
|
||||
endif(test MATCHES "^SUITE .*")
|
||||
endforeach(test_raw)
|
||||
endforeach(file)
|
||||
|
||||
file(GLOB HEADERS "*.h")
|
||||
add_executable(testeth ${SRC_LIST} ${HEADERS})
|
||||
|
||||
target_link_libraries(testeth ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
|
||||
target_link_libraries(testeth ${CURL_LIBRARIES})
|
||||
target_link_libraries(testeth ${CRYPTOPP_LIBRARIES})
|
||||
target_link_libraries(testeth ethereum)
|
||||
target_link_libraries(testeth ethcore)
|
||||
if (NOT WIN32)
|
||||
target_link_libraries(testeth secp256k1)
|
||||
endif ()
|
||||
|
||||
if (JSCONSOLE)
|
||||
target_link_libraries(testeth jsengine)
|
||||
endif()
|
||||
|
||||
if (SOLIDITY)
|
||||
target_link_libraries(testeth solidity)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(testeth testutils)
|
||||
|
||||
if (GUI AND NOT JUSTTESTS)
|
||||
target_link_libraries(testeth webthree)
|
||||
target_link_libraries(testeth natspec)
|
||||
endif()
|
||||
|
||||
if (JSONRPC)
|
||||
target_link_libraries(testeth web3jsonrpc)
|
||||
target_link_libraries(testeth ${JSON_RPC_CPP_CLIENT_LIBRARIES})
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
set(CTEST_OUTPUT_ON_FAILURE TRUE)
|
||||
|
||||
include(EthUtils)
|
||||
|
||||
eth_add_test(ClientBase
|
||||
ARGS --eth_testfile=BlockTests/bcJS_API_Test --eth_threads=1
|
||||
ARGS --eth_testfile=BlockTests/bcJS_API_Test --eth_threads=3
|
||||
ARGS --eth_testfile=BlockTests/bcJS_API_Test --eth_threads=10
|
||||
ARGS --eth_testfile=BlockTests/bcValidBlockTest --eth_threads=1
|
||||
ARGS --eth_testfile=BlockTests/bcValidBlockTest --eth_threads=3
|
||||
ARGS --eth_testfile=BlockTests/bcValidBlockTest --eth_threads=10
|
||||
)
|
||||
|
||||
eth_add_test(JsonRpc
|
||||
ARGS --eth_testfile=BlockTests/bcJS_API_Test
|
||||
ARGS --eth_testfile=BlockTests/bcValidBlockTest
|
||||
)
|
||||
|
892
test/TestHelper.cpp
Normal file
892
test/TestHelper.cpp
Normal file
@ -0,0 +1,892 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/** @file TestHelper.cpp
|
||||
* @author Marko Simovic <markobarko@gmail.com>
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "TestHelper.h"
|
||||
|
||||
#include <thread>
|
||||
#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"
|
||||
|
||||
using namespace std;
|
||||
using namespace dev::eth;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace eth
|
||||
{
|
||||
|
||||
void mine(Client& c, int numBlocks)
|
||||
{
|
||||
auto startBlock = c.blockChain().details().number;
|
||||
|
||||
c.startMining();
|
||||
while(c.blockChain().details().number < startBlock + numBlocks)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
c.stopMining();
|
||||
}
|
||||
|
||||
void connectClients(Client& c1, Client& c2)
|
||||
{
|
||||
(void)c1;
|
||||
(void)c2;
|
||||
// TODO: Move to WebThree. eth::Client no longer handles networking.
|
||||
#if 0
|
||||
short c1Port = 20000;
|
||||
short c2Port = 21000;
|
||||
c1.startNetwork(c1Port);
|
||||
c2.startNetwork(c2Port);
|
||||
c2.connect("127.0.0.1", c1Port);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mine(Block& s, BlockChain const& _bc)
|
||||
{
|
||||
std::unique_ptr<SealEngineFace> sealer(Ethash::createSealEngine());
|
||||
s.commitToSeal(_bc);
|
||||
Notified<bytes> sealed;
|
||||
sealer->onSealGenerated([&](bytes const& sealedHeader){ sealed = sealedHeader; });
|
||||
sealer->generateSeal(s.info());
|
||||
sealed.waitNot({});
|
||||
sealer.reset();
|
||||
s.sealBlock(sealed);
|
||||
}
|
||||
|
||||
void mine(Ethash::BlockHeader& _bi)
|
||||
{
|
||||
std::unique_ptr<SealEngineFace> sealer(Ethash::createSealEngine());
|
||||
Notified<bytes> sealed;
|
||||
sealer->onSealGenerated([&](bytes const& sealedHeader){ sealed = sealedHeader; });
|
||||
sealer->generateSeal(_bi);
|
||||
sealed.waitNot({});
|
||||
sealer.reset();
|
||||
_bi = Ethash::BlockHeader(sealed, CheckNothing, h256{}, HeaderData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace test
|
||||
{
|
||||
|
||||
struct ValueTooLarge: virtual Exception {};
|
||||
struct MissingFields : virtual Exception {};
|
||||
|
||||
bigint const c_max256plus1 = bigint(1) << 256;
|
||||
|
||||
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)
|
||||
{
|
||||
if (testTemplate == testType::StateTests)
|
||||
{
|
||||
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;
|
||||
try
|
||||
{
|
||||
std::pair<ExecutionResult, TransactionReceipt> execOut = m_statePre.execute(m_envInfo, m_transaction);
|
||||
res = execOut.first;
|
||||
m_logs = execOut.second.log();
|
||||
}
|
||||
catch (Exception const& _e)
|
||||
{
|
||||
cnote << "Exception: " << diagnostic_information(_e);
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
cnote << "state execution exception: " << _e.what();
|
||||
}
|
||||
|
||||
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",
|
||||
"stateRoot", "transactionsTrie", "uncleHash", "currentCoinbase",
|
||||
"previousHash", "to", "address", "caller", "origin", "secretKey", "data"};
|
||||
|
||||
for (auto& i: _o)
|
||||
{
|
||||
std::string key = i.first;
|
||||
if (hashes.count(key))
|
||||
continue;
|
||||
|
||||
std::string str;
|
||||
json_spirit::mValue value = i.second;
|
||||
|
||||
if (value.type() == json_spirit::int_type)
|
||||
str = toString(value.get_int());
|
||||
else if (value.type() == json_spirit::str_type)
|
||||
str = value.get_str();
|
||||
else continue;
|
||||
|
||||
_o[key] = (str.substr(0, 2) == "0x") ? str : toCompactHex(toInt(str), HexPrefix::Add, 1);
|
||||
}
|
||||
return _o;
|
||||
}
|
||||
|
||||
void ImportTest::importEnv(json_spirit::mObject& _o)
|
||||
{
|
||||
assert(_o.count("currentGasLimit") > 0);
|
||||
assert(_o.count("currentDifficulty") > 0);
|
||||
assert(_o.count("currentNumber") > 0);
|
||||
assert(_o.count("currentTimestamp") > 0);
|
||||
assert(_o.count("currentCoinbase") > 0);
|
||||
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, 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)
|
||||
{
|
||||
AccountMaskMap mask;
|
||||
importState(_o, _state, mask);
|
||||
for (auto const& i: mask)
|
||||
//check that every parameter was declared in state object
|
||||
if (!i.second.allSet())
|
||||
BOOST_THROW_EXCEPTION(MissingFields() << errinfo_comment("Import State: Missing state fields!"));
|
||||
}
|
||||
|
||||
void ImportTest::importTransaction (json_spirit::mObject const& _o, eth::Transaction& o_tr)
|
||||
{
|
||||
if (_o.count("secretKey") > 0)
|
||||
{
|
||||
assert(_o.count("nonce") > 0);
|
||||
assert(_o.count("gasPrice") > 0);
|
||||
assert(_o.count("gasLimit") > 0);
|
||||
assert(_o.count("to") > 0);
|
||||
assert(_o.count("value") > 0);
|
||||
assert(_o.count("data") > 0);
|
||||
|
||||
if (bigint(_o.at("nonce").get_str()) >= c_max256plus1)
|
||||
BOOST_THROW_EXCEPTION(ValueTooLarge() << errinfo_comment("Transaction 'nonce' is equal or greater than 2**256") );
|
||||
if (bigint(_o.at("gasPrice").get_str()) >= c_max256plus1)
|
||||
BOOST_THROW_EXCEPTION(ValueTooLarge() << errinfo_comment("Transaction 'gasPrice' is equal or greater than 2**256") );
|
||||
if (bigint(_o.at("gasLimit").get_str()) >= c_max256plus1)
|
||||
BOOST_THROW_EXCEPTION(ValueTooLarge() << errinfo_comment("Transaction 'gasLimit' is equal or greater than 2**256") );
|
||||
if (bigint(_o.at("value").get_str()) >= c_max256plus1)
|
||||
BOOST_THROW_EXCEPTION(ValueTooLarge() << errinfo_comment("Transaction 'value' is equal or greater than 2**256") );
|
||||
|
||||
o_tr = _o.at("to").get_str().empty() ?
|
||||
Transaction(toInt(_o.at("value")), toInt(_o.at("gasPrice")), toInt(_o.at("gasLimit")), importData(_o), toInt(_o.at("nonce")), Secret(_o.at("secretKey").get_str())) :
|
||||
Transaction(toInt(_o.at("value")), toInt(_o.at("gasPrice")), toInt(_o.at("gasLimit")), Address(_o.at("to").get_str()), importData(_o), toInt(_o.at("nonce")), Secret(_o.at("secretKey").get_str()));
|
||||
}
|
||||
else
|
||||
{
|
||||
RLPStream transactionRLPStream = createRLPStreamFromTransactionFields(_o);
|
||||
RLP transactionRLP(transactionRLPStream.out());
|
||||
try
|
||||
{
|
||||
o_tr = Transaction(transactionRLP.data(), CheckTransaction::Everything);
|
||||
}
|
||||
catch (InvalidSignature)
|
||||
{
|
||||
// create unsigned transaction
|
||||
o_tr = _o.at("to").get_str().empty() ?
|
||||
Transaction(toInt(_o.at("value")), toInt(_o.at("gasPrice")), toInt(_o.at("gasLimit")), importData(_o), toInt(_o.at("nonce"))) :
|
||||
Transaction(toInt(_o.at("value")), toInt(_o.at("gasPrice")), toInt(_o.at("gasLimit")), Address(_o.at("to").get_str()), importData(_o), toInt(_o.at("nonce")));
|
||||
}
|
||||
catch (Exception& _e)
|
||||
{
|
||||
cnote << "invalid transaction" << boost::diagnostic_information(_e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImportTest::importTransaction(json_spirit::mObject const& o_tr)
|
||||
{
|
||||
importTransaction(o_tr, m_transaction);
|
||||
}
|
||||
|
||||
int ImportTest::compareStates(State const& _stateExpect, State const& _statePost, AccountMaskMap const _expectedStateOptions, WhenError _throw)
|
||||
{
|
||||
#define CHECK(a,b) \
|
||||
{ \
|
||||
if (_throw == WhenError::Throw) \
|
||||
{ \
|
||||
TBOOST_CHECK_MESSAGE(a, b); \
|
||||
if (!a) \
|
||||
return 1; \
|
||||
} \
|
||||
else \
|
||||
{TBOOST_WARN_MESSAGE(a,b);} \
|
||||
}
|
||||
|
||||
for (auto const& a: _stateExpect.addresses())
|
||||
{
|
||||
CHECK(_statePost.addressInUse(a.first), "Check State: " << a.first << " missing expected address!");
|
||||
if (_statePost.addressInUse(a.first))
|
||||
{
|
||||
AccountMask addressOptions(true);
|
||||
if(_expectedStateOptions.size())
|
||||
{
|
||||
try
|
||||
{
|
||||
addressOptions = _expectedStateOptions.at(a.first);
|
||||
}
|
||||
catch(std::out_of_range const&)
|
||||
{
|
||||
TBOOST_ERROR("expectedStateOptions map does not match expectedState in checkExpectedState!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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.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.hasStorage())
|
||||
{
|
||||
unordered_map<u256, u256> stateStorage = _statePost.storage(a.first);
|
||||
for (auto const& s: _stateExpect.storage(a.first))
|
||||
CHECK((stateStorage[s.first] == s.second),
|
||||
"Check State: " << a.first << ": incorrect storage [" << s.first << "] = " << toHex(stateStorage[s.first]) << ", expected [" << s.first << "] = " << toHex(s.second));
|
||||
|
||||
//Check for unexpected storage values
|
||||
stateStorage = _stateExpect.storage(a.first);
|
||||
for (auto const& s: _statePost.storage(a.first))
|
||||
CHECK((stateStorage[s.first] == s.second),
|
||||
"Check State: " << a.first << ": incorrect storage [" << s.first << "] = " << toHex(s.second) << ", expected [" << s.first << "] = " << toHex(stateStorage[s.first]));
|
||||
}
|
||||
|
||||
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)) << "'");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ImportTest::exportTest(bytes const& _output)
|
||||
{
|
||||
int err = 0;
|
||||
// export output
|
||||
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)
|
||||
{
|
||||
std::string warning = "Check State: Error! Unexpected output: " + m_testObject["out"].get_str() + " Expected: " + m_testObject["expectOut"].get_str();
|
||||
if (Options::get().checkState)
|
||||
{
|
||||
bool statement = (m_testObject["out"].get_str() == m_testObject["expectOut"].get_str());
|
||||
TBOOST_CHECK_MESSAGE(statement, warning);
|
||||
if (!statement)
|
||||
err = 1;
|
||||
}
|
||||
else
|
||||
TBOOST_WARN_MESSAGE((m_testObject["out"].get_str() == m_testObject["expectOut"].get_str()), warning);
|
||||
|
||||
m_testObject.erase(m_testObject.find("expectOut"));
|
||||
}
|
||||
|
||||
// export logs
|
||||
m_testObject["logs"] = exportLog(m_logs);
|
||||
|
||||
// compare expected state with post state
|
||||
if (m_testObject.count("expect") > 0)
|
||||
{
|
||||
eth::AccountMaskMap stateMap;
|
||||
State expectState(OverlayDB(), eth::BaseState::Empty);
|
||||
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(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());
|
||||
return err;
|
||||
}
|
||||
|
||||
json_spirit::mObject fillJsonWithTransaction(Transaction _txn)
|
||||
{
|
||||
json_spirit::mObject txObject;
|
||||
txObject["nonce"] = toCompactHex(_txn.nonce(), HexPrefix::Add, 1);
|
||||
txObject["data"] = toHex(_txn.data(), 2, HexPrefix::Add);
|
||||
txObject["gasLimit"] = toCompactHex(_txn.gas(), HexPrefix::Add, 1);
|
||||
txObject["gasPrice"] = toCompactHex(_txn.gasPrice(), HexPrefix::Add, 1);
|
||||
txObject["r"] = toCompactHex(_txn.signature().r, HexPrefix::Add, 1);
|
||||
txObject["s"] = toCompactHex(_txn.signature().s, HexPrefix::Add, 1);
|
||||
txObject["v"] = toCompactHex(_txn.signature().v + 27, HexPrefix::Add, 1);
|
||||
txObject["to"] = _txn.isCreation() ? "" : toString(_txn.receiveAddress());
|
||||
txObject["value"] = toCompactHex(_txn.value(), HexPrefix::Add, 1);
|
||||
return txObject;
|
||||
}
|
||||
|
||||
json_spirit::mObject fillJsonWithState(State _state)
|
||||
{
|
||||
json_spirit::mObject oState;
|
||||
for (auto const& a: _state.addresses())
|
||||
{
|
||||
json_spirit::mObject o;
|
||||
o["balance"] = toCompactHex(_state.balance(a.first), HexPrefix::Add, 1);
|
||||
o["nonce"] = toCompactHex(_state.transactionsFrom(a.first), HexPrefix::Add, 1);
|
||||
{
|
||||
json_spirit::mObject store;
|
||||
for (auto const& s: _state.storage(a.first))
|
||||
store[toCompactHex(s.first, HexPrefix::Add, 1)] = toCompactHex(s.second, HexPrefix::Add, 1);
|
||||
o["storage"] = store;
|
||||
}
|
||||
o["code"] = toHex(_state.code(a.first), 2, HexPrefix::Add);
|
||||
oState[toString(a.first)] = o;
|
||||
}
|
||||
return oState;
|
||||
}
|
||||
|
||||
json_spirit::mArray exportLog(eth::LogEntries _logs)
|
||||
{
|
||||
json_spirit::mArray ret;
|
||||
if (_logs.size() == 0) return ret;
|
||||
for (LogEntry const& l: _logs)
|
||||
{
|
||||
json_spirit::mObject o;
|
||||
o["address"] = toString(l.address);
|
||||
json_spirit::mArray topics;
|
||||
for (auto const& t: l.topics)
|
||||
topics.push_back(toString(t));
|
||||
o["topics"] = topics;
|
||||
o["data"] = toHex(l.data, 2, HexPrefix::Add);
|
||||
o["bloom"] = toString(l.bloom());
|
||||
ret.push_back(o);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
u256 toInt(json_spirit::mValue const& _v)
|
||||
{
|
||||
switch (_v.type())
|
||||
{
|
||||
case json_spirit::str_type: return u256(_v.get_str());
|
||||
case json_spirit::int_type: return (u256)_v.get_uint64();
|
||||
case json_spirit::bool_type: return (u256)(uint64_t)_v.get_bool();
|
||||
case json_spirit::real_type: return (u256)(uint64_t)_v.get_real();
|
||||
default: cwarn << "Bad type for scalar: " << _v.type();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte toByte(json_spirit::mValue const& _v)
|
||||
{
|
||||
switch (_v.type())
|
||||
{
|
||||
case json_spirit::str_type: return (byte)stoi(_v.get_str());
|
||||
case json_spirit::int_type: return (byte)_v.get_uint64();
|
||||
case json_spirit::bool_type: return (byte)_v.get_bool();
|
||||
case json_spirit::real_type: return (byte)_v.get_real();
|
||||
default: cwarn << "Bad type for scalar: " << _v.type();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes importByteArray(std::string const& _str)
|
||||
{
|
||||
return fromHex(_str.substr(0, 2) == "0x" ? _str.substr(2) : _str, WhenError::Throw);
|
||||
}
|
||||
|
||||
bytes importData(json_spirit::mObject const& _o)
|
||||
{
|
||||
bytes data;
|
||||
if (_o.at("data").type() == json_spirit::str_type)
|
||||
data = importByteArray(_o.at("data").get_str());
|
||||
else
|
||||
for (auto const& j: _o.at("data").get_array())
|
||||
data.push_back(toByte(j));
|
||||
return data;
|
||||
}
|
||||
|
||||
bytes importCode(json_spirit::mObject& _o)
|
||||
{
|
||||
bytes code;
|
||||
if (_o["code"].type() == json_spirit::str_type)
|
||||
if (_o["code"].get_str().find("0x") != 0)
|
||||
code = compileLLL(_o["code"].get_str(), false);
|
||||
else
|
||||
code = fromHex(_o["code"].get_str().substr(2));
|
||||
else if (_o["code"].type() == json_spirit::array_type)
|
||||
{
|
||||
code.clear();
|
||||
for (auto const& j: _o["code"].get_array())
|
||||
code.push_back(toByte(j));
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
LogEntries importLog(json_spirit::mArray& _a)
|
||||
{
|
||||
LogEntries logEntries;
|
||||
for (auto const& l: _a)
|
||||
{
|
||||
json_spirit::mObject o = l.get_obj();
|
||||
// cant use BOOST_REQUIRE, because this function is used outside boost test (createRandomTest)
|
||||
assert(o.count("address") > 0);
|
||||
assert(o.count("topics") > 0);
|
||||
assert(o.count("data") > 0);
|
||||
assert(o.count("bloom") > 0);
|
||||
LogEntry log;
|
||||
log.address = Address(o["address"].get_str());
|
||||
for (auto const& t: o["topics"].get_array())
|
||||
log.topics.push_back(h256(t.get_str()));
|
||||
log.data = importData(o);
|
||||
logEntries.push_back(log);
|
||||
}
|
||||
return logEntries;
|
||||
}
|
||||
|
||||
void checkOutput(bytes const& _output, json_spirit::mObject& _o)
|
||||
{
|
||||
int j = 0;
|
||||
|
||||
if (_o["out"].get_str().find("#") == 0)
|
||||
{TBOOST_CHECK(((u256)_output.size() == toInt(_o["out"].get_str().substr(1))));}
|
||||
else if (_o["out"].type() == json_spirit::array_type)
|
||||
for (auto const& d: _o["out"].get_array())
|
||||
{
|
||||
TBOOST_CHECK_MESSAGE((_output[j] == toInt(d)), "Output byte [" << j << "] different!");
|
||||
++j;
|
||||
}
|
||||
else if (_o["out"].get_str().find("0x") == 0)
|
||||
{TBOOST_CHECK((_output == fromHex(_o["out"].get_str().substr(2))));}
|
||||
else
|
||||
TBOOST_CHECK((_output == fromHex(_o["out"].get_str())));
|
||||
}
|
||||
|
||||
void checkStorage(map<u256, u256> _expectedStore, map<u256, u256> _resultStore, Address _expectedAddr)
|
||||
{
|
||||
_expectedAddr = _expectedAddr; //unsed parametr when macro
|
||||
for (auto&& expectedStorePair : _expectedStore)
|
||||
{
|
||||
auto& expectedStoreKey = expectedStorePair.first;
|
||||
auto resultStoreIt = _resultStore.find(expectedStoreKey);
|
||||
if (resultStoreIt == _resultStore.end())
|
||||
{TBOOST_ERROR(_expectedAddr << ": missing store key " << expectedStoreKey);}
|
||||
else
|
||||
{
|
||||
auto& expectedStoreValue = expectedStorePair.second;
|
||||
auto& resultStoreValue = resultStoreIt->second;
|
||||
TBOOST_CHECK_MESSAGE((expectedStoreValue == resultStoreValue), _expectedAddr << ": store[" << expectedStoreKey << "] = " << resultStoreValue << ", expected " << expectedStoreValue);
|
||||
}
|
||||
}
|
||||
TBOOST_CHECK_EQUAL(_resultStore.size(), _expectedStore.size());
|
||||
for (auto&& resultStorePair: _resultStore)
|
||||
{
|
||||
if (!_expectedStore.count(resultStorePair.first))
|
||||
TBOOST_ERROR(_expectedAddr << ": unexpected store key " << resultStorePair.first);
|
||||
}
|
||||
}
|
||||
|
||||
void checkLog(LogEntries _resultLogs, LogEntries _expectedLogs)
|
||||
{
|
||||
TBOOST_REQUIRE_EQUAL(_resultLogs.size(), _expectedLogs.size());
|
||||
|
||||
for (size_t i = 0; i < _resultLogs.size(); ++i)
|
||||
{
|
||||
TBOOST_CHECK_EQUAL(_resultLogs[i].address, _expectedLogs[i].address);
|
||||
TBOOST_CHECK_EQUAL(_resultLogs[i].topics, _expectedLogs[i].topics);
|
||||
TBOOST_CHECK((_resultLogs[i].data == _expectedLogs[i].data));
|
||||
}
|
||||
}
|
||||
|
||||
void checkCallCreates(eth::Transactions _resultCallCreates, eth::Transactions _expectedCallCreates)
|
||||
{
|
||||
TBOOST_REQUIRE_EQUAL(_resultCallCreates.size(), _expectedCallCreates.size());
|
||||
|
||||
for (size_t i = 0; i < _resultCallCreates.size(); ++i)
|
||||
{
|
||||
TBOOST_CHECK((_resultCallCreates[i].data() == _expectedCallCreates[i].data()));
|
||||
TBOOST_CHECK((_resultCallCreates[i].receiveAddress() == _expectedCallCreates[i].receiveAddress()));
|
||||
TBOOST_CHECK((_resultCallCreates[i].gas() == _expectedCallCreates[i].gas()));
|
||||
TBOOST_CHECK((_resultCallCreates[i].value() == _expectedCallCreates[i].value()));
|
||||
}
|
||||
}
|
||||
|
||||
void userDefinedTest(std::function<void(json_spirit::mValue&, bool)> doTests)
|
||||
{
|
||||
if (!Options::get().singleTest)
|
||||
return;
|
||||
|
||||
if (Options::get().singleTestFile.empty() || Options::get().singleTestName.empty())
|
||||
{
|
||||
cnote << "Missing user test specification\nUsage: testeth --singletest <filename> <testname>\n";
|
||||
return;
|
||||
}
|
||||
|
||||
auto& filename = Options::get().singleTestFile;
|
||||
auto& testname = Options::get().singleTestName;
|
||||
|
||||
if (g_logVerbosity != -1)
|
||||
VerbosityHolder sentinel(12);
|
||||
|
||||
try
|
||||
{
|
||||
cnote << "Testing user defined test: " << filename;
|
||||
json_spirit::mValue v;
|
||||
string s = contentsString(filename);
|
||||
TBOOST_REQUIRE_MESSAGE((s.length() > 0), "Contents of " + filename + " is empty. ");
|
||||
json_spirit::read_string(s, v);
|
||||
json_spirit::mObject oSingleTest;
|
||||
|
||||
json_spirit::mObject::const_iterator pos = v.get_obj().find(testname);
|
||||
if (pos == v.get_obj().end())
|
||||
{
|
||||
cnote << "Could not find test: " << testname << " in " << filename << "\n";
|
||||
return;
|
||||
}
|
||||
else
|
||||
oSingleTest[pos->first] = pos->second;
|
||||
|
||||
json_spirit::mValue v_singleTest(oSingleTest);
|
||||
doTests(v_singleTest, test::Options::get().fillTests);
|
||||
}
|
||||
catch (Exception const& _e)
|
||||
{
|
||||
TBOOST_ERROR("Failed Test with Exception: " << diagnostic_information(_e));
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
TBOOST_ERROR("Failed Test with Exception: " << _e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void executeTests(const string& _name, const string& _testPathAppendix, const boost::filesystem::path _pathToFiller, std::function<void(json_spirit::mValue&, bool)> doTests)
|
||||
{
|
||||
string testPath = getTestPath();
|
||||
testPath += _testPathAppendix;
|
||||
|
||||
if (Options::get().stats)
|
||||
Listener::registerListener(Stats::get());
|
||||
|
||||
if (Options::get().fillTests)
|
||||
{
|
||||
try
|
||||
{
|
||||
cnote << "Populating tests...";
|
||||
json_spirit::mValue v;
|
||||
boost::filesystem::path p(__FILE__);
|
||||
string s = asString(dev::contents(_pathToFiller.string() + "/" + _name + "Filler.json"));
|
||||
TBOOST_REQUIRE_MESSAGE((s.length() > 0), "Contents of " + _pathToFiller.string() + "/" + _name + "Filler.json is empty.");
|
||||
json_spirit::read_string(s, v);
|
||||
doTests(v, true);
|
||||
writeFile(testPath + "/" + _name + ".json", asBytes(json_spirit::write_string(v, true)));
|
||||
}
|
||||
catch (Exception const& _e)
|
||||
{
|
||||
TBOOST_ERROR("Failed filling test with Exception: " << diagnostic_information(_e));
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
TBOOST_ERROR("Failed filling test with Exception: " << _e.what());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cnote << "TEST " << _name << ":";
|
||||
json_spirit::mValue v;
|
||||
string s = asString(dev::contents(testPath + "/" + _name + ".json"));
|
||||
TBOOST_REQUIRE_MESSAGE((s.length() > 0), "Contents of " + testPath + "/" + _name + ".json is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?");
|
||||
json_spirit::read_string(s, v);
|
||||
Listener::notifySuiteStarted(_name);
|
||||
doTests(v, false);
|
||||
}
|
||||
catch (Exception const& _e)
|
||||
{
|
||||
TBOOST_ERROR("Failed test with Exception: " << diagnostic_information(_e));
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
TBOOST_ERROR("Failed test with Exception: " << _e.what());
|
||||
}
|
||||
}
|
||||
|
||||
RLPStream createRLPStreamFromTransactionFields(json_spirit::mObject const& _tObj)
|
||||
{
|
||||
//Construct Rlp of the given transaction
|
||||
RLPStream rlpStream;
|
||||
rlpStream.appendList(_tObj.size());
|
||||
|
||||
if (_tObj.count("nonce"))
|
||||
rlpStream << bigint(_tObj.at("nonce").get_str());
|
||||
|
||||
if (_tObj.count("gasPrice"))
|
||||
rlpStream << bigint(_tObj.at("gasPrice").get_str());
|
||||
|
||||
if (_tObj.count("gasLimit"))
|
||||
rlpStream << bigint(_tObj.at("gasLimit").get_str());
|
||||
|
||||
if (_tObj.count("to"))
|
||||
{
|
||||
if (_tObj.at("to").get_str().empty())
|
||||
rlpStream << "";
|
||||
else
|
||||
rlpStream << importByteArray(_tObj.at("to").get_str());
|
||||
}
|
||||
|
||||
if (_tObj.count("value"))
|
||||
rlpStream << bigint(_tObj.at("value").get_str());
|
||||
|
||||
if (_tObj.count("data"))
|
||||
rlpStream << importData(_tObj);
|
||||
|
||||
if (_tObj.count("v"))
|
||||
rlpStream << bigint(_tObj.at("v").get_str());
|
||||
|
||||
if (_tObj.count("r"))
|
||||
rlpStream << bigint(_tObj.at("r").get_str());
|
||||
|
||||
if (_tObj.count("s"))
|
||||
rlpStream << bigint(_tObj.at("s").get_str());
|
||||
|
||||
if (_tObj.count("extrafield"))
|
||||
rlpStream << bigint(_tObj.at("extrafield").get_str());
|
||||
|
||||
return rlpStream;
|
||||
}
|
||||
|
||||
Options::Options()
|
||||
{
|
||||
auto argc = boost::unit_test::framework::master_test_suite().argc;
|
||||
auto argv = boost::unit_test::framework::master_test_suite().argv;
|
||||
|
||||
for (auto i = 0; i < argc; ++i)
|
||||
{
|
||||
auto arg = std::string{argv[i]};
|
||||
if (arg == "--vm" && i + 1 < argc)
|
||||
{
|
||||
string vmKind = argv[++i];
|
||||
if (vmKind == "interpreter")
|
||||
VMFactory::setKind(VMKind::Interpreter);
|
||||
else if (vmKind == "jit")
|
||||
VMFactory::setKind(VMKind::JIT);
|
||||
else if (vmKind == "smart")
|
||||
VMFactory::setKind(VMKind::Smart);
|
||||
else
|
||||
cerr << "Unknown VM kind: " << vmKind << endl;
|
||||
}
|
||||
else if (arg == "--jit") // TODO: Remove deprecated option "--jit"
|
||||
VMFactory::setKind(VMKind::JIT);
|
||||
else if (arg == "--vmtrace")
|
||||
vmtrace = true;
|
||||
else if (arg == "--filltests")
|
||||
fillTests = true;
|
||||
else if (arg == "--stats" && i + 1 < argc)
|
||||
{
|
||||
stats = true;
|
||||
statsOutFile = argv[i + 1];
|
||||
}
|
||||
else if (arg == "--performance")
|
||||
performance = true;
|
||||
else if (arg == "--quadratic")
|
||||
quadratic = true;
|
||||
else if (arg == "--memory")
|
||||
memory = true;
|
||||
else if (arg == "--inputlimits")
|
||||
inputLimits = true;
|
||||
else if (arg == "--bigdata")
|
||||
bigData = true;
|
||||
else if (arg == "--checkstate")
|
||||
checkState = true;
|
||||
else if (arg == "--wallet")
|
||||
wallet = true;
|
||||
else if (arg == "--nonetwork")
|
||||
nonetwork = true;
|
||||
else if (arg == "--network")
|
||||
nonetwork = false;
|
||||
else if (arg == "--nodag")
|
||||
nodag = true;
|
||||
else if (arg == "--all")
|
||||
{
|
||||
performance = true;
|
||||
quadratic = true;
|
||||
memory = true;
|
||||
inputLimits = true;
|
||||
bigData = true;
|
||||
wallet = true;
|
||||
}
|
||||
else if (arg == "--singletest" && i + 1 < argc)
|
||||
{
|
||||
singleTest = true;
|
||||
auto name1 = std::string{argv[i + 1]};
|
||||
if (i + 1 < argc) // two params
|
||||
{
|
||||
auto name2 = std::string{argv[i + 2]};
|
||||
if (name2[0] == '-') // not param, another option
|
||||
singleTestName = std::move(name1);
|
||||
else
|
||||
{
|
||||
singleTestFile = std::move(name1);
|
||||
singleTestName = std::move(name2);
|
||||
}
|
||||
}
|
||||
else
|
||||
singleTestName = std::move(name1);
|
||||
}
|
||||
else if (arg == "--fulloutput")
|
||||
fulloutput = true;
|
||||
else if (arg == "--verbosity" && i + 1 < argc)
|
||||
{
|
||||
static std::ostringstream strCout; //static string to redirect logs to
|
||||
std::string indentLevel = std::string{argv[i + 1]};
|
||||
if (indentLevel == "0")
|
||||
{
|
||||
logVerbosity = Verbosity::None;
|
||||
std::cout.rdbuf(strCout.rdbuf());
|
||||
std::cerr.rdbuf(strCout.rdbuf());
|
||||
}
|
||||
else if (indentLevel == "1")
|
||||
logVerbosity = Verbosity::NiceReport;
|
||||
else
|
||||
logVerbosity = Verbosity::Full;
|
||||
}
|
||||
}
|
||||
|
||||
//Default option
|
||||
if (logVerbosity == Verbosity::NiceReport)
|
||||
g_logVerbosity = -1; //disable cnote but leave cerr and cout
|
||||
}
|
||||
|
||||
Options const& Options::get()
|
||||
{
|
||||
static Options instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
LastHashes lastHashes(u256 _currentBlockNumber)
|
||||
{
|
||||
LastHashes ret;
|
||||
for (u256 i = 1; i <= 256 && i <= _currentBlockNumber; ++i)
|
||||
ret.push_back(sha3(toString(_currentBlockNumber - i)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev::eth::Ethash::BlockHeader constructHeader(
|
||||
h256 const& _parentHash,
|
||||
h256 const& _sha3Uncles,
|
||||
Address const& _coinbaseAddress,
|
||||
h256 const& _stateRoot,
|
||||
h256 const& _transactionsRoot,
|
||||
h256 const& _receiptsRoot,
|
||||
dev::eth::LogBloom const& _logBloom,
|
||||
u256 const& _difficulty,
|
||||
u256 const& _number,
|
||||
u256 const& _gasLimit,
|
||||
u256 const& _gasUsed,
|
||||
u256 const& _timestamp,
|
||||
bytes const& _extraData)
|
||||
{
|
||||
RLPStream rlpStream;
|
||||
rlpStream.appendList(Ethash::BlockHeader::Fields);
|
||||
|
||||
rlpStream << _parentHash << _sha3Uncles << _coinbaseAddress << _stateRoot << _transactionsRoot << _receiptsRoot << _logBloom
|
||||
<< _difficulty << _number << _gasLimit << _gasUsed << _timestamp << _extraData << h256{} << Nonce{};
|
||||
|
||||
return Ethash::BlockHeader(rlpStream.out(), CheckNothing, h256{}, HeaderData);
|
||||
}
|
||||
|
||||
void updateEthashSeal(dev::eth::Ethash::BlockHeader& _header, h256 const& _mixHash, dev::eth::Nonce const& _nonce)
|
||||
{
|
||||
RLPStream source;
|
||||
_header.streamRLP(source);
|
||||
RLP sourceRlp(source.out());
|
||||
RLPStream header;
|
||||
header.appendList(Ethash::BlockHeader::Fields);
|
||||
for (size_t i = 0; i < BlockInfo::BasicFields; i++)
|
||||
header << sourceRlp[i];
|
||||
|
||||
header << _mixHash << _nonce;
|
||||
_header = Ethash::BlockHeader(header.out(), CheckNothing, h256{}, HeaderData);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
Listener* g_listener;
|
||||
}
|
||||
|
||||
void Listener::registerListener(Listener& _listener)
|
||||
{
|
||||
g_listener = &_listener;
|
||||
}
|
||||
|
||||
void Listener::notifySuiteStarted(std::string const& _name)
|
||||
{
|
||||
if (g_listener)
|
||||
g_listener->suiteStarted(_name);
|
||||
}
|
||||
|
||||
void Listener::notifyTestStarted(std::string const& _name)
|
||||
{
|
||||
if (g_listener)
|
||||
g_listener->testStarted(_name);
|
||||
}
|
||||
|
||||
void Listener::notifyTestFinished()
|
||||
{
|
||||
if (g_listener)
|
||||
g_listener->testFinished();
|
||||
}
|
||||
|
||||
} } // namespaces
|
290
test/TestHelper.h
Normal file
290
test/TestHelper.h
Normal file
@ -0,0 +1,290 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/** @file TestHelper.h
|
||||
* @author Marko Simovic <markobarko@gmail.com>
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "JsonSpiritHeaders.h"
|
||||
#include <libethcore/Ethash.h>
|
||||
#include <libethereum/State.h>
|
||||
#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();
|
||||
#define TBOOST_CHECK_EQUAL(arg1, arg2) if(arg1 != arg2) throw dev::Exception();
|
||||
#define TBOOST_CHECK(arg) if(arg == false) throw dev::Exception();
|
||||
#define TBOOST_REQUIRE_MESSAGE(arg1, arg2) if(arg1 == false) throw dev::Exception();
|
||||
#define TBOOST_CHECK_MESSAGE(arg1, arg2) if(arg1 == false) throw dev::Exception();
|
||||
#define TBOOST_WARN_MESSAGE(arg1, arg2) throw dev::Exception();
|
||||
#define TBOOST_ERROR(arg) throw dev::Exception();
|
||||
#else
|
||||
#define TBOOST_REQUIRE(arg) BOOST_REQUIRE(arg)
|
||||
#define TBOOST_REQUIRE_EQUAL(arg1, arg2) BOOST_REQUIRE_EQUAL(arg1, arg2)
|
||||
#define TBOOST_CHECK(arg) BOOST_CHECK(arg)
|
||||
#define TBOOST_CHECK_EQUAL(arg1, arg2) BOOST_CHECK_EQUAL(arg1, arg2)
|
||||
#define TBOOST_CHECK_MESSAGE(arg1, arg2) BOOST_CHECK_MESSAGE(arg1, arg2)
|
||||
#define TBOOST_REQUIRE_MESSAGE(arg1, arg2) BOOST_REQUIRE_MESSAGE(arg1, arg2)
|
||||
#define TBOOST_WARN_MESSAGE(arg1, arg2) BOOST_WARN_MESSAGE(arg1, arg2)
|
||||
#define TBOOST_ERROR(arg) BOOST_ERROR(arg)
|
||||
#endif
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace eth
|
||||
{
|
||||
|
||||
class Client;
|
||||
class State;
|
||||
|
||||
void mine(Client& c, int numBlocks);
|
||||
void connectClients(Client& c1, Client& c2);
|
||||
void mine(Block& _s, BlockChain const& _bc);
|
||||
void mine(Ethash::BlockHeader& _bi);
|
||||
|
||||
}
|
||||
|
||||
namespace test
|
||||
{
|
||||
|
||||
/// Make sure that no Exception is thrown during testing. If one is thrown show its info and fail the test.
|
||||
/// Our version of BOOST_REQUIRE_NO_THROW()
|
||||
/// @param _statenent The statement for which to make sure no exceptions are thrown
|
||||
/// @param _message A message to act as a prefix to the expression's error information
|
||||
#define ETH_TEST_REQUIRE_NO_THROW(_statement, _message) \
|
||||
do \
|
||||
{ \
|
||||
try \
|
||||
{ \
|
||||
BOOST_TEST_PASSPOINT(); \
|
||||
_statement; \
|
||||
} \
|
||||
catch (boost::exception const& _e) \
|
||||
{ \
|
||||
auto msg = std::string(_message " due to an exception thrown by " \
|
||||
BOOST_STRINGIZE(_statement) "\n") + boost::diagnostic_information(_e); \
|
||||
BOOST_CHECK_IMPL(false, msg, REQUIRE, CHECK_MSG); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
BOOST_CHECK_IMPL(false, "Unknown exception thrown by " \
|
||||
BOOST_STRINGIZE(_statement), REQUIRE, CHECK_MSG); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/// Check if an Exception is thrown during testing. If one is thrown show its info and continue the test
|
||||
/// Our version of BOOST_CHECK_NO_THROW()
|
||||
/// @param _statement The statement for which to make sure no exceptions are thrown
|
||||
/// @param _message A message to act as a prefix to the expression's error information
|
||||
#define ETH_TEST_CHECK_NO_THROW(_statement, _message) \
|
||||
do \
|
||||
{ \
|
||||
try \
|
||||
{ \
|
||||
BOOST_TEST_PASSPOINT(); \
|
||||
_statement; \
|
||||
} \
|
||||
catch (boost::exception const& _e) \
|
||||
{ \
|
||||
auto msg = std::string(_message " due to an exception thrown by " \
|
||||
BOOST_STRINGIZE(_statement) "\n") + boost::diagnostic_information(_e); \
|
||||
BOOST_CHECK_IMPL(false, msg, CHECK, CHECK_MSG); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
BOOST_CHECK_IMPL(false, "Unknown exception thrown by " \
|
||||
BOOST_STRINGIZE(_statement), CHECK, CHECK_MSG ); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
enum class testType
|
||||
{
|
||||
StateTests,
|
||||
BlockChainTests,
|
||||
Other
|
||||
};
|
||||
|
||||
class ImportTest
|
||||
{
|
||||
public:
|
||||
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, eth::AccountMaskMap& o_mask);
|
||||
static void importTransaction (json_spirit::mObject const& _o, eth::Transaction& o_tr);
|
||||
void importTransaction(json_spirit::mObject const& _o);
|
||||
static json_spirit::mObject& makeAllFieldsHex(json_spirit::mObject& _o);
|
||||
|
||||
bytes executeTest();
|
||||
int exportTest(bytes const& _output);
|
||||
static int 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::EnvInfo m_envInfo;
|
||||
eth::Transaction m_transaction;
|
||||
eth::LogEntries m_logs;
|
||||
eth::LogEntries m_logsExpected;
|
||||
|
||||
private:
|
||||
json_spirit::mObject& m_testObject;
|
||||
};
|
||||
|
||||
class ZeroGasPricer: public eth::GasPricer
|
||||
{
|
||||
protected:
|
||||
u256 ask(eth::Block const&) const override { return 0; }
|
||||
u256 bid(eth::TransactionPriority = eth::TransactionPriority::Medium) const override { return 0; }
|
||||
};
|
||||
|
||||
// helping functions
|
||||
u256 toInt(json_spirit::mValue const& _v);
|
||||
byte toByte(json_spirit::mValue const& _v);
|
||||
bytes importCode(json_spirit::mObject& _o);
|
||||
bytes importData(json_spirit::mObject const& _o);
|
||||
bytes importByteArray(std::string const& _str);
|
||||
eth::LogEntries importLog(json_spirit::mArray& _o);
|
||||
json_spirit::mArray exportLog(eth::LogEntries _logs);
|
||||
void checkOutput(bytes const& _output, json_spirit::mObject& _o);
|
||||
void checkStorage(std::map<u256, u256> _expectedStore, std::map<u256, u256> _resultStore, Address _expectedAddr);
|
||||
void checkLog(eth::LogEntries _resultLogs, eth::LogEntries _expectedLogs);
|
||||
void checkCallCreates(eth::Transactions _resultCallCreates, eth::Transactions _expectedCallCreates);
|
||||
dev::eth::Ethash::BlockHeader constructHeader(
|
||||
h256 const& _parentHash,
|
||||
h256 const& _sha3Uncles,
|
||||
Address const& _coinbaseAddress,
|
||||
h256 const& _stateRoot,
|
||||
h256 const& _transactionsRoot,
|
||||
h256 const& _receiptsRoot,
|
||||
dev::eth::LogBloom const& _logBloom,
|
||||
u256 const& _difficulty,
|
||||
u256 const& _number,
|
||||
u256 const& _gasLimit,
|
||||
u256 const& _gasUsed,
|
||||
u256 const& _timestamp,
|
||||
bytes const& _extraData);
|
||||
void updateEthashSeal(dev::eth::Ethash::BlockHeader& _header, h256 const& _mixHash, dev::eth::Nonce const& _nonce);
|
||||
void executeTests(const std::string& _name, const std::string& _testPathAppendix, const boost::filesystem::path _pathToFiller, std::function<void(json_spirit::mValue&, bool)> doTests);
|
||||
void userDefinedTest(std::function<void(json_spirit::mValue&, bool)> doTests);
|
||||
RLPStream createRLPStreamFromTransactionFields(json_spirit::mObject const& _tObj);
|
||||
eth::LastHashes lastHashes(u256 _currentBlockNumber);
|
||||
json_spirit::mObject fillJsonWithState(eth::State _state);
|
||||
json_spirit::mObject fillJsonWithTransaction(eth::Transaction _txn);
|
||||
|
||||
//Fill Test Functions
|
||||
void doTransactionTests(json_spirit::mValue& _v, bool _fillin);
|
||||
void doStateTests(json_spirit::mValue& v, bool _fillin);
|
||||
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>
|
||||
void checkAddresses(mapType& _expectedAddrs, mapType& _resultAddrs)
|
||||
{
|
||||
for (auto& resultPair : _resultAddrs)
|
||||
{
|
||||
auto& resultAddr = resultPair.first;
|
||||
auto expectedAddrIt = _expectedAddrs.find(resultAddr);
|
||||
if (expectedAddrIt == _expectedAddrs.end())
|
||||
TBOOST_ERROR("Missing result address " << resultAddr);
|
||||
}
|
||||
TBOOST_CHECK((_expectedAddrs == _resultAddrs));
|
||||
}*/
|
||||
|
||||
enum class Verbosity
|
||||
{
|
||||
Full,
|
||||
NiceReport,
|
||||
None
|
||||
};
|
||||
|
||||
class Options
|
||||
{
|
||||
public:
|
||||
bool vmtrace = false; ///< Create EVM execution tracer // TODO: Link with log verbosity?
|
||||
bool fillTests = false; ///< Create JSON test files from execution results
|
||||
bool stats = false; ///< Execution time stats
|
||||
std::string statsOutFile; ///< Stats output file. "out" for standard output
|
||||
bool checkState = false;///< Throw error when checking test states
|
||||
bool fulloutput = false;///< Replace large output to just it's length
|
||||
Verbosity logVerbosity = Verbosity::NiceReport;
|
||||
|
||||
/// Test selection
|
||||
/// @{
|
||||
bool singleTest = false;
|
||||
std::string singleTestFile;
|
||||
std::string singleTestName;
|
||||
bool performance = false;
|
||||
bool quadratic = false;
|
||||
bool memory = false;
|
||||
bool inputLimits = false;
|
||||
bool bigData = false;
|
||||
bool wallet = false;
|
||||
bool nonetwork = true;
|
||||
bool nodag = true;
|
||||
/// @}
|
||||
|
||||
/// Get reference to options
|
||||
/// The first time used, options are parsed
|
||||
static Options const& get();
|
||||
|
||||
private:
|
||||
Options();
|
||||
Options(Options const&) = delete;
|
||||
};
|
||||
|
||||
/// Allows observing test execution process.
|
||||
/// This class also provides methods for registering and notifying the listener
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
virtual ~Listener() = default;
|
||||
|
||||
virtual void suiteStarted(std::string const&) {}
|
||||
virtual void testStarted(std::string const& _name) = 0;
|
||||
virtual void testFinished() = 0;
|
||||
|
||||
static void registerListener(Listener& _listener);
|
||||
static void notifySuiteStarted(std::string const& _name);
|
||||
static void notifyTestStarted(std::string const& _name);
|
||||
static void notifyTestFinished();
|
||||
|
||||
/// Test started/finished notification RAII helper
|
||||
class ExecTimeGuard
|
||||
{
|
||||
public:
|
||||
ExecTimeGuard(std::string const& _testName) { notifyTestStarted(_testName); }
|
||||
~ExecTimeGuard() { notifyTestFinished(); }
|
||||
ExecTimeGuard(ExecTimeGuard const&) = delete;
|
||||
ExecTimeGuard& operator=(ExecTimeGuard) = delete;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
}
|
122
test/TestUtils.cpp
Normal file
122
test/TestUtils.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/** @file TestUtils.cpp
|
||||
* @author Marek Kotewicz <marek@ethdev.com>
|
||||
* @date 2015
|
||||
*/
|
||||
|
||||
#include <thread>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <libdevcrypto/Common.h>
|
||||
#include <libtestutils/Common.h>
|
||||
#include <libtestutils/BlockChainLoader.h>
|
||||
#include <libtestutils/FixedClient.h>
|
||||
#include "TestUtils.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
using namespace dev::test;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
bool getCommandLineOption(std::string const& _name);
|
||||
std::string getCommandLineArgument(std::string const& _name, bool _require = false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool dev::test::getCommandLineOption(string const& _name)
|
||||
{
|
||||
auto argc = boost::unit_test::framework::master_test_suite().argc;
|
||||
auto argv = boost::unit_test::framework::master_test_suite().argv;
|
||||
bool result = false;
|
||||
for (auto i = 0; !result && i < argc; ++i)
|
||||
result = _name == argv[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string dev::test::getCommandLineArgument(string const& _name, bool _require)
|
||||
{
|
||||
auto argc = boost::unit_test::framework::master_test_suite().argc;
|
||||
auto argv = boost::unit_test::framework::master_test_suite().argv;
|
||||
for (auto i = 1; i < argc; ++i)
|
||||
{
|
||||
string str = argv[i];
|
||||
if (_name == str.substr(0, _name.size()))
|
||||
return str.substr(str.find("=") + 1);
|
||||
}
|
||||
if (_require)
|
||||
BOOST_ERROR("Failed getting command line argument: " << _name << " from: " << argv);
|
||||
return "";
|
||||
}
|
||||
|
||||
LoadTestFileFixture::LoadTestFileFixture()
|
||||
{
|
||||
m_json = loadJsonFromFile(toTestFilePath(getCommandLineArgument("--eth_testfile")));
|
||||
}
|
||||
|
||||
void ParallelFixture::enumerateThreads(std::function<void()> callback) const
|
||||
{
|
||||
size_t threadsCount = std::stoul(getCommandLineArgument("--eth_threads"), nullptr, 10);
|
||||
|
||||
vector<thread> workers;
|
||||
for (size_t i = 0; i < threadsCount; i++)
|
||||
workers.emplace_back(callback);
|
||||
|
||||
for_each(workers.begin(), workers.end(), [](thread &t)
|
||||
{
|
||||
t.join();
|
||||
});
|
||||
}
|
||||
|
||||
void BlockChainFixture::enumerateBlockchains(std::function<void(Json::Value const&, dev::eth::BlockChain const&, State state)> callback) const
|
||||
{
|
||||
for (string const& name: m_json.getMemberNames())
|
||||
{
|
||||
BlockChainLoader bcl(m_json[name]);
|
||||
callback(m_json[name], bcl.bc(), bcl.state());
|
||||
}
|
||||
}
|
||||
|
||||
void ClientBaseFixture::enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const
|
||||
{
|
||||
enumerateBlockchains([&callback](Json::Value const& _json, BlockChain const& _bc, State _state) -> void
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
void ParallelClientBaseFixture::enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const
|
||||
{
|
||||
ClientBaseFixture::enumerateClients([this, &callback](Json::Value const& _json, dev::eth::ClientBase& _client) -> void
|
||||
{
|
||||
// json is being copied here
|
||||
enumerateThreads([callback, _json, &_client]() -> void
|
||||
{
|
||||
callback(_json, _client);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
83
test/TestUtils.h
Normal file
83
test/TestUtils.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/** @file TestUtils.h
|
||||
* @author Marek Kotewicz <marek@ethdev.com>
|
||||
* @date 2015
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <json/json.h>
|
||||
#include <libdevcore/TransientDirectory.h>
|
||||
#include <libethereum/BlockChain.h>
|
||||
#include <libethereum/ClientBase.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
// should be used for multithread tests
|
||||
static SharedMutex x_boostTest;
|
||||
#define ETH_CHECK_EQUAL(x, y) { dev::WriteGuard(x_boostTest); BOOST_CHECK_EQUAL(x, y); }
|
||||
#define ETH_CHECK_EQUAL_COLLECTIONS(xb, xe, yb, ye) { dev::WriteGuard(x_boostTest); BOOST_CHECK_EQUAL_COLLECTIONS(xb, xe, yb, ye); }
|
||||
#define ETH_REQUIRE(x) { dev::WriteGuard(x_boostTest); BOOST_REQUIRE(x); }
|
||||
|
||||
struct LoadTestFileFixture
|
||||
{
|
||||
LoadTestFileFixture();
|
||||
|
||||
protected:
|
||||
Json::Value m_json;
|
||||
};
|
||||
|
||||
struct ParallelFixture
|
||||
{
|
||||
void enumerateThreads(std::function<void()> callback) const;
|
||||
};
|
||||
|
||||
struct BlockChainFixture: public LoadTestFileFixture
|
||||
{
|
||||
void enumerateBlockchains(std::function<void(Json::Value const&, dev::eth::BlockChain const&, dev::eth::State state)> callback) const;
|
||||
};
|
||||
|
||||
struct ClientBaseFixture: public BlockChainFixture
|
||||
{
|
||||
void enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const;
|
||||
};
|
||||
|
||||
// important BOOST TEST do have problems with thread safety!!!
|
||||
// BOOST_CHECK is not thread safe
|
||||
// BOOST_MESSAGE is not thread safe
|
||||
// http://boost.2283326.n4.nabble.com/Is-boost-test-thread-safe-td3471644.html
|
||||
// http://lists.boost.org/boost-users/2010/03/57691.php
|
||||
// worth reading
|
||||
// https://codecrafter.wordpress.com/2012/11/01/c-unit-test-framework-adapter-part-3/
|
||||
struct ParallelClientBaseFixture: public ClientBaseFixture, public ParallelFixture
|
||||
{
|
||||
void enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const;
|
||||
};
|
||||
|
||||
struct JsonRpcFixture: public ClientBaseFixture
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
92
test/boostTest.cpp
Normal file
92
test/boostTest.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/** @file boostTest.cpp
|
||||
* @author Marko Simovic <markobarko@gmail.com>
|
||||
* @date 2014
|
||||
* Stub for generating main boost.test module.
|
||||
* Original code taken from boost sources.
|
||||
*/
|
||||
|
||||
#define BOOST_TEST_MODULE EthereumTests
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
//#define BOOST_DISABLE_WIN32 //disables SEH warning
|
||||
#define BOOST_TEST_NO_MAIN
|
||||
#include <boost/test/included/unit_test.hpp>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <test/TestHelper.h>
|
||||
using namespace boost::unit_test;
|
||||
|
||||
//Custom Boost Initialization
|
||||
test_suite* init_func( int argc, char* argv[] )
|
||||
{
|
||||
if (argc == 0)
|
||||
argv[1]=(char*)"a";
|
||||
|
||||
dev::test::Options::get();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//Custom Boost Unit Test Main
|
||||
int main( int argc, char* argv[] )
|
||||
{
|
||||
try
|
||||
{
|
||||
framework::init( init_func, argc, argv );
|
||||
|
||||
if( !runtime_config::test_to_run().is_empty() )
|
||||
{
|
||||
test_case_filter filter( runtime_config::test_to_run() );
|
||||
|
||||
traverse_test_tree( framework::master_test_suite().p_id, filter );
|
||||
}
|
||||
|
||||
framework::run();
|
||||
|
||||
results_reporter::make_report();
|
||||
|
||||
return runtime_config::no_result_code()
|
||||
? boost::exit_success
|
||||
: results_collector.results( framework::master_test_suite().p_id ).result_code();
|
||||
}
|
||||
catch (framework::nothing_to_test const&)
|
||||
{
|
||||
return boost::exit_success;
|
||||
}
|
||||
catch (framework::internal_error const& ex)
|
||||
{
|
||||
results_reporter::get_stream() << "Boost.Test framework internal error: " << ex.what() << std::endl;
|
||||
|
||||
return boost::exit_exception_failure;
|
||||
}
|
||||
catch (framework::setup_error const& ex)
|
||||
{
|
||||
results_reporter::get_stream() << "Test setup error: " << ex.what() << std::endl;
|
||||
|
||||
return boost::exit_exception_failure;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
results_reporter::get_stream() << "Boost.Test framework internal error: unknown reason" << std::endl;
|
||||
|
||||
return boost::exit_exception_failure;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
497
test/contracts/AuctionRegistrar.cpp
Normal file
497
test/contracts/AuctionRegistrar.cpp
Normal file
@ -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
|
5
test/contracts/CMakeLists.txt
Normal file
5
test/contracts/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
cmake_policy(SET CMP0015 NEW)
|
||||
|
||||
aux_source_directory(. SRCS)
|
||||
|
||||
add_sources(${SRCS})
|
242
test/contracts/FixedFeeRegistrar.cpp
Normal file
242
test/contracts/FixedFeeRegistrar.cpp
Normal file
@ -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
|
668
test/contracts/Wallet.cpp
Normal file
668
test/contracts/Wallet.cpp
Normal file
@ -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
|
120
test/libsolidity/Assembly.cpp
Normal file
120
test/libsolidity/Assembly.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
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 Lefteris Karapetsas <lefteris@ethdev.com>
|
||||
* @date 2015
|
||||
* Unit tests for Assembly Items from evmasm/Assembly.h
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <libdevcore/Log.h>
|
||||
#include <libevmasm/SourceLocation.h>
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include <libsolidity/Scanner.h>
|
||||
#include <libsolidity/Parser.h>
|
||||
#include <libsolidity/NameAndTypeResolver.h>
|
||||
#include <libsolidity/Compiler.h>
|
||||
#include <libsolidity/AST.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev::eth;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
eth::AssemblyItems compileContract(const string& _sourceCode)
|
||||
{
|
||||
Parser parser;
|
||||
ASTPointer<SourceUnit> sourceUnit;
|
||||
BOOST_REQUIRE_NO_THROW(sourceUnit = parser.parse(make_shared<Scanner>(CharStream(_sourceCode))));
|
||||
NameAndTypeResolver resolver({});
|
||||
resolver.registerDeclarations(*sourceUnit);
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract));
|
||||
}
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract));
|
||||
}
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
Compiler compiler;
|
||||
compiler.compileContract(*contract, map<ContractDefinition const*, bytes const*>{});
|
||||
|
||||
return compiler.getRuntimeAssemblyItems();
|
||||
}
|
||||
BOOST_FAIL("No contract found in source.");
|
||||
return AssemblyItems();
|
||||
}
|
||||
|
||||
void checkAssemblyLocations(AssemblyItems const& _items, vector<SourceLocation> const& _locations)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(_items.size(), _locations.size());
|
||||
for (size_t i = 0; i < min(_items.size(), _locations.size()); ++i)
|
||||
{
|
||||
BOOST_CHECK_MESSAGE(
|
||||
_items[i].getLocation() == _locations[i],
|
||||
"Location mismatch for assembly item " + to_string(i) + ". Found: " +
|
||||
to_string(_items[i].getLocation().start) + "-" +
|
||||
to_string(_items[i].getLocation().end) + ", expected: " +
|
||||
to_string(_locations[i].start) + "-" +
|
||||
to_string(_locations[i].end));
|
||||
}
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(Assembly)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(location_test)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function f() returns (uint256 a) {
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
)";
|
||||
shared_ptr<string const> n = make_shared<string>("source");
|
||||
AssemblyItems items = compileContract(sourceCode);
|
||||
vector<SourceLocation> locations =
|
||||
vector<SourceLocation>(17, SourceLocation(2, 75, n)) +
|
||||
vector<SourceLocation>(26, SourceLocation(20, 72, n)) +
|
||||
vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} +
|
||||
vector<SourceLocation>(4, SourceLocation(58, 67, n)) +
|
||||
vector<SourceLocation>(3, SourceLocation(20, 72, n));
|
||||
checkAssemblyLocations(items, locations);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
} // end namespaces
|
5
test/libsolidity/CMakeLists.txt
Normal file
5
test/libsolidity/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
cmake_policy(SET CMP0015 NEW)
|
||||
|
||||
aux_source_directory(. SRCS)
|
||||
|
||||
add_sources(${SRCS})
|
235
test/libsolidity/GasMeter.cpp
Normal file
235
test/libsolidity/GasMeter.cpp
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
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
|
||||
* Unit tests for the gas estimator.
|
||||
*/
|
||||
|
||||
#include <test/libsolidity/solidityExecutionFramework.h>
|
||||
#include <libevmasm/GasMeter.h>
|
||||
#include <libevmasm/KnownState.h>
|
||||
#include <libevmasm/PathGasMeter.h>
|
||||
#include <libsolidity/AST.h>
|
||||
#include <libsolidity/GasEstimator.h>
|
||||
#include <libsolidity/SourceReferenceFormatter.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev::eth;
|
||||
using namespace dev::solidity;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
class GasMeterTestFramework: public ExecutionFramework
|
||||
{
|
||||
public:
|
||||
GasMeterTestFramework() { }
|
||||
void compile(string const& _sourceCode)
|
||||
{
|
||||
m_compiler.setSource(_sourceCode);
|
||||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed");
|
||||
|
||||
AssemblyItems const* items = m_compiler.getRuntimeAssemblyItems("");
|
||||
ASTNode const& sourceUnit = m_compiler.getAST();
|
||||
BOOST_REQUIRE(items != nullptr);
|
||||
m_gasCosts = GasEstimator::breakToStatementLevel(
|
||||
GasEstimator::structuralEstimation(*items, vector<ASTNode const*>({&sourceUnit})),
|
||||
{&sourceUnit}
|
||||
);
|
||||
}
|
||||
|
||||
void testCreationTimeGas(string const& _sourceCode)
|
||||
{
|
||||
compileAndRun(_sourceCode);
|
||||
auto state = make_shared<KnownState>();
|
||||
PathGasMeter meter(*m_compiler.getAssemblyItems());
|
||||
GasMeter::GasConsumption gas = meter.estimateMax(0, state);
|
||||
u256 bytecodeSize(m_compiler.getRuntimeBytecode().size());
|
||||
gas += bytecodeSize * c_createDataGas;
|
||||
BOOST_REQUIRE(!gas.isInfinite);
|
||||
BOOST_CHECK(gas.value == m_gasUsed);
|
||||
}
|
||||
|
||||
/// Compares the gas computed by PathGasMeter for the given signature (but unknown arguments)
|
||||
/// against the actual gas usage computed by the VM on the given set of argument variants.
|
||||
void testRunTimeGas(string const& _sig, vector<bytes> _argumentVariants)
|
||||
{
|
||||
u256 gasUsed = 0;
|
||||
FixedHash<4> hash(dev::sha3(_sig));
|
||||
for (bytes const& arguments: _argumentVariants)
|
||||
{
|
||||
sendMessage(hash.asBytes() + arguments, false, 0);
|
||||
gasUsed = max(gasUsed, m_gasUsed);
|
||||
}
|
||||
|
||||
GasMeter::GasConsumption gas = GasEstimator::functionalEstimation(
|
||||
*m_compiler.getRuntimeAssemblyItems(),
|
||||
_sig
|
||||
);
|
||||
BOOST_REQUIRE(!gas.isInfinite);
|
||||
BOOST_CHECK(gas.value == m_gasUsed);
|
||||
}
|
||||
|
||||
protected:
|
||||
map<ASTNode const*, eth::GasMeter::GasConsumption> m_gasCosts;
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(GasMeterTests, GasMeterTestFramework)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
bytes x;
|
||||
function f(uint a) returns (uint b) {
|
||||
x.length = a;
|
||||
for (; a < 200; ++a) {
|
||||
x[a] = 9;
|
||||
b = a * a;
|
||||
}
|
||||
return f(a - 1);
|
||||
}
|
||||
}
|
||||
)";
|
||||
compile(sourceCode);
|
||||
for (auto first = m_gasCosts.cbegin(); first != m_gasCosts.cend(); ++first)
|
||||
{
|
||||
auto second = first;
|
||||
for (++second; second != m_gasCosts.cend(); ++second)
|
||||
if (first->first->getLocation().intersects(second->first->getLocation()))
|
||||
{
|
||||
BOOST_CHECK_MESSAGE(false, "Source locations should not overlap!");
|
||||
SourceReferenceFormatter::printSourceLocation(cout, first->first->getLocation(), m_compiler.getScanner());
|
||||
SourceReferenceFormatter::printSourceLocation(cout, second->first->getLocation(), m_compiler.getScanner());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(simple_contract)
|
||||
{
|
||||
// Tests a simple "deploy contract" code without constructor. The actual contract is not relevant.
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
bytes32 public shaValue;
|
||||
function f(uint a) {
|
||||
shaValue = sha3(a);
|
||||
}
|
||||
}
|
||||
)";
|
||||
testCreationTimeGas(sourceCode);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(store_sha3)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
bytes32 public shaValue;
|
||||
function test(uint a) {
|
||||
shaValue = sha3(a);
|
||||
}
|
||||
}
|
||||
)";
|
||||
testCreationTimeGas(sourceCode);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(updating_store)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
uint data;
|
||||
uint data2;
|
||||
function test() {
|
||||
data = 1;
|
||||
data = 2;
|
||||
data2 = 0;
|
||||
}
|
||||
}
|
||||
)";
|
||||
testCreationTimeGas(sourceCode);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(branches)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
uint data;
|
||||
uint data2;
|
||||
function f(uint x) {
|
||||
if (x > 7)
|
||||
data2 = 1;
|
||||
else
|
||||
data = 1;
|
||||
}
|
||||
}
|
||||
)";
|
||||
testCreationTimeGas(sourceCode);
|
||||
testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_calls)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
uint data;
|
||||
uint data2;
|
||||
function f(uint x) {
|
||||
if (x > 7)
|
||||
data2 = g(x**8) + 1;
|
||||
else
|
||||
data = 1;
|
||||
}
|
||||
function g(uint x) internal returns (uint) {
|
||||
return data2;
|
||||
}
|
||||
}
|
||||
)";
|
||||
testCreationTimeGas(sourceCode);
|
||||
testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_external_functions)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
uint data;
|
||||
uint data2;
|
||||
function f(uint x) {
|
||||
if (x > 7)
|
||||
data2 = g(x**8) + 1;
|
||||
else
|
||||
data = 1;
|
||||
}
|
||||
function g(uint x) returns (uint) {
|
||||
return data2;
|
||||
}
|
||||
}
|
||||
)";
|
||||
testCreationTimeGas(sourceCode);
|
||||
testRunTimeGas("f(uint256)", vector<bytes>{encodeArgs(2), encodeArgs(8)});
|
||||
testRunTimeGas("g(uint256)", vector<bytes>{encodeArgs(2)});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
602
test/libsolidity/SolidityABIJSON.cpp
Normal file
602
test/libsolidity/SolidityABIJSON.cpp
Normal file
@ -0,0 +1,602 @@
|
||||
/*
|
||||
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 Marek Kotewicz <marek@ethdev.com>
|
||||
* @date 2014
|
||||
* Unit tests for the solidity compiler JSON Interface output.
|
||||
*/
|
||||
|
||||
#include "../TestHelper.h"
|
||||
#include <libsolidity/CompilerStack.h>
|
||||
#include <json/json.h>
|
||||
#include <libdevcore/Exceptions.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
class JSONInterfaceChecker
|
||||
{
|
||||
public:
|
||||
JSONInterfaceChecker(): m_compilerStack(false) {}
|
||||
|
||||
void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString)
|
||||
{
|
||||
ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing contract failed");
|
||||
std::string generatedInterfaceString = m_compilerStack.getMetadata("", DocumentationType::ABIInterface);
|
||||
Json::Value generatedInterface;
|
||||
m_reader.parse(generatedInterfaceString, generatedInterface);
|
||||
Json::Value expectedInterface;
|
||||
m_reader.parse(_expectedInterfaceString, expectedInterface);
|
||||
BOOST_CHECK_MESSAGE(expectedInterface == generatedInterface,
|
||||
"Expected:\n" << expectedInterface.toStyledString() <<
|
||||
"\n but got:\n" << generatedInterface.toStyledString());
|
||||
}
|
||||
|
||||
private:
|
||||
CompilerStack m_compilerStack;
|
||||
Json::Reader m_reader;
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(SolidityABIJSON, JSONInterfaceChecker)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(basic_test)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a) returns(uint d) { return a * 7; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "f",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "d",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
}
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_contract)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
"}\n";
|
||||
char const* interface = "[]";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_methods)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a) returns(uint d) { return a * 7; }\n"
|
||||
" function g(uint b) returns(uint e) { return b * 8; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "f",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "d",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "g",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "b",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "e",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
}
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_params)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a, uint b) returns(uint d) { return a + b; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "f",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "d",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
}
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_methods_order)
|
||||
{
|
||||
// methods are expected to be in alpabetical order
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a) returns(uint d) { return a * 7; }\n"
|
||||
" function c(uint b) returns(uint e) { return b * 8; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "c",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "b",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "e",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "f",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "d",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
}
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(const_function)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function foo(uint a, uint b) returns(uint d) { return a + b; }\n"
|
||||
" function boo(uint32 a) constant returns(uint b) { return a * 4; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "foo",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "b",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "d",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "boo",
|
||||
"constant": true,
|
||||
"type": "function",
|
||||
"inputs": [{
|
||||
"name": "a",
|
||||
"type": "uint32"
|
||||
}],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "b",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
}
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(exclude_fallback_function)
|
||||
{
|
||||
char const* sourceCode = "contract test { function() {} }";
|
||||
|
||||
char const* interface = "[]";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(events)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a) returns(uint d) { return a * 7; }\n"
|
||||
" event e1(uint b, address indexed c); \n"
|
||||
" event e2(); \n"
|
||||
"}\n";
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "f",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "d",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "e1",
|
||||
"type": "event",
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "b",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "c",
|
||||
"type": "address"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "e2",
|
||||
"type": "event",
|
||||
"anonymous": false,
|
||||
"inputs": []
|
||||
}
|
||||
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(events_anonymous)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" event e() anonymous; \n"
|
||||
"}\n";
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "e",
|
||||
"type": "event",
|
||||
"anonymous": true,
|
||||
"inputs": []
|
||||
}
|
||||
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(inherited)
|
||||
{
|
||||
char const* sourceCode =
|
||||
" contract Base { \n"
|
||||
" function baseFunction(uint p) returns (uint i) { return p; } \n"
|
||||
" event baseEvent(bytes32 indexed evtArgBase); \n"
|
||||
" } \n"
|
||||
" contract Derived is Base { \n"
|
||||
" function derivedFunction(bytes32 p) returns (bytes32 i) { return p; } \n"
|
||||
" event derivedEvent(uint indexed evtArgDerived); \n"
|
||||
" }";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "baseFunction",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs":
|
||||
[{
|
||||
"name": "p",
|
||||
"type": "uint256"
|
||||
}],
|
||||
"outputs":
|
||||
[{
|
||||
"name": "i",
|
||||
"type": "uint256"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "derivedFunction",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs":
|
||||
[{
|
||||
"name": "p",
|
||||
"type": "bytes32"
|
||||
}],
|
||||
"outputs":
|
||||
[{
|
||||
"name": "i",
|
||||
"type": "bytes32"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "derivedEvent",
|
||||
"type": "event",
|
||||
"anonymous": false,
|
||||
"inputs":
|
||||
[{
|
||||
"indexed": true,
|
||||
"name": "evtArgDerived",
|
||||
"type": "uint256"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"name": "baseEvent",
|
||||
"type": "event",
|
||||
"anonymous": false,
|
||||
"inputs":
|
||||
[{
|
||||
"indexed": true,
|
||||
"name": "evtArgBase",
|
||||
"type": "bytes32"
|
||||
}]
|
||||
}])";
|
||||
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE(empty_name_input_parameter_with_named_one)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function f(uint, uint k) returns(uint ret_k, uint ret_g){
|
||||
uint g = 8;
|
||||
ret_k = k;
|
||||
ret_g = g;
|
||||
}
|
||||
})";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "f",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "k",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "ret_k",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "ret_g",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
}
|
||||
])";
|
||||
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_name_return_parameter)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function f(uint k) returns(uint){
|
||||
return k;
|
||||
}
|
||||
})";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"name": "f",
|
||||
"constant": false,
|
||||
"type": "function",
|
||||
"inputs": [
|
||||
{
|
||||
"name": "k",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
]
|
||||
}
|
||||
])";
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(constructor_abi)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function test(uint param1, test param2, bool param3) {}
|
||||
}
|
||||
)";
|
||||
|
||||
char const* interface = R"([
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "param1",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "param2",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "param3",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "constructor"
|
||||
}
|
||||
])";
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(return_param_in_abi)
|
||||
{
|
||||
// bug #1801
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
|
||||
function test(ActionChoices param) {}
|
||||
function ret() returns(ActionChoices){
|
||||
ActionChoices action = ActionChoices.GoLeft;
|
||||
return action;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const* interface = R"(
|
||||
[
|
||||
{
|
||||
"constant" : false,
|
||||
"inputs" : [],
|
||||
"name" : "ret",
|
||||
"outputs" : [
|
||||
{
|
||||
"name" : "",
|
||||
"type" : "uint8"
|
||||
}
|
||||
],
|
||||
"type" : "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "param",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"type": "constructor"
|
||||
}
|
||||
]
|
||||
)";
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(strings_and_arrays)
|
||||
{
|
||||
// bug #1801
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function f(string a, bytes b, uint[] c) external {}
|
||||
}
|
||||
)";
|
||||
|
||||
char const* interface = R"(
|
||||
[
|
||||
{
|
||||
"constant" : false,
|
||||
"name": "f",
|
||||
"inputs": [
|
||||
{ "name": "a", "type": "string" },
|
||||
{ "name": "b", "type": "bytes" },
|
||||
{ "name": "c", "type": "uint256[]" }
|
||||
],
|
||||
"outputs": [],
|
||||
"type" : "function"
|
||||
}
|
||||
]
|
||||
)";
|
||||
checkInterface(sourceCode, interface);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
5168
test/libsolidity/SolidityEndToEndTest.cpp
Normal file
5168
test/libsolidity/SolidityEndToEndTest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
491
test/libsolidity/SolidityExpressionCompiler.cpp
Normal file
491
test/libsolidity/SolidityExpressionCompiler.cpp
Normal file
@ -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 2014
|
||||
* Unit tests for the solidity expression compiler.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <libdevcore/Log.h>
|
||||
#include <libsolidity/Scanner.h>
|
||||
#include <libsolidity/Parser.h>
|
||||
#include <libsolidity/NameAndTypeResolver.h>
|
||||
#include <libsolidity/CompilerContext.h>
|
||||
#include <libsolidity/ExpressionCompiler.h>
|
||||
#include <libsolidity/AST.h>
|
||||
#include "../TestHelper.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// Helper class that extracts the first expression in an AST.
|
||||
class FirstExpressionExtractor: private ASTVisitor
|
||||
{
|
||||
public:
|
||||
FirstExpressionExtractor(ASTNode& _node): m_expression(nullptr) { _node.accept(*this); }
|
||||
Expression* getExpression() const { return m_expression; }
|
||||
private:
|
||||
virtual bool visit(Assignment& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(UnaryOperation& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(BinaryOperation& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(FunctionCall& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(MemberAccess& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(IndexAccess& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(Identifier& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(ElementaryTypeNameExpression& _expression) override { return checkExpression(_expression); }
|
||||
virtual bool visit(Literal& _expression) override { return checkExpression(_expression); }
|
||||
bool checkExpression(Expression& _expression)
|
||||
{
|
||||
if (m_expression == nullptr)
|
||||
m_expression = &_expression;
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
Expression* m_expression;
|
||||
};
|
||||
|
||||
Declaration const& resolveDeclaration(
|
||||
vector<string> const& _namespacedName, NameAndTypeResolver const& _resolver)
|
||||
{
|
||||
Declaration const* declaration = nullptr;
|
||||
// bracers are required, cause msvc couldnt handle this macro in for statement
|
||||
for (string const& namePart: _namespacedName)
|
||||
{
|
||||
auto declarations = _resolver.resolveName(namePart, declaration);
|
||||
BOOST_REQUIRE(!declarations.empty());
|
||||
BOOST_REQUIRE(declaration = *declarations.begin());
|
||||
}
|
||||
BOOST_REQUIRE(declaration);
|
||||
return *declaration;
|
||||
}
|
||||
|
||||
bytes compileFirstExpression(const string& _sourceCode, vector<vector<string>> _functions = {},
|
||||
vector<vector<string>> _localVariables = {},
|
||||
vector<shared_ptr<MagicVariableDeclaration const>> _globalDeclarations = {})
|
||||
{
|
||||
Parser parser;
|
||||
ASTPointer<SourceUnit> sourceUnit;
|
||||
try
|
||||
{
|
||||
sourceUnit = parser.parse(make_shared<Scanner>(CharStream(_sourceCode)));
|
||||
}
|
||||
catch(boost::exception const& _e)
|
||||
{
|
||||
auto msg = std::string("Parsing source code failed with: \n") + boost::diagnostic_information(_e);
|
||||
BOOST_FAIL(msg);
|
||||
}
|
||||
|
||||
vector<Declaration const*> declarations;
|
||||
declarations.reserve(_globalDeclarations.size() + 1);
|
||||
for (ASTPointer<Declaration const> const& variable: _globalDeclarations)
|
||||
declarations.push_back(variable.get());
|
||||
NameAndTypeResolver resolver(declarations);
|
||||
resolver.registerDeclarations(*sourceUnit);
|
||||
|
||||
vector<ContractDefinition const*> inheritanceHierarchy;
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
ETH_TEST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract), "Resolving names failed");
|
||||
inheritanceHierarchy = vector<ContractDefinition const*>(1, contract);
|
||||
}
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
ETH_TEST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract), "Checking type Requirements failed");
|
||||
}
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
FirstExpressionExtractor extractor(*contract);
|
||||
BOOST_REQUIRE(extractor.getExpression() != nullptr);
|
||||
|
||||
CompilerContext context;
|
||||
context.resetVisitedNodes(contract);
|
||||
context.setInheritanceHierarchy(inheritanceHierarchy);
|
||||
unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack
|
||||
context.adjustStackOffset(parametersSize);
|
||||
for (vector<string> const& variable: _localVariables)
|
||||
context.addVariable(dynamic_cast<VariableDeclaration const&>(resolveDeclaration(variable, resolver)),
|
||||
parametersSize--);
|
||||
|
||||
ExpressionCompiler(context).compile(*extractor.getExpression());
|
||||
|
||||
for (vector<string> const& function: _functions)
|
||||
context << context.getFunctionEntryLabel(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver)));
|
||||
bytes instructions = context.getAssembledBytecode();
|
||||
// debug
|
||||
// cout << eth::disassemble(instructions) << endl;
|
||||
return instructions;
|
||||
}
|
||||
BOOST_FAIL("No contract found in source.");
|
||||
return bytes();
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(literal_true)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { var x = true; }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(literal_false)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { var x = false; }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x0});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(int_literal)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { var x = 0x12345678901234567890; }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90,
|
||||
0x12, 0x34, 0x56, 0x78, 0x90});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(int_with_wei_ether_subdenomination)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function test ()
|
||||
{
|
||||
var x = 1 wei;
|
||||
}
|
||||
})";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(int_with_szabo_ether_subdenomination)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function test ()
|
||||
{
|
||||
var x = 1 szabo;
|
||||
}
|
||||
})";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH5), 0xe8, 0xd4, 0xa5, 0x10, 0x00});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(int_with_finney_ether_subdenomination)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function test ()
|
||||
{
|
||||
var x = 1 finney;
|
||||
}
|
||||
})";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH7), 0x3, 0x8d, 0x7e, 0xa4, 0xc6, 0x80, 0x00});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(int_with_ether_ether_subdenomination)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
function test ()
|
||||
{
|
||||
var x = 1 ether;
|
||||
}
|
||||
})";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH8), 0xd, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(comparison)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { var x = (0x10aa < 0x11aa) != true; }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1,
|
||||
byte(eth::Instruction::PUSH2), 0x11, 0xaa,
|
||||
byte(eth::Instruction::PUSH2), 0x10, 0xaa,
|
||||
byte(eth::Instruction::LT),
|
||||
byte(eth::Instruction::EQ),
|
||||
byte(eth::Instruction::ISZERO)});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(short_circuiting)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { var x = true != (4 <= 8 + 10 || 9 != 2); }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x12, // 8 + 10
|
||||
byte(eth::Instruction::PUSH1), 0x4,
|
||||
byte(eth::Instruction::GT),
|
||||
byte(eth::Instruction::ISZERO), // after this we have 4 <= 8 + 10
|
||||
byte(eth::Instruction::DUP1),
|
||||
byte(eth::Instruction::PUSH1), 0x11,
|
||||
byte(eth::Instruction::JUMPI), // short-circuit if it is true
|
||||
byte(eth::Instruction::POP),
|
||||
byte(eth::Instruction::PUSH1), 0x2,
|
||||
byte(eth::Instruction::PUSH1), 0x9,
|
||||
byte(eth::Instruction::EQ),
|
||||
byte(eth::Instruction::ISZERO), // after this we have 9 != 2
|
||||
byte(eth::Instruction::JUMPDEST),
|
||||
byte(eth::Instruction::PUSH1), 0x1,
|
||||
byte(eth::Instruction::EQ),
|
||||
byte(eth::Instruction::ISZERO)});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(arithmetics)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint y) { var x = ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}});
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x1,
|
||||
byte(eth::Instruction::PUSH1), 0x2,
|
||||
byte(eth::Instruction::PUSH1), 0x3,
|
||||
byte(eth::Instruction::PUSH1), 0x4,
|
||||
byte(eth::Instruction::PUSH1), 0x5,
|
||||
byte(eth::Instruction::PUSH1), 0x6,
|
||||
byte(eth::Instruction::PUSH1), 0x7,
|
||||
byte(eth::Instruction::PUSH1), 0x8,
|
||||
byte(eth::Instruction::DUP10),
|
||||
byte(eth::Instruction::XOR),
|
||||
byte(eth::Instruction::AND),
|
||||
byte(eth::Instruction::OR),
|
||||
byte(eth::Instruction::SUB),
|
||||
byte(eth::Instruction::ADD),
|
||||
byte(eth::Instruction::MOD),
|
||||
byte(eth::Instruction::DIV),
|
||||
byte(eth::Instruction::MUL)});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(unary_operators)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(int y) { var x = !(~+- y == 2); }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}});
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x2,
|
||||
byte(eth::Instruction::DUP3),
|
||||
byte(eth::Instruction::PUSH1), 0x0,
|
||||
byte(eth::Instruction::SUB),
|
||||
byte(eth::Instruction::NOT),
|
||||
byte(eth::Instruction::EQ),
|
||||
byte(eth::Instruction::ISZERO)});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(unary_inc_dec)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a) { var x = --a ^ (a-- ^ (++a ^ a++)); }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}});
|
||||
|
||||
// Stack: a, x
|
||||
bytes expectation({byte(eth::Instruction::DUP2),
|
||||
byte(eth::Instruction::DUP1),
|
||||
byte(eth::Instruction::PUSH1), 0x1,
|
||||
byte(eth::Instruction::ADD),
|
||||
// Stack here: a x a (a+1)
|
||||
byte(eth::Instruction::SWAP3),
|
||||
byte(eth::Instruction::POP), // first ++
|
||||
// Stack here: (a+1) x a
|
||||
byte(eth::Instruction::DUP3),
|
||||
byte(eth::Instruction::PUSH1), 0x1,
|
||||
byte(eth::Instruction::ADD),
|
||||
// Stack here: (a+1) x a (a+2)
|
||||
byte(eth::Instruction::SWAP3),
|
||||
byte(eth::Instruction::POP),
|
||||
// Stack here: (a+2) x a
|
||||
byte(eth::Instruction::DUP3), // second ++
|
||||
byte(eth::Instruction::XOR),
|
||||
// Stack here: (a+2) x a^(a+2)
|
||||
byte(eth::Instruction::DUP3),
|
||||
byte(eth::Instruction::DUP1),
|
||||
byte(eth::Instruction::PUSH1), 0x1,
|
||||
byte(eth::Instruction::SWAP1),
|
||||
byte(eth::Instruction::SUB),
|
||||
// Stack here: (a+2) x a^(a+2) (a+2) (a+1)
|
||||
byte(eth::Instruction::SWAP4),
|
||||
byte(eth::Instruction::POP), // first --
|
||||
byte(eth::Instruction::XOR),
|
||||
// Stack here: (a+1) x a^(a+2)^(a+2)
|
||||
byte(eth::Instruction::DUP3),
|
||||
byte(eth::Instruction::PUSH1), 0x1,
|
||||
byte(eth::Instruction::SWAP1),
|
||||
byte(eth::Instruction::SUB),
|
||||
// Stack here: (a+1) x a^(a+2)^(a+2) a
|
||||
byte(eth::Instruction::SWAP3),
|
||||
byte(eth::Instruction::POP), // second ++
|
||||
// Stack here: a x a^(a+2)^(a+2)
|
||||
byte(eth::Instruction::DUP3), // will change
|
||||
byte(eth::Instruction::XOR)});
|
||||
// Stack here: a x a^(a+2)^(a+2)^a
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(assignment)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a, uint b) { (a += b) * 2; }"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}});
|
||||
|
||||
// Stack: a, b
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x2,
|
||||
byte(eth::Instruction::DUP2),
|
||||
byte(eth::Instruction::DUP4),
|
||||
byte(eth::Instruction::ADD),
|
||||
// Stack here: a b 2 a+b
|
||||
byte(eth::Instruction::SWAP3),
|
||||
byte(eth::Instruction::POP),
|
||||
byte(eth::Instruction::DUP3),
|
||||
// Stack here: a+b b 2 a+b
|
||||
byte(eth::Instruction::MUL)});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_call)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n"
|
||||
" function g(uint a, uint b) returns (uint c) {}\n"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode, {{"test", "g"}},
|
||||
{{"test", "f", "a"}, {"test", "f", "b"}});
|
||||
|
||||
// Stack: a, b
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x02,
|
||||
byte(eth::Instruction::PUSH1), 0x0c,
|
||||
byte(eth::Instruction::PUSH1), 0x01,
|
||||
byte(eth::Instruction::DUP5),
|
||||
byte(eth::Instruction::ADD),
|
||||
// Stack here: a b 2 <ret label> (a+1)
|
||||
byte(eth::Instruction::DUP4),
|
||||
byte(eth::Instruction::PUSH1), 0x13,
|
||||
byte(eth::Instruction::JUMP),
|
||||
byte(eth::Instruction::JUMPDEST),
|
||||
// Stack here: a b 2 g(a+1, b)
|
||||
byte(eth::Instruction::MUL),
|
||||
// Stack here: a b g(a+1, b)*2
|
||||
byte(eth::Instruction::DUP3),
|
||||
byte(eth::Instruction::ADD),
|
||||
// Stack here: a b a+g(a+1, b)*2
|
||||
byte(eth::Instruction::SWAP2),
|
||||
byte(eth::Instruction::POP),
|
||||
byte(eth::Instruction::DUP2),
|
||||
byte(eth::Instruction::JUMPDEST)});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(negative_literals_8bits)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { int8 x = -0x80; }\n"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(31, 0xff) + bytes(1, 0x80));
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(negative_literals_16bits)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { int64 x = ~0xabc; }\n"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(30, 0xff) + bytes{0xf5, 0x43});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(intermediately_overflowing_literals)
|
||||
{
|
||||
// first literal itself is too large for 256 bits but it fits after all constant operations
|
||||
// have been applied
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() { var x = (0xffffffffffffffffffffffffffffffffffffffff * 0xffffffffffffffffffffffffff01) & 0xbf; }\n"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode);
|
||||
|
||||
bytes expectation(bytes({byte(eth::Instruction::PUSH1), 0xbf}));
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(blockhash)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f() {\n"
|
||||
" block.blockhash(3);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
bytes code = compileFirstExpression(sourceCode, {}, {},
|
||||
{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block))});
|
||||
|
||||
bytes expectation({byte(eth::Instruction::PUSH1), 0x03,
|
||||
byte(eth::Instruction::BLOCKHASH)});
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
} // end namespaces
|
149
test/libsolidity/SolidityInterface.cpp
Normal file
149
test/libsolidity/SolidityInterface.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
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
|
||||
* Unit tests for generating source interfaces for Solidity contracts.
|
||||
*/
|
||||
|
||||
#include "../TestHelper.h"
|
||||
#include <libsolidity/CompilerStack.h>
|
||||
#include <libsolidity/AST.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
class SolidityInterfaceChecker
|
||||
{
|
||||
public:
|
||||
SolidityInterfaceChecker(): m_compilerStack(false) {}
|
||||
|
||||
/// Compiles the given code, generates the interface and parses that again.
|
||||
ContractDefinition const& checkInterface(string const& _code, string const& _contractName = "")
|
||||
{
|
||||
m_code = _code;
|
||||
ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing failed");
|
||||
m_interface = m_compilerStack.getMetadata("", DocumentationType::ABISolidityInterface);
|
||||
ETH_TEST_REQUIRE_NO_THROW(m_reCompiler.parse(m_interface), "Interface parsing failed");
|
||||
return m_reCompiler.getContractDefinition(_contractName);
|
||||
}
|
||||
|
||||
string getSourcePart(ASTNode const& _node) const
|
||||
{
|
||||
SourceLocation location = _node.getLocation();
|
||||
BOOST_REQUIRE(!location.isEmpty());
|
||||
return m_interface.substr(location.start, location.end - location.start);
|
||||
}
|
||||
|
||||
protected:
|
||||
string m_code;
|
||||
string m_interface;
|
||||
CompilerStack m_compilerStack;
|
||||
CompilerStack m_reCompiler;
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(SolidityInterface, SolidityInterfaceChecker)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_contract)
|
||||
{
|
||||
ContractDefinition const& contract = checkInterface("contract test {}");
|
||||
BOOST_CHECK_EQUAL(getSourcePart(contract), "contract test{}");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(single_function)
|
||||
{
|
||||
ContractDefinition const& contract = checkInterface(
|
||||
"contract test {\n"
|
||||
" function f(uint a) returns(uint d) { return a * 7; }\n"
|
||||
"}\n");
|
||||
BOOST_REQUIRE_EQUAL(1, contract.getDefinedFunctions().size());
|
||||
BOOST_CHECK_EQUAL(getSourcePart(*contract.getDefinedFunctions().front()),
|
||||
"function f(uint256 a)returns(uint256 d);");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(single_constant_function)
|
||||
{
|
||||
ContractDefinition const& contract = checkInterface(
|
||||
"contract test { function f(uint a) constant returns(bytes1 x) { 1==2; } }");
|
||||
BOOST_REQUIRE_EQUAL(1, contract.getDefinedFunctions().size());
|
||||
BOOST_CHECK_EQUAL(getSourcePart(*contract.getDefinedFunctions().front()),
|
||||
"function f(uint256 a)constant returns(bytes1 x);");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_functions)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a) returns(uint d) { return a * 7; }\n"
|
||||
" function g(uint b) returns(uint e) { return b * 8; }\n"
|
||||
"}\n";
|
||||
ContractDefinition const& contract = checkInterface(sourceCode);
|
||||
set<string> expectation({"function f(uint256 a)returns(uint256 d);",
|
||||
"function g(uint256 b)returns(uint256 e);"});
|
||||
BOOST_REQUIRE_EQUAL(2, contract.getDefinedFunctions().size());
|
||||
BOOST_CHECK(expectation == set<string>({getSourcePart(*contract.getDefinedFunctions().at(0)),
|
||||
getSourcePart(*contract.getDefinedFunctions().at(1))}));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(exclude_fallback_function)
|
||||
{
|
||||
char const* sourceCode = "contract test { function() {} }";
|
||||
ContractDefinition const& contract = checkInterface(sourceCode);
|
||||
BOOST_CHECK_EQUAL(getSourcePart(contract), "contract test{}");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(events)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function f(uint a) returns(uint d) { return a * 7; }\n"
|
||||
" event e1(uint b, address indexed c); \n"
|
||||
" event e2(); \n"
|
||||
"}\n";
|
||||
ContractDefinition const& contract = checkInterface(sourceCode);
|
||||
// events should not appear in the Solidity Interface
|
||||
BOOST_REQUIRE_EQUAL(0, contract.getEvents().size());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(inheritance)
|
||||
{
|
||||
char const* sourceCode =
|
||||
" contract Base { \n"
|
||||
" function baseFunction(uint p) returns (uint i) { return p; } \n"
|
||||
" event baseEvent(bytes32 indexed evtArgBase); \n"
|
||||
" } \n"
|
||||
" contract Derived is Base { \n"
|
||||
" function derivedFunction(bytes32 p) returns (bytes32 i) { return p; } \n"
|
||||
" event derivedEvent(uint indexed evtArgDerived); \n"
|
||||
" }";
|
||||
ContractDefinition const& contract = checkInterface(sourceCode);
|
||||
set<string> expectedFunctions({"function baseFunction(uint256 p)returns(uint256 i);",
|
||||
"function derivedFunction(bytes32 p)returns(bytes32 i);"});
|
||||
BOOST_REQUIRE_EQUAL(2, contract.getDefinedFunctions().size());
|
||||
BOOST_CHECK(expectedFunctions == set<string>({getSourcePart(*contract.getDefinedFunctions().at(0)),
|
||||
getSourcePart(*contract.getDefinedFunctions().at(1))}));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
2201
test/libsolidity/SolidityNameAndTypeResolution.cpp
Normal file
2201
test/libsolidity/SolidityNameAndTypeResolution.cpp
Normal file
File diff suppressed because it is too large
Load Diff
534
test/libsolidity/SolidityNatspecJSON.cpp
Normal file
534
test/libsolidity/SolidityNatspecJSON.cpp
Normal file
@ -0,0 +1,534 @@
|
||||
/*
|
||||
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 Lefteris Karapetsas <lefteris@ethdev.com>
|
||||
* @date 2014
|
||||
* Unit tests for the solidity compiler JSON Interface output.
|
||||
*/
|
||||
|
||||
#include "../TestHelper.h"
|
||||
#include <string>
|
||||
#include <json/json.h>
|
||||
#include <libsolidity/CompilerStack.h>
|
||||
#include <libsolidity/Exceptions.h>
|
||||
#include <libdevcore/Exceptions.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
class DocumentationChecker
|
||||
{
|
||||
public:
|
||||
DocumentationChecker(): m_compilerStack(false) {}
|
||||
|
||||
void checkNatspec(
|
||||
std::string const& _code,
|
||||
std::string const& _expectedDocumentationString,
|
||||
bool _userDocumentation
|
||||
)
|
||||
{
|
||||
std::string generatedDocumentationString;
|
||||
ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing failed");
|
||||
|
||||
if (_userDocumentation)
|
||||
generatedDocumentationString = m_compilerStack.getMetadata("", DocumentationType::NatspecUser);
|
||||
else
|
||||
generatedDocumentationString = m_compilerStack.getMetadata("", DocumentationType::NatspecDev);
|
||||
Json::Value generatedDocumentation;
|
||||
m_reader.parse(generatedDocumentationString, generatedDocumentation);
|
||||
Json::Value expectedDocumentation;
|
||||
m_reader.parse(_expectedDocumentationString, expectedDocumentation);
|
||||
BOOST_CHECK_MESSAGE(
|
||||
expectedDocumentation == generatedDocumentation,
|
||||
"Expected " << _expectedDocumentationString <<
|
||||
"\n but got:\n" << generatedDocumentationString
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
CompilerStack m_compilerStack;
|
||||
Json::Reader m_reader;
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(SolidityNatspecJSON, DocumentationChecker)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_basic_test)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @notice Multiplies `a` by 7\n"
|
||||
" function mul(uint a) returns(uint d) { return a * 7; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256)\":{ \"notice\": \"Multiplies `a` by 7\"}"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_and_user_basic_test)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @notice Multiplies `a` by 7\n"
|
||||
" /// @dev Multiplies a number by 7\n"
|
||||
" function mul(uint a) returns(uint d) { return a * 7; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* devNatspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
char const* userNatspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256)\":{ \"notice\": \"Multiplies `a` by 7\"}"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, devNatspec, false);
|
||||
checkNatspec(sourceCode, userNatspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_multiline_comment)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @notice Multiplies `a` by 7\n"
|
||||
" /// and then adds `b`\n"
|
||||
" function mul_and_add(uint a, uint256 b) returns(uint256 d)\n"
|
||||
" {\n"
|
||||
" return (a * 7) + b;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul_and_add(uint256,uint256)\":{ \"notice\": \"Multiplies `a` by 7 and then adds `b`\"}"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_multiple_functions)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @notice Multiplies `a` by 7 and then adds `b`\n"
|
||||
" function mul_and_add(uint a, uint256 b) returns(uint256 d)\n"
|
||||
" {\n"
|
||||
" return (a * 7) + b;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /// @notice Divides `input` by `div`\n"
|
||||
" function divide(uint input, uint div) returns(uint d)\n"
|
||||
" {\n"
|
||||
" return input / div;\n"
|
||||
" }\n"
|
||||
" /// @notice Subtracts 3 from `input`\n"
|
||||
" function sub(int input) returns(int d)\n"
|
||||
" {\n"
|
||||
" return input - 3;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul_and_add(uint256,uint256)\":{ \"notice\": \"Multiplies `a` by 7 and then adds `b`\"},"
|
||||
" \"divide(uint256,uint256)\":{ \"notice\": \"Divides `input` by `div`\"},"
|
||||
" \"sub(int256)\":{ \"notice\": \"Subtracts 3 from `input`\"}"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_empty_contract)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{\"methods\":{} }";
|
||||
|
||||
checkNatspec(sourceCode, natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_and_user_no_doc)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" function mul(uint a) returns(uint d) { return a * 7; }\n"
|
||||
" function sub(int input) returns(int d)\n"
|
||||
" {\n"
|
||||
" return input - 3;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
char const* devNatspec = "{\"methods\":{}}";
|
||||
char const* userNatspec = "{\"methods\":{}}";
|
||||
|
||||
checkNatspec(sourceCode, devNatspec, false);
|
||||
checkNatspec(sourceCode, userNatspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_desc_after_nl)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev\n"
|
||||
" /// Multiplies a number by 7 and adds second parameter\n"
|
||||
" /// @param a Documentation for the first parameter\n"
|
||||
" /// @param second Documentation for the second parameter\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \" Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_multiple_params)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev Multiplies a number by 7 and adds second parameter\n"
|
||||
" /// @param a Documentation for the first parameter\n"
|
||||
" /// @param second Documentation for the second parameter\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_mutiline_param_description)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev Multiplies a number by 7 and adds second parameter\n"
|
||||
" /// @param a Documentation for the first parameter starts here.\n"
|
||||
" /// Since it's a really complicated parameter we need 2 lines\n"
|
||||
" /// @param second Documentation for the second parameter\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_multiple_functions)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev Multiplies a number by 7 and adds second parameter\n"
|
||||
" /// @param a Documentation for the first parameter\n"
|
||||
" /// @param second Documentation for the second parameter\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
" \n"
|
||||
" /// @dev Divides 2 numbers\n"
|
||||
" /// @param input Documentation for the input parameter\n"
|
||||
" /// @param div Documentation for the div parameter\n"
|
||||
" function divide(uint input, uint div) returns(uint d)\n"
|
||||
" {\n"
|
||||
" return input / div;\n"
|
||||
" }\n"
|
||||
" /// @dev Subtracts 3 from `input`\n"
|
||||
" /// @param input Documentation for the input parameter\n"
|
||||
" function sub(int input) returns(int d)\n"
|
||||
" {\n"
|
||||
" return input - 3;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" }\n"
|
||||
" },\n"
|
||||
" \"divide(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Divides 2 numbers\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"input\": \"Documentation for the input parameter\",\n"
|
||||
" \"div\": \"Documentation for the div parameter\"\n"
|
||||
" }\n"
|
||||
" },\n"
|
||||
" \"sub(int256)\":{ \n"
|
||||
" \"details\": \"Subtracts 3 from `input`\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"input\": \"Documentation for the input parameter\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_return)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev Multiplies a number by 7 and adds second parameter\n"
|
||||
" /// @param a Documentation for the first parameter starts here.\n"
|
||||
" /// Since it's a really complicated parameter we need 2 lines\n"
|
||||
" /// @param second Documentation for the second parameter\n"
|
||||
" /// @return The result of the multiplication\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" },\n"
|
||||
" \"return\": \"The result of the multiplication\"\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE(dev_return_desc_after_nl)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev Multiplies a number by 7 and adds second parameter\n"
|
||||
" /// @param a Documentation for the first parameter starts here.\n"
|
||||
" /// Since it's a really complicated parameter we need 2 lines\n"
|
||||
" /// @param second Documentation for the second parameter\n"
|
||||
" /// @return\n"
|
||||
" /// The result of the multiplication\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" },\n"
|
||||
" \"return\": \" The result of the multiplication\"\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_multiline_return)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev Multiplies a number by 7 and adds second parameter\n"
|
||||
" /// @param a Documentation for the first parameter starts here.\n"
|
||||
" /// Since it's a really complicated parameter we need 2 lines\n"
|
||||
" /// @param second Documentation for the second parameter\n"
|
||||
" /// @return The result of the multiplication\n"
|
||||
" /// and cookies with nutella\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" },\n"
|
||||
" \"return\": \"The result of the multiplication and cookies with nutella\"\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_multiline_comment)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /**\n"
|
||||
" * @dev Multiplies a number by 7 and adds second parameter\n"
|
||||
" * @param a Documentation for the first parameter starts here.\n"
|
||||
" * Since it's a really complicated parameter we need 2 lines\n"
|
||||
" * @param second Documentation for the second parameter\n"
|
||||
" * @return The result of the multiplication\n"
|
||||
" * and cookies with nutella\n"
|
||||
" */"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
"\"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
|
||||
" \"params\": {\n"
|
||||
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n"
|
||||
" \"second\": \"Documentation for the second parameter\"\n"
|
||||
" },\n"
|
||||
" \"return\": \"The result of the multiplication and cookies with nutella\"\n"
|
||||
" }\n"
|
||||
"}}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_contract_no_doc)
|
||||
{
|
||||
char const* sourceCode = "contract test {\n"
|
||||
" /// @dev Mul function\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
" \"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Mul function\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_contract_doc)
|
||||
{
|
||||
char const* sourceCode = " /// @author Lefteris\n"
|
||||
" /// @title Just a test contract\n"
|
||||
"contract test {\n"
|
||||
" /// @dev Mul function\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
" \"author\": \"Lefteris\","
|
||||
" \"title\": \"Just a test contract\","
|
||||
" \"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Mul function\"\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_author_at_function)
|
||||
{
|
||||
char const* sourceCode = " /// @author Lefteris\n"
|
||||
" /// @title Just a test contract\n"
|
||||
"contract test {\n"
|
||||
" /// @dev Mul function\n"
|
||||
" /// @author John Doe\n"
|
||||
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
|
||||
"}\n";
|
||||
|
||||
char const* natspec = "{"
|
||||
" \"author\": \"Lefteris\","
|
||||
" \"title\": \"Just a test contract\","
|
||||
" \"methods\":{"
|
||||
" \"mul(uint256,uint256)\":{ \n"
|
||||
" \"details\": \"Mul function\",\n"
|
||||
" \"author\": \"John Doe\",\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}";
|
||||
|
||||
checkNatspec(sourceCode, natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(natspec_notice_without_tag)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
/// I do something awesome
|
||||
function mul(uint a) returns(uint d) { return a * 7; }
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
char const* natspec = R"ABCDEF(
|
||||
{
|
||||
"methods" : {
|
||||
"mul(uint256)" : {
|
||||
"notice" : "I do something awesome"
|
||||
}
|
||||
}
|
||||
}
|
||||
)ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(natspec_multiline_notice_without_tag)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract test {
|
||||
/// I do something awesome
|
||||
/// which requires two lines to explain
|
||||
function mul(uint a) returns(uint d) { return a * 7; }
|
||||
}
|
||||
)";
|
||||
|
||||
char const* natspec = R"ABCDEF(
|
||||
{
|
||||
"methods" : {
|
||||
"mul(uint256)" : {
|
||||
"notice" : "I do something awesome which requires two lines to explain"
|
||||
}
|
||||
}
|
||||
}
|
||||
)ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
1132
test/libsolidity/SolidityOptimizer.cpp
Normal file
1132
test/libsolidity/SolidityOptimizer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
921
test/libsolidity/SolidityParser.cpp
Normal file
921
test/libsolidity/SolidityParser.cpp
Normal file
@ -0,0 +1,921 @@
|
||||
/*
|
||||
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 2014
|
||||
* Unit tests for the solidity parser.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <libdevcore/Log.h>
|
||||
#include <libsolidity/Scanner.h>
|
||||
#include <libsolidity/Parser.h>
|
||||
#include <libsolidity/Exceptions.h>
|
||||
#include "../TestHelper.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
ASTPointer<ContractDefinition> parseText(std::string const& _source)
|
||||
{
|
||||
Parser parser;
|
||||
ASTPointer<SourceUnit> sourceUnit = parser.parse(std::make_shared<Scanner>(CharStream(_source)));
|
||||
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
|
||||
if (ASTPointer<ContractDefinition> contract = dynamic_pointer_cast<ContractDefinition>(node))
|
||||
return contract;
|
||||
BOOST_FAIL("No contract found in source.");
|
||||
return ASTPointer<ContractDefinition>();
|
||||
}
|
||||
|
||||
static void checkFunctionNatspec(ASTPointer<FunctionDefinition> _function,
|
||||
std::string const& _expectedDoc)
|
||||
{
|
||||
auto doc = _function->getDocumentation();
|
||||
BOOST_CHECK_MESSAGE(doc != nullptr, "Function does not have Natspec Doc as expected");
|
||||
BOOST_CHECK_EQUAL(*doc, _expectedDoc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(SolidityParser)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(smoke_test)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVariable1;\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(missing_variable_name_in_declaration)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 ;\n"
|
||||
"}\n";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_function)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" function functionName(bytes20 arg1, address addr) constant\n"
|
||||
" returns (int id)\n"
|
||||
" { }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(no_function_params)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" function functionName() {}\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(single_function_param)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" function functionName(bytes32 input) returns (bytes32 out) {}\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_no_body)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function functionName(bytes32 input) returns (bytes32 out);\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed.");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(missing_parameter_name_in_named_args)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; }\n"
|
||||
" function b() returns (uint r) { r = a({: 1, : 2, : 3}); }\n"
|
||||
"}\n";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(missing_argument_in_named_args)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; }\n"
|
||||
" function b() returns (uint r) { r = a({a: , b: , c: }); }\n"
|
||||
"}\n";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(two_exact_functions)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract test {
|
||||
function fun(uint a) returns(uint r) { return a; }
|
||||
function fun(uint a) returns(uint r) { return a; }
|
||||
}
|
||||
)";
|
||||
// with support of overloaded functions, during parsing,
|
||||
// we can't determine whether they match exactly, however
|
||||
// it will throw DeclarationError in following stage.
|
||||
BOOST_CHECK_NO_THROW(parseText(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(overloaded_functions)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract test {
|
||||
function fun(uint a) returns(uint r) { return a; }
|
||||
function fun(uint a, uint b) returns(uint r) { return a + b; }
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK_NO_THROW(parseText(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_natspec_documentation)
|
||||
{
|
||||
ASTPointer<ContractDefinition> contract;
|
||||
ASTPointer<FunctionDefinition> function;
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" /// This is a test function\n"
|
||||
" function functionName(bytes32 input) returns (bytes32 out) {}\n"
|
||||
"}\n";
|
||||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed");
|
||||
auto functions = contract->getDefinedFunctions();
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function");
|
||||
checkFunctionNatspec(function, "This is a test function");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(function_normal_comments)
|
||||
{
|
||||
ASTPointer<ContractDefinition> contract;
|
||||
ASTPointer<FunctionDefinition> function;
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" // We won't see this comment\n"
|
||||
" function functionName(bytes32 input) returns (bytes32 out) {}\n"
|
||||
"}\n";
|
||||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed");
|
||||
auto functions = contract->getDefinedFunctions();
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function");
|
||||
BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr,
|
||||
"Should not have gotten a Natspecc comment for this function");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation)
|
||||
{
|
||||
ASTPointer<ContractDefinition> contract;
|
||||
ASTPointer<FunctionDefinition> function;
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" /// This is test function 1\n"
|
||||
" function functionName1(bytes32 input) returns (bytes32 out) {}\n"
|
||||
" /// This is test function 2\n"
|
||||
" function functionName2(bytes32 input) returns (bytes32 out) {}\n"
|
||||
" // nothing to see here\n"
|
||||
" function functionName3(bytes32 input) returns (bytes32 out) {}\n"
|
||||
" /// This is test function 4\n"
|
||||
" function functionName4(bytes32 input) returns (bytes32 out) {}\n"
|
||||
"}\n";
|
||||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed");
|
||||
auto functions = contract->getDefinedFunctions();
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function");
|
||||
checkFunctionNatspec(function, "This is test function 1");
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(1), "Failed to retrieve function");
|
||||
checkFunctionNatspec(function, "This is test function 2");
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(2), "Failed to retrieve function");
|
||||
BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr,
|
||||
"Should not have gotten natspec comment for functionName3()");
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(3), "Failed to retrieve function");
|
||||
checkFunctionNatspec(function, "This is test function 4");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiline_function_documentation)
|
||||
{
|
||||
ASTPointer<ContractDefinition> contract;
|
||||
ASTPointer<FunctionDefinition> function;
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" /// This is a test function\n"
|
||||
" /// and it has 2 lines\n"
|
||||
" function functionName1(bytes32 input) returns (bytes32 out) {}\n"
|
||||
"}\n";
|
||||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed");
|
||||
auto functions = contract->getDefinedFunctions();
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function");
|
||||
checkFunctionNatspec(function, "This is a test function\n"
|
||||
" and it has 2 lines");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body)
|
||||
{
|
||||
ASTPointer<ContractDefinition> contract;
|
||||
ASTPointer<FunctionDefinition> function;
|
||||
char const* text = "contract test {\n"
|
||||
" /// fun1 description\n"
|
||||
" function fun1(uint256 a) {\n"
|
||||
" var b;\n"
|
||||
" /// I should not interfere with actual natspec comments\n"
|
||||
" uint256 c;\n"
|
||||
" mapping(address=>bytes32) d;\n"
|
||||
" bytes7 name = \"Solidity\";"
|
||||
" }\n"
|
||||
" /// This is a test function\n"
|
||||
" /// and it has 2 lines\n"
|
||||
" function fun(bytes32 input) returns (bytes32 out) {}\n"
|
||||
"}\n";
|
||||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed");
|
||||
auto functions = contract->getDefinedFunctions();
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function");
|
||||
checkFunctionNatspec(function, "fun1 description");
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(1), "Failed to retrieve function");
|
||||
checkFunctionNatspec(function, "This is a test function\n"
|
||||
" and it has 2 lines");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(natspec_docstring_between_keyword_and_signature)
|
||||
{
|
||||
ASTPointer<ContractDefinition> contract;
|
||||
ASTPointer<FunctionDefinition> function;
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" function ///I am in the wrong place \n"
|
||||
" fun1(uint256 a) {\n"
|
||||
" var b;\n"
|
||||
" /// I should not interfere with actual natspec comments\n"
|
||||
" uint256 c;\n"
|
||||
" mapping(address=>bytes32) d;\n"
|
||||
" bytes7 name = \"Solidity\";"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed");
|
||||
auto functions = contract->getDefinedFunctions();
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function");
|
||||
BOOST_CHECK_MESSAGE(!function->getDocumentation(),
|
||||
"Shouldn't get natspec docstring for this function");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(natspec_docstring_after_signature)
|
||||
{
|
||||
ASTPointer<ContractDefinition> contract;
|
||||
ASTPointer<FunctionDefinition> function;
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" function fun1(uint256 a) {\n"
|
||||
" /// I should have been above the function signature\n"
|
||||
" var b;\n"
|
||||
" /// I should not interfere with actual natspec comments\n"
|
||||
" uint256 c;\n"
|
||||
" mapping(address=>bytes32) d;\n"
|
||||
" bytes7 name = \"Solidity\";"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_REQUIRE_NO_THROW(contract = parseText(text), "Parsing failed");
|
||||
auto functions = contract->getDefinedFunctions();
|
||||
|
||||
ETH_TEST_REQUIRE_NO_THROW(function = functions.at(0), "Failed to retrieve function");
|
||||
BOOST_CHECK_MESSAGE(!function->getDocumentation(),
|
||||
"Shouldn't get natspec docstring for this function");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(struct_definition)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" uint256 stateVar;\n"
|
||||
" struct MyStructName {\n"
|
||||
" address addr;\n"
|
||||
" uint256 count;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mapping)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" mapping(address => bytes32) names;\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mapping_in_struct)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" struct test_struct {\n"
|
||||
" address addr;\n"
|
||||
" uint256 count;\n"
|
||||
" mapping(bytes32 => test_struct) self_reference;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mapping_to_mapping_in_struct)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" struct test_struct {\n"
|
||||
" address addr;\n"
|
||||
" mapping (uint64 => mapping (bytes32 => uint)) complex_mapping;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(variable_definition)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" var b;\n"
|
||||
" uint256 c;\n"
|
||||
" mapping(address=>bytes32) d;\n"
|
||||
" customtype varname;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(variable_definition_with_initialization)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" var b = 2;\n"
|
||||
" uint256 c = 0x87;\n"
|
||||
" mapping(address=>bytes32) d;\n"
|
||||
" bytes7 name = \"Solidity\";"
|
||||
" customtype varname;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(variable_definition_in_function_parameter)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract test {
|
||||
function fun(var a) {}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(variable_definition_in_mapping)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract test {
|
||||
function fun() {
|
||||
mapping(var=>bytes32) d;
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(variable_definition_in_function_return)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract test {
|
||||
function fun() returns(var d) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(operator_expression)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" uint256 x = (1 + 4) || false && (1 - 12) + -9;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(complex_expression)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" uint256 x = (1 + 4).member(++67)[a/=9] || true;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(exp_expression)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract test {
|
||||
function fun(uint256 a) {
|
||||
uint256 x = 3 ** a;
|
||||
}
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(while_loop)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" while (true) { uint256 x = 1; break; continue; } x = 9;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_loop_vardef_initexpr)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" for (uint256 i = 0; i < 10; i++)\n"
|
||||
" { uint256 x = i; break; continue; }\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_loop_simple_initexpr)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" uint256 i =0;\n"
|
||||
" for (i = 0; i < 10; i++)\n"
|
||||
" { uint256 x = i; break; continue; }\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_loop_simple_noexpr)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" uint256 i =0;\n"
|
||||
" for (;;)\n"
|
||||
" { uint256 x = i; break; continue; }\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(for_loop_single_stmt_body)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" uint256 i =0;\n"
|
||||
" for (i = 0; i < 10; i++)\n"
|
||||
" continue;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(if_statement)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) {\n"
|
||||
" if (a >= 8) return 2; else { var b = 7; }\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(else_if_statement)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun(uint256 a) returns (address b) {\n"
|
||||
" if (a < 0) b = 0x67; else if (a == 0) b = 0x12; else b = 0x78;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(statement_starting_with_type_conversion)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" uint64[7](3);\n"
|
||||
" uint64[](3);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(type_conversion_to_dynamic_array)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun() {\n"
|
||||
" var x = uint64[](3);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(import_directive)
|
||||
{
|
||||
char const* text = "import \"abc\";\n"
|
||||
"contract test {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_contracts)
|
||||
{
|
||||
char const* text = "contract test {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"contract test2 {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_contracts_and_imports)
|
||||
{
|
||||
char const* text = "import \"abc\";\n"
|
||||
"contract test {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"import \"def\";\n"
|
||||
"contract test2 {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"import \"ghi\";\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(contract_inheritance)
|
||||
{
|
||||
char const* text = "contract base {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"contract derived is base {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(contract_multiple_inheritance)
|
||||
{
|
||||
char const* text = "contract base {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"contract derived is base, nonExisting {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(contract_multiple_inheritance_with_arguments)
|
||||
{
|
||||
char const* text = "contract base {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"contract derived is base(2), nonExisting(\"abc\", \"def\", base.fun()) {\n"
|
||||
" function fun() {\n"
|
||||
" uint64(2);\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(placeholder_in_function_context)
|
||||
{
|
||||
char const* text = "contract c {\n"
|
||||
" function fun() returns (uint r) {\n"
|
||||
" var _ = 8;\n"
|
||||
" return _ + 1;"
|
||||
" }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(modifier)
|
||||
{
|
||||
char const* text = "contract c {\n"
|
||||
" modifier mod { if (msg.sender == 0) _ }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(modifier_arguments)
|
||||
{
|
||||
char const* text = "contract c {\n"
|
||||
" modifier mod(uint a) { if (msg.sender == a) _ }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(modifier_invocation)
|
||||
{
|
||||
char const* text = "contract c {\n"
|
||||
" modifier mod1(uint a) { if (msg.sender == a) _ }\n"
|
||||
" modifier mod2 { if (msg.sender == 2) _ }\n"
|
||||
" function f() mod1(7) mod2 { }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(fallback_function)
|
||||
{
|
||||
char const* text = "contract c {\n"
|
||||
" function() { }\n"
|
||||
"}\n";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(event)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
event e();
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(event_arguments)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
event e(uint a, bytes32 s);
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(event_arguments_indexed)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
event e(uint a, bytes32 indexed s, bool indexed b);
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(visibility_specifiers)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
uint private a;
|
||||
uint internal b;
|
||||
uint public c;
|
||||
uint d;
|
||||
function f() {}
|
||||
function f_priv() private {}
|
||||
function f_public() public {}
|
||||
function f_internal() internal {}
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiple_visibility_specifiers)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
uint private internal a;
|
||||
})";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(literal_constants_with_ether_subdenominations)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
function c ()
|
||||
{
|
||||
a = 1 wei;
|
||||
b = 2 szabo;
|
||||
c = 3 finney;
|
||||
b = 4 ether;
|
||||
}
|
||||
uint256 a;
|
||||
uint256 b;
|
||||
uint256 c;
|
||||
uint256 d;
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(literal_constants_with_ether_subdenominations_in_expressions)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
function c ()
|
||||
{
|
||||
a = 1 wei * 100 wei + 7 szabo - 3;
|
||||
}
|
||||
uint256 a;
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(enum_valid_declaration)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
enum validEnum { Value1, Value2, Value3, Value4 }
|
||||
function c ()
|
||||
{
|
||||
a = foo.Value3;
|
||||
}
|
||||
uint256 a;
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_enum_declaration)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
enum foo { }
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(malformed_enum_declaration)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
enum foo { WARNING,}
|
||||
})";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(external_function)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
function x() external {}
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(external_variable)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
uint external x;
|
||||
})";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(arrays_in_storage)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
uint[10] a;
|
||||
uint[] a2;
|
||||
struct x { uint[2**20] b; y[0] c; }
|
||||
struct y { uint d; mapping(uint=>x)[] e; }
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(arrays_in_events)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
event e(uint[10] a, bytes7[8] indexed b, c[3] x);
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(arrays_in_expressions)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
function f() { c[10] a = 7; uint8[10 * 2] x; }
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multi_arrays)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c {
|
||||
mapping(uint => mapping(uint => int8)[8][][9])[] x;
|
||||
})";
|
||||
ETH_TEST_CHECK_NO_THROW(parseText(text), "Parsing failed");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(constant_is_keyword)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract Foo {
|
||||
uint constant = 4;
|
||||
})";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(var_array)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract Foo {
|
||||
function f() { var[] a; }
|
||||
})";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(location_specifiers_for_params)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract Foo {
|
||||
function f(uint[] storage constant x, uint[] memory y) { }
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK_NO_THROW(parseText(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(location_specifiers_for_locals)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract Foo {
|
||||
function f() {
|
||||
uint[] storage x;
|
||||
uint[] memory y;
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK_NO_THROW(parseText(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(location_specifiers_for_state)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract Foo {
|
||||
uint[] memory x;
|
||||
})";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(location_specifiers_with_var)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract Foo {
|
||||
function f() { var memory x; }
|
||||
})";
|
||||
BOOST_CHECK_THROW(parseText(text), ParserError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
} // end namespaces
|
288
test/libsolidity/SolidityScanner.cpp
Normal file
288
test/libsolidity/SolidityScanner.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
/*
|
||||
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 2014
|
||||
* Unit tests for the solidity scanner.
|
||||
*/
|
||||
|
||||
#include <libsolidity/Scanner.h>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(SolidityScanner)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_empty)
|
||||
{
|
||||
Scanner scanner(CharStream(""));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(smoke_test)
|
||||
{
|
||||
Scanner scanner(CharStream("function break;765 \t \"string1\",'string2'\nidentifier1"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Function);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Break);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "765");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "string1");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Comma);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "string2");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "identifier1");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(string_escapes)
|
||||
{
|
||||
Scanner scanner(CharStream(" { \"a\\x61\""));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LBrace);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "aa");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(string_escapes_with_zero)
|
||||
{
|
||||
Scanner scanner(CharStream(" { \"a\\x61\\x00abc\""));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LBrace);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), std::string("aa\0abc", 6));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(string_escape_illegal)
|
||||
{
|
||||
Scanner scanner(CharStream(" bla \"\\x6rf\" (illegalescape)"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "");
|
||||
// TODO recovery from illegal tokens should be improved
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(hex_numbers)
|
||||
{
|
||||
Scanner scanner(CharStream("var x = 0x765432536763762734623472346;"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Var);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Assign);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "0x765432536763762734623472346");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(negative_numbers)
|
||||
{
|
||||
Scanner scanner(CharStream("var x = -.2 + -0x78 + -7.3 + 8.9;"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Var);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Assign);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), ".2");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "0x78");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "7.3");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "8.9");
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(locations)
|
||||
{
|
||||
Scanner scanner(CharStream("function_identifier has ; -0x743/*comment*/\n ident //comment"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 0);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 19);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 20);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 23);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 24);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 25);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Sub);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 27);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 32);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 45);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 50);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ambiguities)
|
||||
{
|
||||
// test scanning of some operators which need look-ahead
|
||||
Scanner scanner(CharStream("<=""<""+ +=a++ =>""<<"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LessThanOrEqual);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::LessThan);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Add);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::AssignAdd);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Inc);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Arrow);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SHL);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(documentation_comments_parsed_begin)
|
||||
{
|
||||
Scanner scanner(CharStream("/// Send $(value / 1000) chocolates to the user"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed_begin)
|
||||
{
|
||||
Scanner scanner(CharStream("/** Send $(value / 1000) chocolates to the user*/"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(documentation_comments_parsed)
|
||||
{
|
||||
Scanner scanner(CharStream("some other tokens /// Send $(value / 1000) chocolates to the user"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed)
|
||||
{
|
||||
Scanner scanner(CharStream("some other tokens /**\n"
|
||||
"* Send $(value / 1000) chocolates to the user\n"
|
||||
"*/"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiline_documentation_no_stars)
|
||||
{
|
||||
Scanner scanner(CharStream("some other tokens /**\n"
|
||||
" Send $(value / 1000) chocolates to the user\n"
|
||||
"*/"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multiline_documentation_whitespace_hell)
|
||||
{
|
||||
Scanner scanner(CharStream("some other tokens /** \t \r \n"
|
||||
"\t \r * Send $(value / 1000) chocolates to the user\n"
|
||||
"*/"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(comment_before_eos)
|
||||
{
|
||||
Scanner scanner(CharStream("//"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(documentation_comment_before_eos)
|
||||
{
|
||||
Scanner scanner(CharStream("///"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_multiline_comment)
|
||||
{
|
||||
Scanner scanner(CharStream("/**/"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(empty_multiline_documentation_comment_before_eos)
|
||||
{
|
||||
Scanner scanner(CharStream("/***/"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(comments_mixed_in_sequence)
|
||||
{
|
||||
Scanner scanner(CharStream("hello_world ///documentation comment \n"
|
||||
"//simple comment \n"
|
||||
"<<"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::Identifier);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SHL);
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "documentation comment ");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(ether_subdenominations)
|
||||
{
|
||||
Scanner scanner(CharStream("wei szabo finney ether"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::SubWei);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubSzabo);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubFinney);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubEther);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(time_subdenominations)
|
||||
{
|
||||
Scanner scanner(CharStream("seconds minutes hours days weeks years"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::SubSecond);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubMinute);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubHour);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubDay);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubWeek);
|
||||
BOOST_CHECK_EQUAL(scanner.next(), Token::SubYear);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(time_after)
|
||||
{
|
||||
Scanner scanner(CharStream("after 1"));
|
||||
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::After);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
} // end namespaces
|
93
test/libsolidity/SolidityTypes.cpp
Normal file
93
test/libsolidity/SolidityTypes.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
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
|
||||
* Unit tests for the type system of Solidity.
|
||||
*/
|
||||
|
||||
#include <libsolidity/Types.h>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(SolidityTypes)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(storage_layout_simple)
|
||||
{
|
||||
MemberList members(MemberList::MemberMap({
|
||||
{string("first"), Type::fromElementaryTypeName("uint128")},
|
||||
{string("second"), Type::fromElementaryTypeName("uint120")},
|
||||
{string("wraps"), Type::fromElementaryTypeName("uint16")}
|
||||
}));
|
||||
BOOST_REQUIRE_EQUAL(u256(2), members.getStorageSize());
|
||||
BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr);
|
||||
BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr);
|
||||
BOOST_REQUIRE(members.getMemberStorageOffset("wraps") != nullptr);
|
||||
BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0)));
|
||||
BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(0), unsigned(16)));
|
||||
BOOST_CHECK(*members.getMemberStorageOffset("wraps") == make_pair(u256(1), unsigned(0)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(storage_layout_mapping)
|
||||
{
|
||||
MemberList members(MemberList::MemberMap({
|
||||
{string("first"), Type::fromElementaryTypeName("uint128")},
|
||||
{string("second"), make_shared<MappingType>(
|
||||
Type::fromElementaryTypeName("uint8"),
|
||||
Type::fromElementaryTypeName("uint8")
|
||||
)},
|
||||
{string("third"), Type::fromElementaryTypeName("uint16")},
|
||||
{string("final"), make_shared<MappingType>(
|
||||
Type::fromElementaryTypeName("uint8"),
|
||||
Type::fromElementaryTypeName("uint8")
|
||||
)},
|
||||
}));
|
||||
BOOST_REQUIRE_EQUAL(u256(4), members.getStorageSize());
|
||||
BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr);
|
||||
BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr);
|
||||
BOOST_REQUIRE(members.getMemberStorageOffset("third") != nullptr);
|
||||
BOOST_REQUIRE(members.getMemberStorageOffset("final") != nullptr);
|
||||
BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0)));
|
||||
BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(1), unsigned(0)));
|
||||
BOOST_CHECK(*members.getMemberStorageOffset("third") == make_pair(u256(2), unsigned(0)));
|
||||
BOOST_CHECK(*members.getMemberStorageOffset("final") == make_pair(u256(3), unsigned(0)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(storage_layout_arrays)
|
||||
{
|
||||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(1), 32).getStorageSize() == 1);
|
||||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(1), 33).getStorageSize() == 2);
|
||||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(2), 31).getStorageSize() == 2);
|
||||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(7), 8).getStorageSize() == 2);
|
||||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(7), 9).getStorageSize() == 3);
|
||||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(31), 9).getStorageSize() == 9);
|
||||
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(32), 9).getStorageSize() == 9);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
310
test/libsolidity/solidityExecutionFramework.h
Normal file
310
test/libsolidity/solidityExecutionFramework.h
Normal file
@ -0,0 +1,310 @@
|
||||
/*
|
||||
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 2014
|
||||
* Framework for executing Solidity contracts and testing them against C++ implementation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include "../TestHelper.h"
|
||||
#include <libethcore/ABI.h>
|
||||
#include <libethereum/State.h>
|
||||
#include <libethereum/Executive.h>
|
||||
#include <libsolidity/CompilerStack.h>
|
||||
#include <libsolidity/Exceptions.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
||||
namespace solidity
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
class ExecutionFramework
|
||||
{
|
||||
public:
|
||||
ExecutionFramework()
|
||||
{
|
||||
if (g_logVerbosity != -1)
|
||||
g_logVerbosity = 0;
|
||||
//m_state.resetCurrent();
|
||||
}
|
||||
|
||||
bytes const& compileAndRunWithoutCheck(
|
||||
std::string const& _sourceCode,
|
||||
u256 const& _value = 0,
|
||||
std::string const& _contractName = "",
|
||||
bytes const& _arguments = bytes()
|
||||
)
|
||||
{
|
||||
m_compiler.reset(false, m_addStandardSources);
|
||||
m_compiler.addSource("", _sourceCode);
|
||||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), "Compiling contract failed");
|
||||
bytes code = m_compiler.getBytecode(_contractName);
|
||||
sendMessage(code + _arguments, true, _value);
|
||||
return m_output;
|
||||
}
|
||||
|
||||
template <class Exceptiontype>
|
||||
void compileRequireThrow(std::string const& _sourceCode)
|
||||
{
|
||||
m_compiler.reset(false, m_addStandardSources);
|
||||
m_compiler.addSource("", _sourceCode);
|
||||
BOOST_REQUIRE_THROW(m_compiler.compile(m_optimize, m_optimizeRuns), Exceptiontype);
|
||||
}
|
||||
|
||||
bytes const& compileAndRun(
|
||||
std::string const& _sourceCode,
|
||||
u256 const& _value = 0,
|
||||
std::string const& _contractName = "",
|
||||
bytes const& _arguments = bytes()
|
||||
)
|
||||
{
|
||||
compileAndRunWithoutCheck(_sourceCode, _value, _contractName, _arguments);
|
||||
BOOST_REQUIRE(!m_output.empty());
|
||||
return m_output;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments)
|
||||
{
|
||||
FixedHash<4> hash(dev::sha3(_sig));
|
||||
sendMessage(hash.asBytes() + encodeArgs(_arguments...), false, _value);
|
||||
return m_output;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
bytes const& callContractFunction(std::string _sig, Args const&... _arguments)
|
||||
{
|
||||
return callContractFunctionWithValue(_sig, 0, _arguments...);
|
||||
}
|
||||
|
||||
template <class CppFunction, class... Args>
|
||||
void testSolidityAgainstCpp(std::string _sig, CppFunction const& _cppFunction, Args const&... _arguments)
|
||||
{
|
||||
bytes solidityResult = callContractFunction(_sig, _arguments...);
|
||||
bytes cppResult = callCppAndEncodeResult(_cppFunction, _arguments...);
|
||||
BOOST_CHECK_MESSAGE(
|
||||
solidityResult == cppResult,
|
||||
"Computed values do not match.\nSolidity: " +
|
||||
toHex(solidityResult) +
|
||||
"\nC++: " +
|
||||
toHex(cppResult));
|
||||
}
|
||||
|
||||
template <class CppFunction, class... Args>
|
||||
void testSolidityAgainstCppOnRange(std::string _sig, CppFunction const& _cppFunction, u256 const& _rangeStart, u256 const& _rangeEnd)
|
||||
{
|
||||
for (u256 argument = _rangeStart; argument < _rangeEnd; ++argument)
|
||||
{
|
||||
bytes solidityResult = callContractFunction(_sig, argument);
|
||||
bytes cppResult = callCppAndEncodeResult(_cppFunction, argument);
|
||||
BOOST_CHECK_MESSAGE(
|
||||
solidityResult == cppResult,
|
||||
"Computed values do not match.\nSolidity: " +
|
||||
toHex(solidityResult) +
|
||||
"\nC++: " +
|
||||
toHex(cppResult) +
|
||||
"\nArgument: " +
|
||||
toHex(encode(argument))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static bytes encode(bool _value) { return encode(byte(_value)); }
|
||||
static bytes encode(int _value) { return encode(u256(_value)); }
|
||||
static bytes encode(size_t _value) { return encode(u256(_value)); }
|
||||
static bytes encode(char const* _value) { return encode(std::string(_value)); }
|
||||
static bytes encode(byte _value) { return bytes(31, 0) + bytes{_value}; }
|
||||
static bytes encode(u256 const& _value) { return toBigEndian(_value); }
|
||||
static bytes encode(h256 const& _value) { return _value.asBytes(); }
|
||||
static bytes encode(bytes const& _value, bool _padLeft = true)
|
||||
{
|
||||
bytes padding = bytes((32 - _value.size() % 32) % 32, 0);
|
||||
return _padLeft ? padding + _value : _value + padding;
|
||||
}
|
||||
static bytes encode(std::string const& _value) { return encode(asBytes(_value), false); }
|
||||
template <class _T>
|
||||
static bytes encode(std::vector<_T> const& _value)
|
||||
{
|
||||
bytes ret;
|
||||
for (auto const& v: _value)
|
||||
ret += encode(v);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <class FirstArg, class... Args>
|
||||
static bytes encodeArgs(FirstArg const& _firstArg, Args const&... _followingArgs)
|
||||
{
|
||||
return encode(_firstArg) + encodeArgs(_followingArgs...);
|
||||
}
|
||||
static bytes encodeArgs()
|
||||
{
|
||||
return bytes();
|
||||
}
|
||||
//@todo might be extended in the future
|
||||
template <class Arg>
|
||||
static bytes encodeDyn(Arg const& _arg)
|
||||
{
|
||||
return encodeArgs(u256(0x20), u256(_arg.size()), _arg);
|
||||
}
|
||||
|
||||
class ContractInterface
|
||||
{
|
||||
public:
|
||||
ContractInterface(ExecutionFramework& _framework): m_framework(_framework) {}
|
||||
|
||||
void setNextValue(u256 const& _value) { m_nextValue = _value; }
|
||||
|
||||
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)
|
||||
{
|
||||
bytesConstRef ret = ref(call(_name + "(address)", _arg));
|
||||
BOOST_REQUIRE(ret.size() >= 0x20);
|
||||
u256 offset = eth::abiOut<u256>(ret);
|
||||
BOOST_REQUIRE_EQUAL(offset, 0x20);
|
||||
u256 len = eth::abiOut<u256>(ret);
|
||||
BOOST_REQUIRE_EQUAL(ret.size(), ((len + 0x1f) / 0x20) * 0x20);
|
||||
return ret.cropped(0, size_t(len)).toString();
|
||||
}
|
||||
|
||||
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)
|
||||
-> typename std::enable_if<std::is_void<decltype(_cppFunction(_arguments...))>::value, bytes>::type
|
||||
{
|
||||
_cppFunction(_arguments...);
|
||||
return bytes();
|
||||
}
|
||||
template <class CppFunction, class... Args>
|
||||
auto callCppAndEncodeResult(CppFunction const& _cppFunction, Args const&... _arguments)
|
||||
-> typename std::enable_if<!std::is_void<decltype(_cppFunction(_arguments...))>::value, bytes>::type
|
||||
{
|
||||
return encode(_cppFunction(_arguments...));
|
||||
}
|
||||
|
||||
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, m_envInfo, 0);
|
||||
eth::ExecutionResult res;
|
||||
executive.setResultRecipient(res);
|
||||
eth::Transaction t =
|
||||
_isCreation ?
|
||||
eth::Transaction(_value, m_gasPrice, m_gas, _data, 0, KeyPair::create().sec()) :
|
||||
eth::Transaction(_value, m_gasPrice, m_gas, m_contractAddress, _data, 0, KeyPair::create().sec());
|
||||
bytes transactionRLP = t.rlp();
|
||||
try
|
||||
{
|
||||
// this will throw since the transaction is invalid, but it should nevertheless store the transaction
|
||||
executive.initialize(&transactionRLP);
|
||||
executive.execute();
|
||||
}
|
||||
catch (...) {}
|
||||
if (_isCreation)
|
||||
{
|
||||
BOOST_REQUIRE(!executive.create(m_sender, _value, m_gasPrice, m_gas, &_data, m_sender));
|
||||
m_contractAddress = executive.newAddress();
|
||||
BOOST_REQUIRE(m_contractAddress);
|
||||
BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress));
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress));
|
||||
BOOST_REQUIRE(!executive.call(m_contractAddress, m_sender, _value, m_gasPrice, &_data, m_gas));
|
||||
}
|
||||
BOOST_REQUIRE(executive.go(/* DEBUG eth::Executive::simpleTrace() */));
|
||||
m_state.noteSending(m_sender);
|
||||
executive.finalize();
|
||||
m_gasUsed = res.gasUsed;
|
||||
m_output = std::move(res.output);
|
||||
m_logs = executive.logs();
|
||||
}
|
||||
|
||||
size_t m_optimizeRuns = 200;
|
||||
bool m_optimize = false;
|
||||
bool m_addStandardSources = false;
|
||||
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;
|
||||
bytes m_output;
|
||||
eth::LogEntries m_logs;
|
||||
u256 m_gasUsed;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // end namespaces
|
||||
|
Loading…
Reference in New Issue
Block a user