Yul generation of require and assert

This commit is contained in:
Mathias Baumann 2019-04-30 19:08:02 +02:00
parent 442742e497
commit 7f14352bbf
7 changed files with 152 additions and 1 deletions

View File

@ -104,6 +104,60 @@ string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
});
}
string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType)
{
string functionName =
string(_assert ? "assert_helper" : "require_helper") +
(_messageType ? ("_" + _messageType->identifier()) : "");
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
return m_functionCollector->createFunction(functionName, [&]() {
if (!_messageType)
return Whiskers(R"(
function <functionName>(condition) {
if iszero(condition) { <invalidOrRevert> }
}
)")
("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)")
("functionName", functionName)
.render();
solUnimplemented("require() with two parameters is not yet implemented.");
// TODO The code below is completely untested as we don't support StringLiterals yet
int const hashHeaderSize = 4;
int const byteSize = 8;
u256 const errorHash =
u256(FixedHash<hashHeaderSize>::Arith(
FixedHash<hashHeaderSize>(dev::keccak256("Error(string)"))
)) << (256 - hashHeaderSize * byteSize);
string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector)
.tupleEncoder(
{_messageType},
{TypeProvider::stringMemory()}
);
return Whiskers(R"(
function <functionName>(condition, message) {
if iszero(condition) {
let fmp := mload(<freeMemPointer>)
mstore(fmp, <errorHash>)
let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>), message)
revert(fmp, sub(end, fmp))
}
}
)")
("functionName", functionName)
("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
("errorHash", errorHash.str())
("abiEncodeFunc", encodeFunc)
("hashHeaderSize", to_string(hashHeaderSize))
.render();
});
}
string YulUtilFunctions::leftAlignFunction(Type const& _type)
{
string functionName = string("leftAlign_") + _type.identifier();

View File

@ -62,6 +62,10 @@ public:
/// Pads with zeros and might write more than exactly length.
std::string copyToMemoryFunction(bool _fromCalldata);
// @returns the name of a function that has the equivalent logic of an
// `assert` or `require` call.
std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr);
/// @returns the name of a function that takes a (cleaned) value of the given value type and
/// left-aligns it, usually for use in non-padded encoding.
std::string leftAlignFunction(Type const& _type);

View File

@ -89,6 +89,8 @@ public:
/// @returns a new copy of the utility function generator (but using the same function set).
YulUtilFunctions utils();
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
private:
langutil::EVMVersion m_evmVersion;
OptimiserSettings m_optimiserSettings;

View File

@ -20,9 +20,11 @@
#include <libsolidity/codegen/ir/IRGeneratorForStatements.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libyul/AsmPrinter.h>
@ -30,6 +32,8 @@
#include <libyul/optimiser/ASTCopier.h>
#include <libdevcore/StringUtils.h>
#include <libdevcore/Whiskers.h>
#include <libdevcore/Keccak256.h>
using namespace std;
using namespace dev;
@ -302,6 +306,24 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
")\n";
break;
}
case FunctionType::Kind::Assert:
case FunctionType::Kind::Require:
{
solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert");
solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert");
string requireOrAssertFunction = m_utils.requireOrAssertFunction(
functionType->kind() == FunctionType::Kind::Assert,
arguments.size() > 1 ? arguments[1]->annotation().type : nullptr
);
m_code << move(requireOrAssertFunction) << "(" << m_context.variable(*arguments[0]);
if (arguments.size() > 1)
m_code << ", " << m_context.variable(*arguments[1]);
m_code << ")\n";
break;
}
default:
solUnimplemented("");
}
@ -340,7 +362,7 @@ bool IRGeneratorForStatements::visit(Identifier const& _identifier)
setLValue(_identifier, move(lvalue));
}
else
else if (!dynamic_cast<MagicVariableDeclaration const*>(declaration))
solUnimplemented("");
return false;
}

View File

@ -0,0 +1,22 @@
contract C {
function f(bool a) public pure returns (bool x) {
bool b = a;
x = b;
assert(b);
}
function fail() public pure returns (bool x) {
x = true;
assert(false);
}
function succeed() public pure returns (bool x) {
x = true;
assert(true);
}
}
// ====
// compileViaYul: true
// ----
// f(bool): true -> true
// f(bool): false -> FAILURE
// fail() -> FAILURE
// succeed() -> true

View File

@ -0,0 +1,19 @@
contract C {
function f(bool a) public pure returns (bool x) {
bool b = a;
x = b;
assert(b);
}
function f2(bool a) public pure returns (bool x) {
bool b = a;
x = b;
require(b);
}
}
// ====
// compileViaYul: true
// ----
// f(bool): true -> true
// f(bool): false -> FAILURE
// f2(bool): true -> true
// f2(bool): false -> FAILURE

View File

@ -0,0 +1,28 @@
contract C {
function f(bool a) public pure returns (bool x) {
bool b = a;
x = b;
require(b);
}
function fail() public pure returns (bool x) {
x = true;
require(false);
}
function succeed() public pure returns (bool x) {
x = true;
require(true);
}
/* Not properly supported by test system yet
function f2(bool a) public pure returns (bool x) {
x = a;
string memory message;
require(a, message);
}*/
}
// ====
// compileViaYul: true
// ----
// f(bool): true -> true
// f(bool): false -> FAILURE
// fail() -> FAILURE
// succeed() -> true