abiv2 proto fuzzer: Fuzz return data coding

This commit is contained in:
Bhargava Shastry 2019-10-08 02:09:59 +02:00
parent efeee15d32
commit 9772cc44a0
3 changed files with 312 additions and 113 deletions

View File

@ -92,8 +92,13 @@ message TestFunction {
}
message Contract {
enum Test {
CALLDATA_CODER = 1;
RETURNDATA_CODER = 2;
}
required VarDecl state_vars = 1;
required TestFunction testfunction = 2;
required Test test = 3;
}
package dev.test.abiv2fuzzer;

View File

@ -114,7 +114,14 @@ template <typename T>
pair<string, string> ProtoConverter::processType(T const& _type, bool _isValueType)
{
ostringstream local, global;
auto [varName, paramName] = newVarNames(getNextVarCounter());
auto [varName, paramName] = newVarNames(getNextVarCounter(), m_isStateVar);
// Add variable name to the argument list of coder function call
if (m_argsCoder.str().empty())
m_argsCoder << varName;
else
m_argsCoder << ", " << varName;
string location{};
if (!m_isStateVar && !_isValueType)
location = "memory";
@ -177,6 +184,16 @@ pair<string, string> ProtoConverter::varDecl(
_paramName,
((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD)
);
appendTypes(
_isValueType,
typeStr,
((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD)
);
appendTypedReturn(
_isValueType,
typeStr,
((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD)
);
// Update dyn param only if necessary
if (tVisitor.isLastDynParamRightPadded())
@ -259,6 +276,41 @@ void ProtoConverter::appendTypedParams(
}
}
void ProtoConverter::appendTypes(
bool _isValueType,
string const& _typeString,
Delimiter _delimiter
)
{
string qualifiedTypeString = (
_isValueType ?
_typeString :
_typeString + " memory"
);
m_types << Whiskers(R"(<delimiter><type>)")
("delimiter", delimiterToString(_delimiter))
("type", qualifiedTypeString)
.render();
}
void ProtoConverter::appendTypedReturn(
bool _isValueType,
string const& _typeString,
Delimiter _delimiter
)
{
string qualifiedTypeString = (
_isValueType ?
_typeString :
_typeString + " memory"
);
m_typedReturn << Whiskers(R"(<delimiter><type> <varName>)")
("delimiter", delimiterToString(_delimiter))
("type", qualifiedTypeString)
("varName", "lv_" + to_string(m_varCounter - 1))
.render();
}
// Adds the qualifier "calldata" to non-value parameter of an external function.
void ProtoConverter::appendTypedParamsExternal(
bool _isValueType,
@ -310,7 +362,6 @@ std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType)
}
}
// Test function to be called externally.
string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDefs)
{
// TODO: Support more than one but less than N local variables
@ -320,38 +371,76 @@ string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDe
string localVarDefs = localVarBuffers.second;
ostringstream testBuffer;
string functionDecl = "function test() public returns (uint)";
string testFunction = Whiskers(R"(
function test() public returns (uint) {
<?calldata>return test_calldata_coding();</calldata>
<?returndata>return test_returndata_coding();</returndata>
})")
("calldata", m_test == Contract_Test::Contract_Test_CALLDATA_CODER)
("returndata", m_test == Contract_Test::Contract_Test_RETURNDATA_CODER)
.render();
string functionDeclCalldata = "function test_calldata_coding() internal returns (uint)";
string functionDeclReturndata = "function test_returndata_coding() internal returns (uint)";
testBuffer << Whiskers(R"(<structTypeDecl>
<functionDecl> {
<testFunction>
<?calldata>
<functionDeclCalldata> {
<storageVarDefs>
<localVarDefs>
<testCode>
})")
<calldataTestCode>
}
<calldataHelperFuncs>
</calldata>
<?returndata>
<functionDeclReturndata> {
<returndataTestCode>
}
<?varsPresent>
function coder_returndata_external() external returns (<return_types>) {
<storageVarDefs>
<localVarDefs>
return (<return_values>);
}
</varsPresent>
</returndata>)")
("testFunction", testFunction)
("calldata", m_test == Contract_Test::Contract_Test_CALLDATA_CODER)
("returndata", m_test == Contract_Test::Contract_Test_RETURNDATA_CODER)
("calldataHelperFuncs", calldataHelperFunctions())
("varsPresent", !m_types.str().empty())
("structTypeDecl", structTypeDecl)
("functionDecl", functionDecl)
("functionDeclCalldata", functionDeclCalldata)
("functionDeclReturndata", functionDeclReturndata)
("storageVarDefs", _storageVarDefs)
("localVarDefs", localVarDefs)
("testCode", testCode(_x.invalid_encoding_length()))
("calldataTestCode", testCallDataFunction(_x.invalid_encoding_length()))
("returndataTestCode", testReturnDataFunction())
("return_types", m_types.str())
("return_values", m_argsCoder.str())
.render();
return testBuffer.str();
}
string ProtoConverter::testCode(unsigned _invalidLength)
string ProtoConverter::testCallDataFunction(unsigned _invalidLength)
{
return Whiskers(R"(
uint returnVal = this.coder_public(<parameterNames>);
uint returnVal = this.coder_calldata_public(<argumentNames>);
if (returnVal != 0)
return returnVal;
returnVal = this.coder_external(<parameterNames>);
returnVal = this.coder_calldata_external(<argumentNames>);
if (returnVal != 0)
return uint(200000) + returnVal;
<?atLeastOneVar>
bytes memory argumentEncoding = abi.encode(<parameterNames>);
bytes memory argumentEncoding = abi.encode(<argumentNames>);
returnVal = checkEncodedCall(
this.coder_public.selector,
this.coder_calldata_public.selector,
argumentEncoding,
<invalidLengthFuzz>,
<isRightPadded>
@ -360,7 +449,7 @@ string ProtoConverter::testCode(unsigned _invalidLength)
return returnVal;
returnVal = checkEncodedCall(
this.coder_external.selector,
this.coder_calldata_external.selector,
argumentEncoding,
<invalidLengthFuzz>,
<isRightPadded>
@ -370,26 +459,32 @@ string ProtoConverter::testCode(unsigned _invalidLength)
</atLeastOneVar>
return 0;
)")
("parameterNames", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter))
("argumentNames", m_argsCoder.str())
("invalidLengthFuzz", std::to_string(_invalidLength))
("isRightPadded", isLastDynParamRightPadded() ? "true" : "false")
("atLeastOneVar", m_varCounter > 0)
.render();
}
string ProtoConverter::helperFunctions()
string ProtoConverter::testReturnDataFunction()
{
stringstream helperFuncs;
helperFuncs << R"(
function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) {
if(a.length != b.length)
return false;
for (uint i = 0; i < a.length; i++)
if (a[i] != b[i])
return false;
return true;
}
return Whiskers(R"(
<?varsPresent>
(<varDecl>) = this.coder_returndata_external();
<equality_checks>
</varsPresent>
return 0;
)")
("varsPresent", !m_typedReturn.str().empty())
("varDecl", m_typedReturn.str())
("equality_checks", m_checks.str())
.render();
}
string ProtoConverter::calldataHelperFunctions()
{
stringstream calldataHelperFuncs;
calldataHelperFuncs << R"(
/// Accepts function selector, correct argument encoding, and length of
/// invalid encoding and returns the correct and incorrect abi encoding
/// for calling the function specified by the function selector.
@ -452,19 +547,18 @@ string ProtoConverter::helperFunctions()
if (success == true)
return 400001;
return 0;
}
)";
})";
// These are callee functions that encode from storage, decode to
// memory/calldata and check if decoded value matches storage value
// return true on successful match, false otherwise
helperFuncs << Whiskers(R"(
function coder_public(<parameters_memory>) public pure returns (uint) {
calldataHelperFuncs << Whiskers(R"(
function coder_calldata_public(<parameters_memory>) public pure returns (uint) {
<equality_checks>
return 0;
}
function coder_external(<parameters_calldata>) external pure returns (uint) {
function coder_calldata_external(<parameters_calldata>) external pure returns (uint) {
<equality_checks>
return 0;
}
@ -473,6 +567,25 @@ string ProtoConverter::helperFunctions()
("equality_checks", equalityChecksAsString())
("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL))
.render();
return calldataHelperFuncs.str();
}
string ProtoConverter::commonHelperFunctions()
{
stringstream helperFuncs;
helperFuncs << R"(
/// Compares bytes, returning true if they are equal and false otherwise.
function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) {
if(a.length != b.length)
return false;
for (uint i = 0; i < a.length; i++)
if (a[i] != b[i])
return false;
return true;
}
)";
return helperFuncs.str();
}
@ -481,6 +594,9 @@ void ProtoConverter::visit(Contract const& _x)
string pragmas = R"(pragma solidity >=0.0;
pragma experimental ABIEncoderV2;)";
// Record test spec
m_test = _x.test();
// TODO: Support more than one but less than N state variables
auto storageBuffers = visit(_x.state_vars());
string storageVarDecls = storageBuffers.first;
@ -499,7 +615,7 @@ pragma experimental ABIEncoderV2;)";
ostringstream contractBody;
contractBody << storageVarDecls
<< testFunction
<< helperFunctions();
<< commonHelperFunctions();
m_output << Whiskers(R"(<pragmas>
<contractStart>
<contractBody>
@ -1085,4 +1201,4 @@ string ValueGetterVisitor::bytesArrayValueAsString(unsigned _counter, bool _isHe
_counter,
_isHexLiteral
);
}
}

