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 { message Contract {
enum Test {
CALLDATA_CODER = 1;
RETURNDATA_CODER = 2;
}
required VarDecl state_vars = 1; required VarDecl state_vars = 1;
required TestFunction testfunction = 2; required TestFunction testfunction = 2;
required Test test = 3;
} }
package dev.test.abiv2fuzzer; package dev.test.abiv2fuzzer;

View File

@ -114,7 +114,14 @@ template <typename T>
pair<string, string> ProtoConverter::processType(T const& _type, bool _isValueType) pair<string, string> ProtoConverter::processType(T const& _type, bool _isValueType)
{ {
ostringstream local, global; 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{}; string location{};
if (!m_isStateVar && !_isValueType) if (!m_isStateVar && !_isValueType)
location = "memory"; location = "memory";
@ -177,6 +184,16 @@ pair<string, string> ProtoConverter::varDecl(
_paramName, _paramName,
((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD) ((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 // Update dyn param only if necessary
if (tVisitor.isLastDynParamRightPadded()) 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. // Adds the qualifier "calldata" to non-value parameter of an external function.
void ProtoConverter::appendTypedParamsExternal( void ProtoConverter::appendTypedParamsExternal(
bool _isValueType, 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) string ProtoConverter::visit(TestFunction const& _x, string const& _storageVarDefs)
{ {
// TODO: Support more than one but less than N local variables // 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; string localVarDefs = localVarBuffers.second;
ostringstream testBuffer; 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> testBuffer << Whiskers(R"(<structTypeDecl>
<functionDecl> { <testFunction>
<?calldata>
<functionDeclCalldata> {
<storageVarDefs> <storageVarDefs>
<localVarDefs> <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) ("structTypeDecl", structTypeDecl)
("functionDecl", functionDecl) ("functionDeclCalldata", functionDeclCalldata)
("functionDeclReturndata", functionDeclReturndata)
("storageVarDefs", _storageVarDefs) ("storageVarDefs", _storageVarDefs)
("localVarDefs", localVarDefs) ("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(); .render();
return testBuffer.str(); return testBuffer.str();
} }
string ProtoConverter::testCode(unsigned _invalidLength) string ProtoConverter::testCallDataFunction(unsigned _invalidLength)
{ {
return Whiskers(R"( return Whiskers(R"(
uint returnVal = this.coder_public(<parameterNames>); uint returnVal = this.coder_calldata_public(<argumentNames>);
if (returnVal != 0) if (returnVal != 0)
return returnVal; return returnVal;
returnVal = this.coder_external(<parameterNames>); returnVal = this.coder_calldata_external(<argumentNames>);
if (returnVal != 0) if (returnVal != 0)
return uint(200000) + returnVal; return uint(200000) + returnVal;
<?atLeastOneVar> <?atLeastOneVar>
bytes memory argumentEncoding = abi.encode(<parameterNames>); bytes memory argumentEncoding = abi.encode(<argumentNames>);
returnVal = checkEncodedCall( returnVal = checkEncodedCall(
this.coder_public.selector, this.coder_calldata_public.selector,
argumentEncoding, argumentEncoding,
<invalidLengthFuzz>, <invalidLengthFuzz>,
<isRightPadded> <isRightPadded>
@ -360,7 +449,7 @@ string ProtoConverter::testCode(unsigned _invalidLength)
return returnVal; return returnVal;
returnVal = checkEncodedCall( returnVal = checkEncodedCall(
this.coder_external.selector, this.coder_calldata_external.selector,
argumentEncoding, argumentEncoding,
<invalidLengthFuzz>, <invalidLengthFuzz>,
<isRightPadded> <isRightPadded>
@ -370,26 +459,32 @@ string ProtoConverter::testCode(unsigned _invalidLength)
</atLeastOneVar> </atLeastOneVar>
return 0; return 0;
)") )")
("parameterNames", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter)) ("argumentNames", m_argsCoder.str())
("invalidLengthFuzz", std::to_string(_invalidLength)) ("invalidLengthFuzz", std::to_string(_invalidLength))
("isRightPadded", isLastDynParamRightPadded() ? "true" : "false") ("isRightPadded", isLastDynParamRightPadded() ? "true" : "false")
("atLeastOneVar", m_varCounter > 0) ("atLeastOneVar", m_varCounter > 0)
.render(); .render();
} }
string ProtoConverter::helperFunctions() string ProtoConverter::testReturnDataFunction()
{ {
stringstream helperFuncs; return Whiskers(R"(
helperFuncs << R"( <?varsPresent>
function bytesCompare(bytes memory a, bytes memory b) internal pure returns (bool) { (<varDecl>) = this.coder_returndata_external();
if(a.length != b.length) <equality_checks>
return false; </varsPresent>
for (uint i = 0; i < a.length; i++) return 0;
if (a[i] != b[i]) )")
return false; ("varsPresent", !m_typedReturn.str().empty())
return true; ("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 /// Accepts function selector, correct argument encoding, and length of
/// invalid encoding and returns the correct and incorrect abi encoding /// invalid encoding and returns the correct and incorrect abi encoding
/// for calling the function specified by the function selector. /// for calling the function specified by the function selector.
@ -452,19 +547,18 @@ string ProtoConverter::helperFunctions()
if (success == true) if (success == true)
return 400001; return 400001;
return 0; return 0;
} })";
)";
// These are callee functions that encode from storage, decode to // These are callee functions that encode from storage, decode to
// memory/calldata and check if decoded value matches storage value // memory/calldata and check if decoded value matches storage value
// return true on successful match, false otherwise // return true on successful match, false otherwise
helperFuncs << Whiskers(R"( calldataHelperFuncs << Whiskers(R"(
function coder_public(<parameters_memory>) public pure returns (uint) { function coder_calldata_public(<parameters_memory>) public pure returns (uint) {
<equality_checks> <equality_checks>
return 0; return 0;
} }
function coder_external(<parameters_calldata>) external pure returns (uint) { function coder_calldata_external(<parameters_calldata>) external pure returns (uint) {
<equality_checks> <equality_checks>
return 0; return 0;
} }
@ -473,6 +567,25 @@ string ProtoConverter::helperFunctions()
("equality_checks", equalityChecksAsString()) ("equality_checks", equalityChecksAsString())
("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL)) ("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL))
.render(); .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(); return helperFuncs.str();
} }
@ -481,6 +594,9 @@ void ProtoConverter::visit(Contract const& _x)
string pragmas = R"(pragma solidity >=0.0; string pragmas = R"(pragma solidity >=0.0;
pragma experimental ABIEncoderV2;)"; pragma experimental ABIEncoderV2;)";
// Record test spec
m_test = _x.test();
// TODO: Support more than one but less than N state variables // TODO: Support more than one but less than N state variables
auto storageBuffers = visit(_x.state_vars()); auto storageBuffers = visit(_x.state_vars());
string storageVarDecls = storageBuffers.first; string storageVarDecls = storageBuffers.first;
@ -499,7 +615,7 @@ pragma experimental ABIEncoderV2;)";
ostringstream contractBody; ostringstream contractBody;
contractBody << storageVarDecls contractBody << storageVarDecls
<< testFunction << testFunction
<< helperFunctions(); << commonHelperFunctions();
m_output << Whiskers(R"(<pragmas> m_output << Whiskers(R"(<pragmas>
<contractStart> <contractStart>
<contractBody> <contractBody>
@ -1085,4 +1201,4 @@ string ValueGetterVisitor::bytesArrayValueAsString(unsigned _counter, bool _isHe
_counter, _counter,
_isHexLiteral _isHexLiteral
); );
} }

View File

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