Merge remote-tracking branch 'origin/develop' into HEAD

This commit is contained in:
chriseth
2020-10-29 16:44:47 +01:00
16 changed files with 261 additions and 154 deletions
+22 -10
View File
@@ -154,12 +154,12 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
{
if (constructed)
{
soltestAssert(!test.call().isLibrary, "Libraries have to be deployed before any other call.");
soltestAssert(test.call().kind != FunctionCall::Kind::Library, "Libraries have to be deployed before any other call.");
soltestAssert(
!test.call().isConstructor,
test.call().kind != FunctionCall::Kind::Constructor,
"Constructor has to be the first function call expect for library deployments.");
}
else if (test.call().isLibrary)
else if (test.call().kind == FunctionCall::Kind::Library)
{
soltestAssert(
deploy(test.call().signature, 0, {}, libraries) && m_transactionSuccessful,
@@ -169,14 +169,23 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
}
else
{
if (test.call().isConstructor)
if (test.call().kind == FunctionCall::Kind::Constructor)
deploy("", test.call().value.value, test.call().arguments.rawBytes(), libraries);
else
soltestAssert(deploy("", 0, bytes(), libraries), "Failed to deploy contract.");
constructed = true;
}
if (test.call().isConstructor)
if (test.call().kind == FunctionCall::Kind::Storage)
{
test.setFailure(false);
bytes result(1, !storageEmpty(m_contractAddress));
test.setRawBytes(result);
soltestAssert(test.call().expectations.rawBytes().size() == 1, "");
if (test.call().expectations.rawBytes() != result)
success = false;
}
else if (test.call().kind == FunctionCall::Kind::Constructor)
{
if (m_transactionSuccessful == test.call().expectations.failure)
success = false;
@@ -187,17 +196,20 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
else
{
bytes output;
if (test.call().useCallWithoutSignature)
if (test.call().kind == FunctionCall::Kind::LowLevel)
output = callLowLevel(test.call().arguments.rawBytes(), test.call().value.value);
else
{
soltestAssert(
m_allowNonExistingFunctions || m_compiler.methodIdentifiers(m_compiler.lastContractName())
.isMember(test.call().signature),
"The function " + test.call().signature + " is not known to the compiler");
m_allowNonExistingFunctions ||
m_compiler.methodIdentifiers(m_compiler.lastContractName()).isMember(test.call().signature),
"The function " + test.call().signature + " is not known to the compiler"
);
output = callContractFunctionWithValueNoEncoding(
test.call().signature, test.call().value.value, test.call().arguments.rawBytes()
test.call().signature,
test.call().value.value,
test.call().arguments.rawBytes()
);
}
@@ -41,6 +41,10 @@ contract c {
// ====
// compileViaYul: also
// ----
// storage: empty
// test_short() -> 1780731860627700044960722568376587075150542249149356309979516913770823710
// storage: nonempty
// test_long() -> 67
// storage: nonempty
// test_pop() -> 1780731860627700044960722568376592200742329637303199754547598369979433020
// storage: nonempty
@@ -0,0 +1,29 @@
contract Test {
bytes x;
function set(bytes memory _a) public { x = _a; }
}
// ----
// set(bytes): 0x20, 3, "abc"
// storage: nonempty
// set(bytes): 0x20, 0
// storage: empty
// set(bytes): 0x20, 31, "1234567890123456789012345678901"
// storage: nonempty
// set(bytes): 0x20, 36, "12345678901234567890123456789012", "XXXX"
// storage: nonempty
// set(bytes): 0x20, 3, "abc"
// storage: nonempty
// set(bytes): 0x20, 0
// storage: empty
// set(bytes): 0x20, 3, "abc"
// storage: nonempty
// set(bytes): 0x20, 36, "12345678901234567890123456789012", "XXXX"
// storage: nonempty
// set(bytes): 0x20, 0
// storage: empty
// set(bytes): 0x20, 66, "12345678901234567890123456789012", "12345678901234567890123456789012", "12"
// storage: nonempty
// set(bytes): 0x20, 3, "abc"
// storage: nonempty
// set(bytes): 0x20, 0
// storage: empty
+15 -7
View File
@@ -58,6 +58,7 @@ namespace solidity::frontend::test
K(Library, "library", 0) \
K(Right, "right", 0) \
K(Failure, "FAILURE", 0) \
K(Storage, "storage", 0) \
namespace soltest
{
@@ -275,16 +276,23 @@ struct FunctionCall
MultiLine
};
DisplayMode displayMode = DisplayMode::SingleLine;
/// Marks this function call as the constructor.
bool isConstructor = false;
/// If this function call's signature has no name and no arguments,
/// a low-level call with unstructured calldata will be issued.
bool useCallWithoutSignature = false;
enum class Kind {
Regular,
/// Marks this function call as the constructor.
Constructor,
/// If this function call's signature has no name and no arguments,
/// a low-level call with unstructured calldata will be issued.
LowLevel,
/// Marks a library deployment call.
Library,
/// Check that the storage of the current contract is empty or non-empty.
Storage
};
Kind kind = Kind::Regular;
/// Marks this function call as "short-handed", meaning
/// no `->` declared.
bool omitsArrow = true;
/// Marks a library deployment call.
bool isLibrary = false;
};
}
+23 -4
View File
@@ -82,12 +82,31 @@ vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctionCall
expect(Token::Colon);
call.signature = m_scanner.currentLiteral();
expect(Token::Identifier);
call.isLibrary = true;
call.kind = FunctionCall::Kind::Library;
call.expectations.failure = false;
}
else if (accept(Token::Storage, true))
{
expect(Token::Colon);
call.expectations.failure = false;
call.expectations.result.push_back(Parameter());
// empty / non-empty is encoded as false / true
if (m_scanner.currentLiteral() == "empty")
call.expectations.result.back().rawBytes = bytes(1, uint8_t(false));
else if (m_scanner.currentLiteral() == "nonempty")
call.expectations.result.back().rawBytes = bytes(1, uint8_t(true));
else
throw TestParserError("Expected \"empty\" or \"nonempty\".");
call.kind = FunctionCall::Kind::Storage;
m_scanner.scanNextToken();
}
else
{
tie(call.signature, call.useCallWithoutSignature) = parseFunctionSignature();
bool lowLevelCall = false;
tie(call.signature, lowLevelCall) = parseFunctionSignature();
if (lowLevelCall)
call.kind = FunctionCall::Kind::LowLevel;
if (accept(Token::Comma, true))
call.value = parseFunctionCallValue();
@@ -124,8 +143,7 @@ vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctionCall
call.expectations.comment = parseComment();
if (call.signature == "constructor()")
call.isConstructor = true;
call.kind = FunctionCall::Kind::Constructor;
}
calls.emplace_back(std::move(call));
@@ -481,6 +499,7 @@ void TestFileParser::Scanner::scanNextToken()
if (_literal == "right") return TokenDesc{Token::Right, _literal};
if (_literal == "hex") return TokenDesc{Token::Hex, _literal};
if (_literal == "FAILURE") return TokenDesc{Token::Failure, _literal};
if (_literal == "storage") return TokenDesc{Token::Storage, _literal};
return TokenDesc{Token::Identifier, _literal};
};
+2 -1
View File
@@ -44,7 +44,8 @@ namespace solidity::frontend::test
* // h(uint256), 1 ether: 42
* // -> FAILURE # If REVERT or other EVM failure was detected #
* // () # Call fallback function #
* // (), 1 ether # Call ether function #
* // (), 1 ether # Call receive ether function #
* // EMPTY_STORAGE # Check that storage is empty
* ...
*/
class TestFileParser
+24 -2
View File
@@ -82,8 +82,8 @@ void testFunctionCall(
}
}
BOOST_REQUIRE_EQUAL(_call.isConstructor, _isConstructor);
BOOST_REQUIRE_EQUAL(_call.isLibrary, _isLibrary);
BOOST_REQUIRE_EQUAL(_call.kind == FunctionCall::Kind::Constructor, _isConstructor);
BOOST_REQUIRE_EQUAL(_call.kind == FunctionCall::Kind::Library, _isLibrary);
}
BOOST_AUTO_TEST_SUITE(TestFileParserTest)
@@ -953,6 +953,28 @@ BOOST_AUTO_TEST_CASE(library)
);
}
BOOST_AUTO_TEST_CASE(empty_storage)
{
char const* source = R"(
// storage: empty
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 1);
BOOST_CHECK(calls.at(0).kind == FunctionCall::Kind::Storage);
BOOST_CHECK(calls.at(0).expectations.result.front().rawBytes == bytes(1, 0));
}
BOOST_AUTO_TEST_CASE(nonempty_storage)
{
char const* source = R"(
// storage: nonempty
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 1);
BOOST_CHECK(calls.at(0).kind == FunctionCall::Kind::Storage);
BOOST_CHECK(calls.at(0).expectations.result.front().rawBytes == bytes(1, 1));
}
BOOST_AUTO_TEST_SUITE_END()
}
+15 -1
View File
@@ -56,11 +56,25 @@ string TestFunctionCall::format(
string newline = formatToken(Token::Newline);
string failure = formatToken(Token::Failure);
if (m_call.isLibrary)
if (m_call.kind == FunctionCall::Kind::Library)
{
stream << _linePrefix << newline << ws << "library:" << ws << m_call.signature;
return;
}
else if (m_call.kind == FunctionCall::Kind::Storage)
{
stream << _linePrefix << newline << ws << "storage" << colon << ws;
soltestAssert(m_rawBytes.size() == 1, "");
soltestAssert(m_call.expectations.rawBytes().size() == 1, "");
bool isEmpty = _renderResult ? m_rawBytes.front() == 0 : m_call.expectations.rawBytes().front() == 0;
string output = isEmpty ? "empty" : "nonempty";
if (_renderResult && !matchesExpectation())
AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << output;
else
stream << output;
return;
}
/// Formats the function signature. This is the same independent from the display-mode.
stream << _linePrefix << newline << ws << m_call.signature;
@@ -115,8 +115,6 @@ uint64_t popcnt(uint64_t _v)
}
using u512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
u256 EwasmBuiltinInterpreter::evalBuiltin(
YulString _functionName,
vector<Expression> const& _arguments,
@@ -144,14 +142,14 @@ u256 EwasmBuiltinInterpreter::evalBuiltin(
else if (fun == "datacopy")
{
// This is identical to codecopy.
if (accessMemory(_evaluatedArguments.at(0), _evaluatedArguments.at(2)))
copyZeroExtended(
m_state.memory,
m_state.code,
static_cast<size_t>(_evaluatedArguments.at(0)),
static_cast<size_t>(_evaluatedArguments.at(1) & numeric_limits<size_t>::max()),
static_cast<size_t>(_evaluatedArguments.at(2))
);
accessMemory(_evaluatedArguments.at(0), _evaluatedArguments.at(2));
copyZeroExtended(
m_state.memory,
m_state.code,
static_cast<size_t>(_evaluatedArguments.at(0)),
static_cast<size_t>(_evaluatedArguments.at(1) & numeric_limits<size_t>::max()),
static_cast<size_t>(_evaluatedArguments.at(2))
);
return 0;
}
else if (fun == "i32.drop" || fun == "i64.drop" || fun == "nop")
@@ -324,11 +322,11 @@ u256 EwasmBuiltinInterpreter::evalEthBuiltin(string const& _fun, vector<uint64_t
{
if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.calldata.size())
throw ExplicitlyTerminated();
if (accessMemory(arg[0], arg[2]))
copyZeroExtended(
m_state.memory, m_state.calldata,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
accessMemory(arg[0], arg[2]);
copyZeroExtended(
m_state.memory, m_state.calldata,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
return {};
}
else if (_fun == "getCallDataSize")
@@ -377,11 +375,11 @@ u256 EwasmBuiltinInterpreter::evalEthBuiltin(string const& _fun, vector<uint64_t
}
else if (_fun == "codeCopy")
{
if (accessMemory(arg[0], arg[2]))
copyZeroExtended(
m_state.memory, m_state.code,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
accessMemory(arg[0], arg[2]);
copyZeroExtended(
m_state.memory, m_state.code,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
return 0;
}
else if (_fun == "getCodeSize")
@@ -407,12 +405,12 @@ u256 EwasmBuiltinInterpreter::evalEthBuiltin(string const& _fun, vector<uint64_t
else if (_fun == "externalCodeCopy")
{
readAddress(arg[0]);
if (accessMemory(arg[1], arg[3]))
// TODO this way extcodecopy and codecopy do the same thing.
copyZeroExtended(
m_state.memory, m_state.code,
size_t(arg[1]), size_t(arg[2]), size_t(arg[3])
);
accessMemory(arg[1], arg[3]);
// TODO this way extcodecopy and codecopy do the same thing.
copyZeroExtended(
m_state.memory, m_state.code,
size_t(arg[1]), size_t(arg[2]), size_t(arg[3])
);
return 0;
}
else if (_fun == "getExternalCodeSize")
@@ -454,16 +452,16 @@ u256 EwasmBuiltinInterpreter::evalEthBuiltin(string const& _fun, vector<uint64_t
else if (_fun == "finish")
{
bytes data;
if (accessMemory(arg[0], arg[1]))
data = readMemory(arg[0], arg[1]);
accessMemory(arg[0], arg[1]);
data = readMemory(arg[0], arg[1]);
logTrace(evmasm::Instruction::RETURN, {}, data);
throw ExplicitlyTerminated();
}
else if (_fun == "revert")
{
bytes data;
if (accessMemory(arg[0], arg[1]))
data = readMemory(arg[0], arg[1]);
accessMemory(arg[0], arg[1]);
data = readMemory(arg[0], arg[1]);
logTrace(evmasm::Instruction::REVERT, {}, data);
throw ExplicitlyTerminated();
}
@@ -473,11 +471,11 @@ u256 EwasmBuiltinInterpreter::evalEthBuiltin(string const& _fun, vector<uint64_t
{
if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.returndata.size())
throw ExplicitlyTerminated();
if (accessMemory(arg[0], arg[2]))
copyZeroExtended(
m_state.memory, m_state.calldata,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
accessMemory(arg[0], arg[2]);
copyZeroExtended(
m_state.memory, m_state.calldata,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
return {};
}
else if (_fun == "selfDestruct")
@@ -494,18 +492,15 @@ u256 EwasmBuiltinInterpreter::evalEthBuiltin(string const& _fun, vector<uint64_t
return 0;
}
bool EwasmBuiltinInterpreter::accessMemory(u256 const& _offset, u256 const& _size)
void EwasmBuiltinInterpreter::accessMemory(u256 const& _offset, u256 const& _size)
{
if (((_offset + _size) >= _offset) && ((_offset + _size + 0x1f) >= (_offset + _size)))
{
u256 newSize = (_offset + _size + 0x1f) & ~u256(0x1f);
m_state.msize = max(m_state.msize, newSize);
return _size <= 0xffff;
}
else
m_state.msize = u256(-1);
// Single WebAssembly page.
// TODO: Support expansion in this interpreter.
m_state.msize = 65536;
return false;
if (((_offset + _size) < _offset) || ((_offset + _size) > m_state.msize))
// Ewasm throws out of bounds exception as opposed to the EVM.
throw ExplicitlyTerminated();
}
bytes EwasmBuiltinInterpreter::readMemory(uint64_t _offset, uint64_t _size)
@@ -91,8 +91,7 @@ private:
/// Checks if the memory access is not too large for the interpreter and adjusts
/// msize accordingly.
/// @returns false if the amount of bytes read is lager than 0xffff
bool accessMemory(u256 const& _offset, u256 const& _size = 32);
void accessMemory(u256 const& _offset, u256 const& _size = 32);
/// @returns the memory contents at the provided address.
/// Does not adjust msize, use @a accessMemory for that
bytes readMemory(uint64_t _offset, uint64_t _size = 32);