mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			804 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			804 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <regex>
 | 
						|
#include <numeric>
 | 
						|
#include <boost/range/adaptor/reversed.hpp>
 | 
						|
#include <test/tools/ossfuzz/protoToAbiV2.h>
 | 
						|
#include <libdevcore/StringUtils.h>
 | 
						|
#include <libdevcore/Whiskers.h>
 | 
						|
#include <liblangutil/Exceptions.h>
 | 
						|
 | 
						|
using namespace std;
 | 
						|
using namespace dev;
 | 
						|
using namespace dev::test::abiv2fuzzer;
 | 
						|
 | 
						|
// Create a new variable declaration and append said variable to function parameter lists
 | 
						|
// of coder functions.
 | 
						|
// Declared name is x_<i>; parameterized name is c_<i>
 | 
						|
// where <i> is a monotonically increasing integer.
 | 
						|
void ProtoConverter::createDeclAndParamList(
 | 
						|
	std::string const& _type,
 | 
						|
	DataType _dataType,
 | 
						|
	std::string& _varName,
 | 
						|
	std::string& _paramName
 | 
						|
)
 | 
						|
{
 | 
						|
	auto varNames = newVarNames(getNextVarCounter());
 | 
						|
	_varName = varNames.first;
 | 
						|
	_paramName = varNames.second;
 | 
						|
 | 
						|
	// Declare array
 | 
						|
	appendVarDeclToOutput(_type, _varName, getQualifier(_dataType));
 | 
						|
 | 
						|
	// Add typed params for calling public and external functions with said type
 | 
						|
	appendTypedParams(
 | 
						|
		CalleeType::PUBLIC,
 | 
						|
		isValueType(_dataType),
 | 
						|
		_type,
 | 
						|
		_paramName,
 | 
						|
		((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD)
 | 
						|
	);
 | 
						|
	appendTypedParams(
 | 
						|
		CalleeType::EXTERNAL,
 | 
						|
		isValueType(_dataType),
 | 
						|
		_type,
 | 
						|
		_paramName,
 | 
						|
		((m_varCounter == 1) ? Delimiter::SKIP : Delimiter::ADD)
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visitArrayType(std::string const& _baseType, ArrayType const& _x)
 | 
						|
{
 | 
						|
	std::string type = arrayTypeAsString(_baseType, _x);
 | 
						|
	std::string varName, paramName;
 | 
						|
	createDeclAndParamList(type, DataType::ARRAY, varName, paramName);
 | 
						|
	// Resize-initialize array and add checks
 | 
						|
	resizeInitArray(_x, _baseType, varName, paramName);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visitType(
 | 
						|
	DataType _dataType,
 | 
						|
	std::string const& _type,
 | 
						|
	std::string const& _value
 | 
						|
)
 | 
						|
{
 | 
						|
	std::string varName, paramName;
 | 
						|
	createDeclAndParamList(_type, _dataType, varName, paramName);
 | 
						|
	addCheckedVarDef(_dataType, varName, paramName, _value);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::appendVarDeclToOutput(
 | 
						|
	std::string const& _type,
 | 
						|
	std::string const& _varName,
 | 
						|
	std::string const& _qualifier
 | 
						|
)
 | 
						|
{
 | 
						|
	// One level of indentation for state variable declarations
 | 
						|
	// Two levels of indentation for local variable declarations
 | 
						|
	m_output << Whiskers(R"(
 | 
						|
	<?isLocalVar>	</isLocalVar><type><?qual> <qualifier></qual> <varName>;)"
 | 
						|
	)
 | 
						|
		("isLocalVar", !m_isStateVar)
 | 
						|
		("type", _type)
 | 
						|
		("qual", !_qualifier.empty())
 | 
						|
		("qualifier", _qualifier)
 | 
						|
		("varName", _varName)
 | 
						|
		.render();
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::appendChecks(
 | 
						|
	DataType _type,
 | 
						|
	std::string const& _varName,
 | 
						|
	std::string const& _rhs
 | 
						|
)
 | 
						|
{
 | 
						|
	std::string check = {};
 | 
						|
	switch (_type)
 | 
						|
	{
 | 
						|
	case DataType::STRING:
 | 
						|
		check = Whiskers(R"(!bytesCompare(bytes(<varName>), <value>))")
 | 
						|
			("varName", _varName)
 | 
						|
			("value", _rhs)
 | 
						|
			.render();
 | 
						|
		break;
 | 
						|
	case DataType::BYTES:
 | 
						|
		check = Whiskers(R"(!bytesCompare(<varName>, <value>))")
 | 
						|
			("varName", _varName)
 | 
						|
			("value", _rhs)
 | 
						|
			.render();
 | 
						|
		break;
 | 
						|
	case DataType::VALUE:
 | 
						|
		check = Whiskers(R"(<varName> != <value>)")
 | 
						|
			("varName", _varName)
 | 
						|
			("value", _rhs)
 | 
						|
			.render();
 | 
						|
		break;
 | 
						|
	case DataType::ARRAY:
 | 
						|
		solUnimplemented("Proto ABIv2 fuzzer: Invalid data type.");
 | 
						|
	}
 | 
						|
 | 
						|
	// Each (failing) check returns a unique value to simplify debugging.
 | 
						|
	m_checks << Whiskers(R"(
 | 
						|
		if (<check>) return <returnVal>;)"
 | 
						|
	)
 | 
						|
		("check", check)
 | 
						|
		("returnVal", std::to_string(m_returnValue++))
 | 
						|
		.render();
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::addVarDef(std::string const& _varName, std::string const& _rhs)
 | 
						|
{
 | 
						|
	std::string varDefString = Whiskers(R"(
 | 
						|
		<varName> = <rhs>;)"
 | 
						|
		)
 | 
						|
		("varName", _varName)
 | 
						|
		("rhs", _rhs)
 | 
						|
		.render();
 | 
						|
 | 
						|
	// State variables cannot be assigned in contract-scope
 | 
						|
	// Therefore, we buffer their assignments and
 | 
						|
	// render them in function scope later.
 | 
						|
	if (m_isStateVar)
 | 
						|
		m_statebuffer << varDefString;
 | 
						|
	else
 | 
						|
		m_output << varDefString;
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::addCheckedVarDef(
 | 
						|
	DataType _type,
 | 
						|
	std::string const& _varName,
 | 
						|
	std::string const& _paramName,
 | 
						|
	std::string const& _rhs)
 | 
						|
{
 | 
						|
	addVarDef(_varName, _rhs);
 | 
						|
	appendChecks(_type, _paramName, _rhs);
 | 
						|
}
 | 
						|
 | 
						|
// Runtime check for array length.
 | 
						|
void ProtoConverter::checkResizeOp(std::string const& _paramName, unsigned _len)
 | 
						|
{
 | 
						|
	appendChecks(DataType::VALUE, _paramName + ".length", std::to_string(_len));
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::boolValueAsString(unsigned _counter)
 | 
						|
{
 | 
						|
	return ((_counter % 2) ? "true" : "false");
 | 
						|
}
 | 
						|
 | 
						|
/* Input(s)
 | 
						|
 *   - Unsigned integer to be hashed
 | 
						|
 *   - Width of desired uint value
 | 
						|
 * Processing
 | 
						|
 *   - Take hash of first parameter and mask it with the max unsigned value for given bit width
 | 
						|
 * Output
 | 
						|
 *   - string representation of uint value
 | 
						|
 */
 | 
						|
std::string ProtoConverter::uintValueAsString(unsigned _width, unsigned _counter)
 | 
						|
{
 | 
						|
	solAssert(
 | 
						|
		(_width % 8 == 0),
 | 
						|
		"Proto ABIv2 Fuzzer: Unsigned integer width is not a multiple of 8"
 | 
						|
	);
 | 
						|
	return maskUnsignedIntToHex(_counter, _width/4);
 | 
						|
}
 | 
						|
 | 
						|
/* Input(s)
 | 
						|
 *   - counter to be hashed to derive a value for Integer type
 | 
						|
 *   - Width of desired int value
 | 
						|
 * Processing
 | 
						|
 *   - Take hash of first parameter and mask it with the max signed value for given bit width
 | 
						|
 * Output
 | 
						|
 *   - string representation of int value
 | 
						|
 */
 | 
						|
std::string ProtoConverter::intValueAsString(unsigned _width, unsigned _counter)
 | 
						|
{
 | 
						|
	solAssert(
 | 
						|
		(_width % 8 == 0),
 | 
						|
		"Proto ABIv2 Fuzzer: Signed integer width is not a multiple of 8"
 | 
						|
	);
 | 
						|
	return maskUnsignedIntToHex(_counter, ((_width/4) - 1));
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::addressValueAsString(unsigned _counter)
 | 
						|
{
 | 
						|
	return Whiskers(R"(address(<value>))")
 | 
						|
		("value", uintValueAsString(160, _counter))
 | 
						|
		.render();
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _counter)
 | 
						|
{
 | 
						|
	solAssert(
 | 
						|
		(_width >= 1 && _width <= 32),
 | 
						|
		"Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32"
 | 
						|
	);
 | 
						|
	// Masked value must contain twice the number of nibble "f"'s as _width
 | 
						|
	unsigned numMaskNibbles = _width * 2;
 | 
						|
	// Start position of substring equals totalHexStringLength - numMaskNibbles
 | 
						|
	// totalHexStringLength = 64 + 2 = 66
 | 
						|
	// e.g., 0x12345678901234567890123456789012 is a total of 66 characters
 | 
						|
	//      |---------------------^-----------|
 | 
						|
	//      <--- start position---><--numMask->
 | 
						|
	//      <-----------total length --------->
 | 
						|
	// Note: This assumes that maskUnsignedIntToHex() invokes toHex(..., HexPrefix::Add)
 | 
						|
	unsigned startPos = 66 - numMaskNibbles;
 | 
						|
 | 
						|
	// Extracts the least significant numMaskNibbles from the result of "maskUnsignedIntToHex",
 | 
						|
	// and replaces "0x" with "hex\"...\"" string.
 | 
						|
	// This is needed because solidity interprets a 20-byte 0x prefixed hex literal as an address
 | 
						|
	// payable type.
 | 
						|
	return Whiskers(R"(hex"<value>")")
 | 
						|
		("value", maskUnsignedIntToHex(_counter, numMaskNibbles).substr(startPos, numMaskNibbles))
 | 
						|
		.render();
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::integerValueAsString(bool _sign, unsigned _width, unsigned _counter)
 | 
						|
{
 | 
						|
	if (_sign)
 | 
						|
		return intValueAsString(_width, _counter);
 | 
						|
	else
 | 
						|
		return uintValueAsString(_width, _counter);
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::bytesArrayTypeAsString(DynamicByteArrayType const& _x)
 | 
						|
{
 | 
						|
	switch (_x.type())
 | 
						|
	{
 | 
						|
	case DynamicByteArrayType::BYTES:
 | 
						|
		return "bytes";
 | 
						|
	case DynamicByteArrayType::STRING:
 | 
						|
		return "string";
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::structTypeAsString(StructType const&)
 | 
						|
{
 | 
						|
	// TODO: Implement this
 | 
						|
	return {};
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(BoolType const&)
 | 
						|
{
 | 
						|
	visitType(
 | 
						|
		DataType::VALUE,
 | 
						|
		getBoolTypeAsString(),
 | 
						|
		boolValueAsString(getNextCounter())
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(IntegerType const& _x)
 | 
						|
{
 | 
						|
	visitType(
 | 
						|
		DataType::VALUE,
 | 
						|
		getIntTypeAsString(_x),
 | 
						|
		integerValueAsString(isIntSigned(_x), getIntWidth(_x), getNextCounter())
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(AddressType const& _x)
 | 
						|
{
 | 
						|
	visitType(
 | 
						|
		DataType::VALUE,
 | 
						|
		getAddressTypeAsString(_x),
 | 
						|
		addressValueAsString(getNextCounter())
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(FixedByteType const& _x)
 | 
						|
{
 | 
						|
	visitType(
 | 
						|
		DataType::VALUE,
 | 
						|
		getFixedByteTypeAsString(_x),
 | 
						|
		fixedByteValueAsString(getFixedByteWidth(_x), getNextCounter())
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(ValueType const& _x)
 | 
						|
{
 | 
						|
	switch (_x.value_type_oneof_case())
 | 
						|
	{
 | 
						|
		case ValueType::kInty:
 | 
						|
			visit(_x.inty());
 | 
						|
			break;
 | 
						|
		case ValueType::kByty:
 | 
						|
			visit(_x.byty());
 | 
						|
			break;
 | 
						|
		case ValueType::kAdty:
 | 
						|
			visit(_x.adty());
 | 
						|
			break;
 | 
						|
		case ValueType::kBoolty:
 | 
						|
			visit(_x.boolty());
 | 
						|
			break;
 | 
						|
		case ValueType::VALUE_TYPE_ONEOF_NOT_SET:
 | 
						|
			break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(DynamicByteArrayType const& _x)
 | 
						|
{
 | 
						|
	visitType(
 | 
						|
		(_x.type() == DynamicByteArrayType::BYTES) ? DataType::BYTES : DataType::STRING,
 | 
						|
		bytesArrayTypeAsString(_x),
 | 
						|
		bytesArrayValueAsString(getNextCounter())
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
// TODO: Implement struct visitor
 | 
						|
void ProtoConverter::visit(StructType const&)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::arrayDimInfoAsString(ArrayDimensionInfo const& _x)
 | 
						|
{
 | 
						|
	return Whiskers(R"([<?isStatic><length></isStatic>])")
 | 
						|
		("isStatic", _x.is_static())
 | 
						|
		("length", std::to_string(getStaticArrayLengthFromFuzz(_x.length())))
 | 
						|
		.render();
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::arrayDimensionsAsStringVector(
 | 
						|
	ArrayType const& _x,
 | 
						|
	std::vector<std::string>& _vecOfStr)
 | 
						|
{
 | 
						|
	solAssert(_x.info_size() > 0, "Proto ABIv2 Fuzzer: Array dimensions empty.");
 | 
						|
	for (auto const& dim: _x.info())
 | 
						|
		_vecOfStr.push_back(arrayDimInfoAsString(dim));
 | 
						|
}
 | 
						|
 | 
						|
ProtoConverter::VecOfBoolUnsigned ProtoConverter::arrayDimensionsAsPairVector(
 | 
						|
	ArrayType const& _x
 | 
						|
)
 | 
						|
{
 | 
						|
	VecOfBoolUnsigned arrayDimsPairVector = {};
 | 
						|
	for (auto const& dim: _x.info())
 | 
						|
		arrayDimsPairVector.push_back(arrayDimInfoAsPair(dim));
 | 
						|
	solAssert(!arrayDimsPairVector.empty(), "Proto ABIv2 Fuzzer: Array dimensions empty.");
 | 
						|
	return arrayDimsPairVector;
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::getValueByBaseType(ArrayType const& _x)
 | 
						|
{
 | 
						|
	switch (_x.base_type_oneof_case())
 | 
						|
	{
 | 
						|
	case ArrayType::kInty:
 | 
						|
		return integerValueAsString(isIntSigned(_x.inty()), getIntWidth(_x.inty()), getNextCounter());
 | 
						|
	case ArrayType::kByty:
 | 
						|
		return fixedByteValueAsString(getFixedByteWidth(_x.byty()), getNextCounter());
 | 
						|
	case ArrayType::kAdty:
 | 
						|
		return addressValueAsString(getNextCounter());
 | 
						|
	case ArrayType::kBoolty:
 | 
						|
		return boolValueAsString(getNextCounter());
 | 
						|
	case ArrayType::kDynbytesty:
 | 
						|
		return bytesArrayValueAsString(getNextCounter());
 | 
						|
	// TODO: Implement structs.
 | 
						|
	case ArrayType::kStty:
 | 
						|
	case ArrayType::BASE_TYPE_ONEOF_NOT_SET:
 | 
						|
		solAssert(false, "Proto ABIv2 fuzzer: Invalid array base type");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
ProtoConverter::DataType ProtoConverter::getDataTypeByBaseType(ArrayType const& _x)
 | 
						|
{
 | 
						|
	switch (_x.base_type_oneof_case())
 | 
						|
	{
 | 
						|
	case ArrayType::kInty:
 | 
						|
	case ArrayType::kByty:
 | 
						|
	case ArrayType::kAdty:
 | 
						|
	case ArrayType::kBoolty:
 | 
						|
		return DataType::VALUE;
 | 
						|
	case ArrayType::kDynbytesty:
 | 
						|
		return getDataTypeOfDynBytesType(_x.dynbytesty());
 | 
						|
	case ArrayType::kStty:
 | 
						|
	case ArrayType::BASE_TYPE_ONEOF_NOT_SET:
 | 
						|
		solUnimplemented("Proto ABIv2 fuzzer: Invalid array base type");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Adds a resize operation for a given dimension of type `_type` and expression referenced
 | 
						|
// by `_var`. `_isStatic` is true for statically sized dimensions, false otherwise.
 | 
						|
// `_arrayLen` is equal to length of statically sized array dimension. For dynamically
 | 
						|
// sized dimension, we use `getDynArrayLengthFromFuzz()` and a monotonically increasing
 | 
						|
// counter to obtain actual length. Function returns dimension length.
 | 
						|
unsigned ProtoConverter::resizeDimension(
 | 
						|
	bool _isStatic,
 | 
						|
	unsigned _arrayLen,
 | 
						|
	std::string const& _var,
 | 
						|
	std::string const& _param,
 | 
						|
	std::string const& _type
 | 
						|
)
 | 
						|
{
 | 
						|
	unsigned length;
 | 
						|
	if (_isStatic)
 | 
						|
		length = _arrayLen;
 | 
						|
	else
 | 
						|
	{
 | 
						|
		length = getDynArrayLengthFromFuzz(_arrayLen, getNextCounter());
 | 
						|
 | 
						|
		// If local var, new T(l);
 | 
						|
		// Else, l;
 | 
						|
		std::string lhs, rhs;
 | 
						|
		if (m_isStateVar)
 | 
						|
		{
 | 
						|
			lhs = _var + ".length";
 | 
						|
			rhs = Whiskers(R"(<length>)")
 | 
						|
				("length", std::to_string(length))
 | 
						|
				.render();
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			lhs = _var;
 | 
						|
			rhs = Whiskers(R"(new <type>(<length>))")
 | 
						|
				("type", _type)
 | 
						|
				("length", std::to_string(length))
 | 
						|
				.render();
 | 
						|
		}
 | 
						|
		// If local var, x = new T(l);
 | 
						|
		// Else, x.length = l;
 | 
						|
		addVarDef(lhs, rhs);
 | 
						|
	}
 | 
						|
 | 
						|
	// if (c.length != l)
 | 
						|
	checkResizeOp(_param, length);
 | 
						|
	return length;
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::resizeHelper(
 | 
						|
	ArrayType const& _x,
 | 
						|
	std::vector<std::string> _arrStrVec,
 | 
						|
	VecOfBoolUnsigned _arrInfoVec,
 | 
						|
	std::string const& _varName,
 | 
						|
	std::string const& _paramName
 | 
						|
)
 | 
						|
{
 | 
						|
	// Initialize value expressions if we have arrived at leaf node,
 | 
						|
	// (depth-first) recurse otherwise.
 | 
						|
	if (_arrInfoVec.empty())
 | 
						|
	{
 | 
						|
		// expression name is _var
 | 
						|
		// value is a value of base type
 | 
						|
		std::string value = getValueByBaseType(_x);
 | 
						|
		// add assignment and check
 | 
						|
		DataType dataType = getDataTypeByBaseType(_x);
 | 
						|
		addCheckedVarDef(dataType, _varName, _paramName, value);
 | 
						|
	}
 | 
						|
	else
 | 
						|
	{
 | 
						|
		auto& dim = _arrInfoVec.back();
 | 
						|
 | 
						|
		std::string type = std::accumulate(
 | 
						|
			_arrStrVec.begin(),
 | 
						|
			_arrStrVec.end(),
 | 
						|
			std::string("")
 | 
						|
		);
 | 
						|
		unsigned length = resizeDimension(dim.first, dim.second, _varName, _paramName, type);
 | 
						|
		// Recurse one level dimension down.
 | 
						|
		_arrStrVec.pop_back();
 | 
						|
		_arrInfoVec.pop_back();
 | 
						|
		for (unsigned i = 0; i < length; i++)
 | 
						|
			resizeHelper(
 | 
						|
				_x,
 | 
						|
				_arrStrVec,
 | 
						|
				_arrInfoVec,
 | 
						|
				_varName + "[" + std::to_string(i) + "]",
 | 
						|
				_paramName + "[" + std::to_string(i) + "]"
 | 
						|
			);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// This function takes care of properly resizing and initializing ArrayType.
 | 
						|
// In parallel, it adds runtime checks on array bound and values.
 | 
						|
void ProtoConverter::resizeInitArray(
 | 
						|
	ArrayType const& _x,
 | 
						|
	std::string const& _baseType,
 | 
						|
	std::string const& _varName,
 | 
						|
	std::string const& _paramName
 | 
						|
)
 | 
						|
{
 | 
						|
	VecOfBoolUnsigned arrInfoVec = arrayDimensionsAsPairVector(_x);
 | 
						|
	std::vector<std::string> arrStrVec = {_baseType};
 | 
						|
	arrayDimensionsAsStringVector(_x, arrStrVec);
 | 
						|
	resizeHelper(_x, arrStrVec, arrInfoVec, _varName, _paramName);
 | 
						|
}
 | 
						|
 | 
						|
// Returns array type from it's base type (e.g., int8) and array dimensions info contained in
 | 
						|
// ArrayType.
 | 
						|
std::string ProtoConverter::arrayTypeAsString(std::string const& _baseType, ArrayType const& _x)
 | 
						|
{
 | 
						|
	std::vector<std::string> typeStringVec = {_baseType};
 | 
						|
	arrayDimensionsAsStringVector(_x, typeStringVec);
 | 
						|
 | 
						|
	return std::accumulate(
 | 
						|
		typeStringVec.begin(),
 | 
						|
		typeStringVec.end(),
 | 
						|
		std::string("")
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(ArrayType const& _x)
 | 
						|
{
 | 
						|
	// Bail out if input contains too few or too many dimensions.
 | 
						|
	if (_x.info_size() == 0 || _x.info_size() > (int)s_maxArrayDimensions)
 | 
						|
		return;
 | 
						|
 | 
						|
	string baseType = {};
 | 
						|
	switch (_x.base_type_oneof_case())
 | 
						|
	{
 | 
						|
	case ArrayType::kInty:
 | 
						|
		baseType = getIntTypeAsString(_x.inty());
 | 
						|
		break;
 | 
						|
	case ArrayType::kByty:
 | 
						|
		baseType = getFixedByteTypeAsString(_x.byty());
 | 
						|
		break;
 | 
						|
	case ArrayType::kAdty:
 | 
						|
		baseType = getAddressTypeAsString(_x.adty());
 | 
						|
		break;
 | 
						|
	case ArrayType::kBoolty:
 | 
						|
		baseType = getBoolTypeAsString();
 | 
						|
		break;
 | 
						|
	case ArrayType::kDynbytesty:
 | 
						|
		baseType = bytesArrayTypeAsString(_x.dynbytesty());
 | 
						|
		break;
 | 
						|
	case ArrayType::kStty:
 | 
						|
	case ArrayType::BASE_TYPE_ONEOF_NOT_SET:
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	visitArrayType(baseType, _x);
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(NonValueType const& _x)
 | 
						|
{
 | 
						|
	switch (_x.nonvalue_type_oneof_case())
 | 
						|
	{
 | 
						|
	case NonValueType::kDynbytearray:
 | 
						|
		visit(_x.dynbytearray());
 | 
						|
		break;
 | 
						|
	case NonValueType::kArrtype:
 | 
						|
		visit(_x.arrtype());
 | 
						|
		break;
 | 
						|
	case NonValueType::kStype:
 | 
						|
		visit(_x.stype());
 | 
						|
		break;
 | 
						|
	case NonValueType::NONVALUE_TYPE_ONEOF_NOT_SET:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(Type const& _x)
 | 
						|
{
 | 
						|
	switch (_x.type_oneof_case())
 | 
						|
	{
 | 
						|
	case Type::kVtype:
 | 
						|
		visit(_x.vtype());
 | 
						|
		break;
 | 
						|
	case Type::kNvtype:
 | 
						|
		visit(_x.nvtype());
 | 
						|
		break;
 | 
						|
	case Type::TYPE_ONEOF_NOT_SET:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(VarDecl const& _x)
 | 
						|
{
 | 
						|
	visit(_x.type());
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::equalityChecksAsString()
 | 
						|
{
 | 
						|
	return m_checks.str();
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::delimiterToString(Delimiter _delimiter)
 | 
						|
{
 | 
						|
	switch (_delimiter)
 | 
						|
	{
 | 
						|
	case Delimiter::ADD:
 | 
						|
		return ", ";
 | 
						|
	case Delimiter::SKIP:
 | 
						|
		return "";
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* When a new variable is declared, we can invoke this function
 | 
						|
 * to prepare the typed param list to be passed to callee functions.
 | 
						|
 * We independently prepare this list for "public" and "external"
 | 
						|
 * callee functions.
 | 
						|
 */
 | 
						|
void ProtoConverter::appendTypedParams(
 | 
						|
	CalleeType _calleeType,
 | 
						|
	bool _isValueType,
 | 
						|
	std::string const& _typeString,
 | 
						|
	std::string const& _varName,
 | 
						|
	Delimiter _delimiter
 | 
						|
)
 | 
						|
{
 | 
						|
	switch (_calleeType)
 | 
						|
	{
 | 
						|
	case CalleeType::PUBLIC:
 | 
						|
		appendTypedParamsPublic(_isValueType, _typeString, _varName, _delimiter);
 | 
						|
		break;
 | 
						|
	case CalleeType::EXTERNAL:
 | 
						|
		appendTypedParamsExternal(_isValueType, _typeString, _varName, _delimiter);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Adds the qualifier "calldata" to non-value parameter of an external function.
 | 
						|
void ProtoConverter::appendTypedParamsExternal(
 | 
						|
	bool _isValueType,
 | 
						|
    std::string const& _typeString,
 | 
						|
    std::string const& _varName,
 | 
						|
    Delimiter _delimiter
 | 
						|
)
 | 
						|
{
 | 
						|
	std::string qualifiedTypeString = (
 | 
						|
		_isValueType ?
 | 
						|
		_typeString :
 | 
						|
		_typeString + " calldata"
 | 
						|
	);
 | 
						|
	m_typedParamsExternal << Whiskers(R"(<delimiter><type> <varName>)")
 | 
						|
		("delimiter", delimiterToString(_delimiter))
 | 
						|
		("type", qualifiedTypeString)
 | 
						|
		("varName", _varName)
 | 
						|
		.render();
 | 
						|
}
 | 
						|
 | 
						|
// Adds the qualifier "memory" to non-value parameter of an external function.
 | 
						|
void ProtoConverter::appendTypedParamsPublic(
 | 
						|
	bool _isValueType,
 | 
						|
	std::string const& _typeString,
 | 
						|
	std::string const& _varName,
 | 
						|
	Delimiter _delimiter
 | 
						|
)
 | 
						|
{
 | 
						|
	std::string qualifiedTypeString = (
 | 
						|
		_isValueType ?
 | 
						|
		_typeString :
 | 
						|
		_typeString + " memory"
 | 
						|
		);
 | 
						|
	m_typedParamsPublic << Whiskers(R"(<delimiter><type> <varName>)")
 | 
						|
		("delimiter", delimiterToString(_delimiter))
 | 
						|
		("type", qualifiedTypeString)
 | 
						|
		("varName", _varName)
 | 
						|
		.render();
 | 
						|
}
 | 
						|
 | 
						|
std::string ProtoConverter::typedParametersAsString(CalleeType _calleeType)
 | 
						|
{
 | 
						|
	switch (_calleeType)
 | 
						|
	{
 | 
						|
	case CalleeType::PUBLIC:
 | 
						|
		return m_typedParamsPublic.str();
 | 
						|
	case CalleeType::EXTERNAL:
 | 
						|
		return m_typedParamsExternal.str();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/// Test function to be called externally.
 | 
						|
void ProtoConverter::visit(TestFunction const& _x)
 | 
						|
{
 | 
						|
	m_output << R"(
 | 
						|
 | 
						|
	function test() public returns (uint) {
 | 
						|
	)";
 | 
						|
 | 
						|
	// Define state variables in function scope
 | 
						|
	m_output << m_statebuffer.str();
 | 
						|
 | 
						|
	// TODO: Support more than one but less than N local variables
 | 
						|
	visit(_x.local_vars());
 | 
						|
 | 
						|
	m_output << Whiskers(R"(
 | 
						|
		uint returnVal = this.coder_public(<parameter_names>);
 | 
						|
		if (returnVal != 0)
 | 
						|
			return returnVal;
 | 
						|
 | 
						|
		returnVal = this.coder_external(<parameter_names>);
 | 
						|
		if (returnVal != 0)
 | 
						|
			return uint(200000) + returnVal;
 | 
						|
 | 
						|
		bytes memory argumentEncoding = abi.encode(<parameter_names>);
 | 
						|
 | 
						|
		returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, <invalidLengthFuzz>);
 | 
						|
		if (returnVal != 0)
 | 
						|
			return returnVal;
 | 
						|
 | 
						|
		returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, <invalidLengthFuzz>);
 | 
						|
		if (returnVal != 0)
 | 
						|
			return uint(200000) + returnVal;
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	)")
 | 
						|
	("parameter_names", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter))
 | 
						|
	("invalidLengthFuzz", std::to_string(_x.invalid_encoding_length()))
 | 
						|
	.render();
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::writeHelperFunctions()
 | 
						|
{
 | 
						|
	m_output << 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;
 | 
						|
	}
 | 
						|
 | 
						|
	/// 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)
 | 
						|
	{
 | 
						|
		bytes memory validEncoding = new bytes(4 + argumentEncoding.length);
 | 
						|
		// Ensure that invalidEncoding crops at least one and at most all bytes from correct encoding.
 | 
						|
		uint invalidLength = invalidLengthFuzz % argumentEncoding.length;
 | 
						|
		bytes memory invalidEncoding = new bytes(4 + invalidLength);
 | 
						|
		for (uint i = 0; i < 4; i++)
 | 
						|
			validEncoding[i] = invalidEncoding[i] = funcSelector[i];
 | 
						|
		for (uint i = 0; i < argumentEncoding.length; i++)
 | 
						|
			validEncoding[i+4] = argumentEncoding[i];
 | 
						|
		for (uint i = 0; i < invalidLength; i++)
 | 
						|
			invalidEncoding[i+4] = argumentEncoding[i];
 | 
						|
		return (validEncoding, invalidEncoding);
 | 
						|
	}
 | 
						|
 | 
						|
	/// 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)
 | 
						|
	{
 | 
						|
		(bytes memory validEncoding, bytes memory invalidEncoding) = createEncoding(funcSelector, argumentEncoding, invalidLengthFuzz);
 | 
						|
		(bool success, bytes memory returnVal) = address(this).call(validEncoding);
 | 
						|
		uint returnCode = abi.decode(returnVal, (uint));
 | 
						|
		// Return non-zero value if call fails for correct encoding
 | 
						|
		if (success == false || returnCode != 0)
 | 
						|
			return 400000;
 | 
						|
		(success, ) = address(this).call(invalidEncoding);
 | 
						|
		// Return non-zero value if call succeeds for incorrect encoding
 | 
						|
		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
 | 
						|
	m_output << Whiskers(R"(
 | 
						|
	function coder_public(<parameters_memory>) public pure returns (uint) {
 | 
						|
		<equality_checks>
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	function coder_external(<parameters_calldata>) external pure returns (uint) {
 | 
						|
		<equality_checks>
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	)")
 | 
						|
	("parameters_memory", typedParametersAsString(CalleeType::PUBLIC))
 | 
						|
	("equality_checks", equalityChecksAsString())
 | 
						|
	("parameters_calldata", typedParametersAsString(CalleeType::EXTERNAL))
 | 
						|
	.render();
 | 
						|
}
 | 
						|
 | 
						|
void ProtoConverter::visit(Contract const& _x)
 | 
						|
{
 | 
						|
	m_output << R"(pragma solidity >=0.0;
 | 
						|
pragma experimental ABIEncoderV2;
 | 
						|
 | 
						|
contract C {
 | 
						|
)";
 | 
						|
	// TODO: Support more than one but less than N state variables
 | 
						|
	visit(_x.state_vars());
 | 
						|
	m_isStateVar = false;
 | 
						|
	// Test function
 | 
						|
	visit(_x.testfunction());
 | 
						|
	writeHelperFunctions();
 | 
						|
	m_output << "\n}";
 | 
						|
}
 | 
						|
 | 
						|
string ProtoConverter::contractToString(Contract const& _input)
 | 
						|
{
 | 
						|
	visit(_input);
 | 
						|
	return m_output.str();
 | 
						|
} |