View File

@ -19,84 +19,118 @@
/**
* Template of the solidity test program generated by this converter is as follows:
*
* pragma solidity >=0.0;
* pragma experimental ABIEncoderV2;
* pragma solidity >=0.0;
* pragma experimental ABIEncoderV2;
*
* contract C {
* // State variable
* string x_0;
* // Test function that is called by the VM.
* function test() public returns (uint) {
* // Local variable
* bytes x_1 = "1";
* x_0 = "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d";
* uint returnVal = this.coder_public(x_0, x_1);
* if (returnVal != 0)
* return returnVal;
* // Since the return codes in the public and external coder functions are identical
* // we offset error code by a fixed amount (200000) for differentiation.
* returnVal = this.coder_external(x_0, x_1);
* if (returnVal != 0)
* return 200000 + returnVal;
* // Encode parameters
* bytes memory argumentEncoding = abi.encode(<parameter_names>);
* returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, <invalidLengthFuzz>);
* // Check if calls to coder_public meet expectations for correctly/incorrectly encoded data.
* if (returnVal != 0)
* return returnVal;
* contract C {
* // State variable
* string sv_0;
* // Test function that is called by the VM.
* // There are 2 variations of this function: one returns
* // the output of test_calldata_coding() and the other
* // returns the output of test_returndata_coding(). The
* // proto field called Test decides which one of the two
* // are chosen for creating a test case.
* function test() public returns (uint) {
* // The protobuf field "Contract.test" decides which of
* // the two internal functions "test_calldata_coding()"
* // and "test_returndata_coding()" are called. Here,
* // we assume that the protobuf field equals "CALLDATA_CODER"
* return this.test_calldata_coding()
* }
*
* returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, <invalidLengthFuzz>);
* // Check if calls to coder_external meet expectations for correctly/incorrectly encoded data.
* // Offset return value to distinguish between failures originating from coder_public and coder_external.
* if (returnVal != 0)
* return uint(200000) + returnVal;
* // Return zero if all checks pass.
* return 0;
* }
* // The following function is generated if the protobuf field
* // "Contract.test" is equal to "RETURNDATA_CODER".
* function test_returndata_coding() internal returns (uint) {
* string memory lv_0, bytes memory lv_1 = test_returndata_external();
* if (lv_0 != 044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d)
* return 1;
* if (lv_1 != "1")
* return 2;
* return 0;
* }
*
* /// Accepts function selector, correct argument encoding, and an invalid encoding length as input.
* /// Returns a non-zero value if either call with correct encoding fails or call with incorrect encoding
* /// succeeds. Returns zero if both calls meet expectation.
* function checkEncodedCall(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz)
* public returns (uint) {
* ...
* }
* // The following function is generated if the protobuf field
* // "Contract.test" is equal to "RETURNDATA_CODER".
* function test_returndata_external() external returns (string memory, bytes memory)
* {
* sv_0 = "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d";
* bytes memory lv_0 = "1";
* return (sv_0, lv_0);
* }
*
* /// Accepts function selector, correct argument encoding, and length of invalid encoding and returns
* /// the correct and incorrect abi encoding for calling the function specified by the function selector.
* function createEncoding(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz)
* internal pure returns (bytes memory, bytes memory) {
* ...
* }
* // The following function is generated if the protobuf field
* // "Contract.test" is equal to "CALLDATA_CODER".
* function test_calldata_coding() internal returns (uint) {
* // Local variable
* bytes lv_1 = "1";
* sv_0 = "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d";
* uint returnVal = this.coder_public(sv_0, lv_1);
* if (returnVal != 0)
* return returnVal;
* // Since the return codes in the public and external coder functions are identical
* // we offset error code by a fixed amount (200000) for differentiation.
* returnVal = this.coder_external(sv_0, lv_1);
* if (returnVal != 0)
* return 200000 + returnVal;
* // Encode parameters
* bytes memory argumentEncoding = abi.encode(<parameter_names>);
* returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, <invalidLengthFuzz>);
* // Check if calls to coder_public meet expectations for correctly/incorrectly encoded data.
* if (returnVal != 0)
* return returnVal;
*
* /// Compares two dynamically sized bytes arrays for equality.
* function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) {
* ...
* }
* returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, <invalidLengthFuzz>);
* // Check if calls to coder_external meet expectations for correctly/incorrectly encoded data.
* // Offset return value to distinguish between failures originating from coder_public and coder_external.
* if (returnVal != 0)
* return uint(200000) + returnVal;
* // Return zero if all checks pass.
* return 0;
* }
*
* // Public function that is called by test() function. Accepts one or more arguments and returns
* // a uint value (zero if abi en/decoding was successful, non-zero otherwise)
* function coder_public(string memory c_0, bytes memory c_1) public pure returns (uint) {
* if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"))
* return 1;
* if (!bytesCompare(c_1, "1"))
* return 2;
* return 0;
* }
* /// Accepts function selector, correct argument encoding, and an invalid encoding length as input.
* /// Returns a non-zero value if either call with correct encoding fails or call with incorrect encoding
* /// succeeds. Returns zero if both calls meet expectation.
* function checkEncodedCall(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz)
* public returns (uint) {
* ...
* }
*
* // External function that is called by test() function. Accepts one or more arguments and returns
* // a uint value (zero if abi en/decoding was successful, non-zero otherwise)
* function coder_external(string calldata c_0, bytes calldata c_1) external pure returns (uint) {
* if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"))
* return 1;
* if (!bytesCompare(c_1, "1"))
* return 2;
* return 0;
* }
* }
* /// Accepts function selector, correct argument encoding, and length of invalid encoding and returns
* /// the correct and incorrect abi encoding for calling the function specified by the function selector.
* function createEncoding(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz)
* internal pure returns (bytes memory, bytes memory) {
* ...
* }
*
* /// Compares two dynamically sized bytes arrays for equality.
* function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) {
* ...
* }
*
* // Public function that is called by test() function. Accepts one or more arguments and returns
* // a uint value (zero if abi en/decoding was successful, non-zero otherwise)
* function coder_public(string memory c_0, bytes memory c_1) public pure returns (uint) {
* if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"))
* return 1;
* if (!bytesCompare(c_1, "1"))
* return 2;
* return 0;
* }
*
* // External function that is called by test() function. Accepts one or more arguments and returns
* // a uint value (zero if abi en/decoding was successful, non-zero otherwise)
* function coder_external(string calldata c_0, bytes calldata c_1) external pure returns (uint) {
* if (!bytesCompare(bytes(c_0), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"))
* return 1;
* if (!bytesCompare(c_1, "1"))
* return 2;
* return 0;
* }
* }
*/
namespace dev
{
namespace test
@ -242,6 +276,20 @@ private:
Delimiter _delimiter = Delimiter::ADD
);
/// Append types to typed stream used by returndata coders.
void appendTypes(
bool _isValueType,
std::string const& _typeString,
Delimiter _delimiter
);
/// Append typed return value.
void appendTypedReturn(
bool _isValueType,
std::string const& _typeString,
Delimiter _delimiter
);
/// Returns a Solidity variable declaration statement
/// @param _type: string containing Solidity type of the
/// variable to be declared.
@ -262,11 +310,17 @@ private:
/// Return comma separated typed function parameters as string
std::string typedParametersAsString(CalleeType _calleeType);
/// Return Solidity helper functions as string
std::string helperFunctions();
/// Return commonly used Solidity helper functions as string
std::string commonHelperFunctions();
/// Return top-level test code as string
std::string testCode(unsigned _invalidLength);
/// Return helper functions used to test calldata coding
std::string calldataHelperFunctions();
/// Return top-level calldata coder test function as string
std::string testCallDataFunction(unsigned _invalidLength);
/// Return top-level returndata coder test function as string
std::string testReturnDataFunction();
/// Return the next variable count that is used for
/// variable naming.
@ -276,15 +330,30 @@ private:
}
/// Return a pair of names for Solidity variable and the same variable when
/// passed as a function parameter.
static std::pair<std::string, std::string> newVarNames(unsigned _varCounter)
/// passed either as a function parameter or used to store the tuple
/// returned from a function.
/// @param _varCounter: name suffix
/// @param _stateVar: predicate that is true for state variables, false otherwise
std::pair<std::string, std::string> newVarNames(unsigned _varCounter, bool _stateVar)
{
std::string varName = _stateVar ? s_stateVarNamePrefix : s_localVarNamePrefix;
return std::make_pair(
s_varNamePrefix + std::to_string(_varCounter),
s_paramNamePrefix + std::to_string(_varCounter)
varName + std::to_string(_varCounter),
paramName() + std::to_string(_varCounter)
);
}
std::string paramName()
{
switch (m_test)
{
case Contract_Test::Contract_Test_CALLDATA_CODER:
return s_paramNamePrefix;
case Contract_Test::Contract_Test_RETURNDATA_CODER:
return s_localVarNamePrefix;
}
}
/// Checks if the last dynamically encoded Solidity type is right
/// padded, returning true if it is and false otherwise.
bool isLastDynParamRightPadded()
@ -303,6 +372,12 @@ private:
/// Contains typed parameter list to be passed to callee functions
std::ostringstream m_typedParamsExternal;
std::ostringstream m_typedParamsPublic;
/// Contains type stream to be used in returndata coder function
/// signature
std::ostringstream m_types;
std::ostringstream m_typedReturn;
/// Argument names to be passed to coder functions
std::ostringstream m_argsCoder;
/// Predicate that is true if we are in contract scope
bool m_isStateVar;
unsigned m_counter;
@ -316,9 +391,12 @@ private:
/// Struct counter
unsigned m_structCounter;
unsigned m_numStructsAdded;
/// Enum stating abiv2 coder to be tested
Contract_Test m_test;
/// Prefixes for declared and parameterized variable names
static auto constexpr s_varNamePrefix = "x_";
static auto constexpr s_paramNamePrefix = "c_";
static auto constexpr s_localVarNamePrefix = "lv_";
static auto constexpr s_stateVarNamePrefix = "sv_";
static auto constexpr s_paramNamePrefix = "p_";
};
/// Visitor interface for Solidity protobuf types.
@ -857,4 +935,4 @@ public:
};
}
}
}
}