mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #6496 from ethereum/move-funcs-6479
Move convert functions to Yul module
This commit is contained in:
		
						commit
						bf653b004e
					
				| @ -255,143 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const | ||||
| 	return suffix; | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::cleanupFunction(Type const& _type) | ||||
| { | ||||
| 	string functionName = string("cleanup_") + _type.identifier(); | ||||
| 	return createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) -> cleaned { | ||||
| 				<body> | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		switch (_type.category()) | ||||
| 		{ | ||||
| 		case Type::Category::Address: | ||||
| 			templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); | ||||
| 			break; | ||||
| 		case Type::Category::Integer: | ||||
| 		{ | ||||
| 			IntegerType const& type = dynamic_cast<IntegerType const&>(_type); | ||||
| 			if (type.numBits() == 256) | ||||
| 				templ("body", "cleaned := value"); | ||||
| 			else if (type.isSigned()) | ||||
| 				templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); | ||||
| 			else | ||||
| 				templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::RationalNumber: | ||||
| 			templ("body", "cleaned := value"); | ||||
| 			break; | ||||
| 		case Type::Category::Bool: | ||||
| 			templ("body", "cleaned := iszero(iszero(value))"); | ||||
| 			break; | ||||
| 		case Type::Category::FixedPoint: | ||||
| 			solUnimplemented("Fixed point types not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::Function: | ||||
| 			solAssert(dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External, ""); | ||||
| 			templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); | ||||
| 			break; | ||||
| 		case Type::Category::Array: | ||||
| 		case Type::Category::Struct: | ||||
| 		case Type::Category::Mapping: | ||||
| 			solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); | ||||
| 			templ("body", "cleaned := value"); | ||||
| 			break; | ||||
| 		case Type::Category::FixedBytes: | ||||
| 		{ | ||||
| 			FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type); | ||||
| 			if (type.numBytes() == 32) | ||||
| 				templ("body", "cleaned := value"); | ||||
| 			else if (type.numBytes() == 0) | ||||
| 				// This is disallowed in the type system.
 | ||||
| 				solAssert(false, ""); | ||||
| 			else | ||||
| 			{ | ||||
| 				size_t numBits = type.numBytes() * 8; | ||||
| 				u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); | ||||
| 				templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Contract: | ||||
| 		{ | ||||
| 			AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ? | ||||
| 				StateMutability::Payable : | ||||
| 				StateMutability::NonPayable | ||||
| 			); | ||||
| 			templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Enum: | ||||
| 		{ | ||||
| 			// Out of range enums cannot be truncated unambigiously and therefore it should be an error.
 | ||||
| 			templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::InaccessibleDynamic: | ||||
| 			templ("body", "cleaned := 0"); | ||||
| 			break; | ||||
| 		default: | ||||
| 			solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); | ||||
| 		} | ||||
| 
 | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) | ||||
| { | ||||
| 	string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); | ||||
| 	return createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) { | ||||
| 				if iszero(<condition>) { <failure> } | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		if (_revertOnFailure) | ||||
| 			templ("failure", "revert(0, 0)"); | ||||
| 		else | ||||
| 			templ("failure", "invalid()"); | ||||
| 
 | ||||
| 		switch (_type.category()) | ||||
| 		{ | ||||
| 		case Type::Category::Address: | ||||
| 		case Type::Category::Integer: | ||||
| 		case Type::Category::RationalNumber: | ||||
| 		case Type::Category::Bool: | ||||
| 		case Type::Category::FixedPoint: | ||||
| 		case Type::Category::Function: | ||||
| 		case Type::Category::Array: | ||||
| 		case Type::Category::Struct: | ||||
| 		case Type::Category::Mapping: | ||||
| 		case Type::Category::FixedBytes: | ||||
| 		case Type::Category::Contract: | ||||
| 		{ | ||||
| 			templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Enum: | ||||
| 		{ | ||||
| 			size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers(); | ||||
| 			solAssert(members > 0, "empty enum should have caused a parser error."); | ||||
| 			templ("condition", "lt(value, " + to_string(members) + ")"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::InaccessibleDynamic: | ||||
| 			templ("condition", "1"); | ||||
| 			break; | ||||
| 		default: | ||||
| 			solAssert(false, "Validation of type " + _type.identifier() + " requested."); | ||||
| 		} | ||||
| 
 | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) | ||||
| { | ||||
| 	solAssert(_type.isValueType(), ""); | ||||
| @ -425,171 +288,6 @@ string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFu | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) | ||||
| { | ||||
| 	string functionName = | ||||
| 		"convert_" + | ||||
| 		_from.identifier() + | ||||
| 		"_to_" + | ||||
| 		_to.identifier(); | ||||
| 	return createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) -> converted { | ||||
| 				<body> | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		string body; | ||||
| 		auto toCategory = _to.category(); | ||||
| 		auto fromCategory = _from.category(); | ||||
| 		switch (fromCategory) | ||||
| 		{ | ||||
| 		case Type::Category::Address: | ||||
| 			body = | ||||
| 				Whiskers("converted := <convert>(value)") | ||||
| 					("convert", conversionFunction(IntegerType(160), _to)) | ||||
| 					.render(); | ||||
| 			break; | ||||
| 		case Type::Category::Integer: | ||||
| 		case Type::Category::RationalNumber: | ||||
| 		case Type::Category::Contract: | ||||
| 		{ | ||||
| 			if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from)) | ||||
| 				solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); | ||||
| 			if (toCategory == Type::Category::FixedBytes) | ||||
| 			{ | ||||
| 				solAssert( | ||||
| 					fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, | ||||
| 					"Invalid conversion to FixedBytesType requested." | ||||
| 				); | ||||
| 				FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to); | ||||
| 				body = | ||||
| 					Whiskers("converted := <shiftLeft>(<clean>(value))") | ||||
| 						("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8)) | ||||
| 						("clean", cleanupFunction(_from)) | ||||
| 						.render(); | ||||
| 			} | ||||
| 			else if (toCategory == Type::Category::Enum) | ||||
| 			{ | ||||
| 				solAssert(_from.mobileType(), ""); | ||||
| 				body = | ||||
| 					Whiskers("converted := <cleanEnum>(<cleanInt>(value))") | ||||
| 					("cleanEnum", cleanupFunction(_to)) | ||||
| 					// "mobileType()" returns integer type for rational
 | ||||
| 					("cleanInt", cleanupFunction(*_from.mobileType())) | ||||
| 					.render(); | ||||
| 			} | ||||
| 			else if (toCategory == Type::Category::FixedPoint) | ||||
| 				solUnimplemented("Not yet implemented - FixedPointType."); | ||||
| 			else if (toCategory == Type::Category::Address) | ||||
| 				body = | ||||
| 					Whiskers("converted := <convert>(value)") | ||||
| 						("convert", conversionFunction(_from, IntegerType(160))) | ||||
| 						.render(); | ||||
| 			else | ||||
| 			{ | ||||
| 				solAssert( | ||||
| 					toCategory == Type::Category::Integer || | ||||
| 					toCategory == Type::Category::Contract, | ||||
| 				""); | ||||
| 				IntegerType const addressType(160); | ||||
| 				IntegerType const& to = | ||||
| 					toCategory == Type::Category::Integer ? | ||||
| 					dynamic_cast<IntegerType const&>(_to) : | ||||
| 					addressType; | ||||
| 
 | ||||
| 				// Clean according to the "to" type, except if this is
 | ||||
| 				// a widening conversion.
 | ||||
| 				IntegerType const* cleanupType = &to; | ||||
| 				if (fromCategory != Type::Category::RationalNumber) | ||||
| 				{ | ||||
| 					IntegerType const& from = | ||||
| 						fromCategory == Type::Category::Integer ? | ||||
| 						dynamic_cast<IntegerType const&>(_from) : | ||||
| 						addressType; | ||||
| 					if (to.numBits() > from.numBits()) | ||||
| 						cleanupType = &from; | ||||
| 				} | ||||
| 				body = | ||||
| 					Whiskers("converted := <cleanInt>(value)") | ||||
| 					("cleanInt", cleanupFunction(*cleanupType)) | ||||
| 					.render(); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Bool: | ||||
| 		{ | ||||
| 			solAssert(_from == _to, "Invalid conversion for bool."); | ||||
| 			body = | ||||
| 				Whiskers("converted := <clean>(value)") | ||||
| 				("clean", cleanupFunction(_from)) | ||||
| 				.render(); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::FixedPoint: | ||||
| 			solUnimplemented("Fixed point types not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::Array: | ||||
| 			solUnimplementedAssert(false, "Array conversion not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::Struct: | ||||
| 			solUnimplementedAssert(false, "Struct conversion not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::FixedBytes: | ||||
| 		{ | ||||
| 			FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from); | ||||
| 			if (toCategory == Type::Category::Integer) | ||||
| 				body = | ||||
| 					Whiskers("converted := <convert>(<shift>(value))") | ||||
| 					("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8)) | ||||
| 					("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) | ||||
| 					.render(); | ||||
| 			else if (toCategory == Type::Category::Address) | ||||
| 				body = | ||||
| 					Whiskers("converted := <convert>(value)") | ||||
| 						("convert", conversionFunction(_from, IntegerType(160))) | ||||
| 						.render(); | ||||
| 			else | ||||
| 			{ | ||||
| 				// clear for conversion to longer bytes
 | ||||
| 				solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); | ||||
| 				body = | ||||
| 					Whiskers("converted := <clean>(value)") | ||||
| 					("clean", cleanupFunction(from)) | ||||
| 					.render(); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Function: | ||||
| 		{ | ||||
| 			solAssert(false, "Conversion should not be called for function types."); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Enum: | ||||
| 		{ | ||||
| 			solAssert(toCategory == Type::Category::Integer || _from == _to, ""); | ||||
| 			EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from); | ||||
| 			body = | ||||
| 				Whiskers("converted := <clean>(value)") | ||||
| 				("clean", cleanupFunction(enumType)) | ||||
| 				.render(); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Tuple: | ||||
| 		{ | ||||
| 			solUnimplementedAssert(false, "Tuple conversion not implemented."); | ||||
| 			break; | ||||
| 		} | ||||
| 		default: | ||||
| 			solAssert(false, ""); | ||||
| 		} | ||||
| 
 | ||||
| 		solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); | ||||
| 		templ("body", body); | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::abiEncodingFunction( | ||||
| 	Type const& _from, | ||||
| 	Type const& _to, | ||||
| @ -678,9 +376,9 @@ string ABIFunctions::abiEncodingFunction( | ||||
| 		{ | ||||
| 			string cleanupConvert; | ||||
| 			if (_from == to) | ||||
| 				cleanupConvert = cleanupFunction(_from) + "(value)"; | ||||
| 				cleanupConvert = m_utils.cleanupFunction(_from) + "(value)"; | ||||
| 			else | ||||
| 				cleanupConvert = conversionFunction(_from, to) + "(value)"; | ||||
| 				cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)"; | ||||
| 			if (!_options.padded) | ||||
| 				cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; | ||||
| 			templ("cleanupConvert", cleanupConvert); | ||||
| @ -1365,7 +1063,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType( | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("cleanExtFun", cleanupFunction(_to)) | ||||
| 			("cleanExtFun", m_utils.cleanupFunction(_to)) | ||||
| 			.render(); | ||||
| 		}); | ||||
| } | ||||
| @ -1431,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM | ||||
| 		templ("load", _fromMemory ? "mload" : "calldataload"); | ||||
| 		// Validation should use the type and not decodingType, because e.g.
 | ||||
| 		// the decoding type of an enum is a plain int.
 | ||||
| 		templ("validator", validatorFunction(_type, true)); | ||||
| 		templ("validator", m_utils.validatorFunction(_type, true)); | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| 
 | ||||
| @ -1696,7 +1394,7 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("load", _fromMemory ? "mload" : "calldataload") | ||||
| 			("validateExtFun", validatorFunction(_type, true)) | ||||
| 			("validateExtFun", m_utils.validatorFunction(_type, true)) | ||||
| 			.render(); | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| @ -128,20 +128,6 @@ private: | ||||
| 		std::string toFunctionNameSuffix() const; | ||||
| 	}; | ||||
| 
 | ||||
| 	/// @returns the name of the cleanup function for the given type and
 | ||||
| 	/// adds its implementation to the requested functions.
 | ||||
| 	/// The cleanup function defers to the validator function with "assert"
 | ||||
| 	/// if there is no reasonable way to clean a value.
 | ||||
| 	std::string cleanupFunction(Type const& _type); | ||||
| 
 | ||||
| 	/// @returns the name of the validator function for the given type and
 | ||||
| 	/// adds its implementation to the requested functions.
 | ||||
| 	/// @param _revertOnFailure if true, causes revert on invalid data,
 | ||||
| 	/// otherwise an assertion failure.
 | ||||
| 	///
 | ||||
| 	/// This is used for data decoded from external sources.
 | ||||
| 	std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); | ||||
| 
 | ||||
| 	/// Performs cleanup after reading from a potentially compressed storage slot.
 | ||||
| 	/// The function does not perform any validation, it just masks or sign-extends
 | ||||
| 	/// higher order bytes or left-aligns (in case of bytesNN).
 | ||||
| @ -151,13 +137,6 @@ private: | ||||
| 	/// single variable.
 | ||||
| 	std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes); | ||||
| 
 | ||||
| 	/// @returns the name of the function that converts a value of type @a _from
 | ||||
| 	/// to a value of type @a _to. The resulting vale is guaranteed to be in range
 | ||||
| 	/// (i.e. "clean"). Asserts on failure.
 | ||||
| 	///
 | ||||
| 	/// This is used for data being encoded or general type conversions in the code.
 | ||||
| 	std::string conversionFunction(Type const& _from, Type const& _to); | ||||
| 
 | ||||
| 	/// @returns the name of the ABI encoding function with the given type
 | ||||
| 	/// and queues the generation of the function to the requested functions.
 | ||||
| 	/// @param _fromStack if false, the input value was just loaded from storage
 | ||||
|  | ||||
| @ -450,6 +450,308 @@ string YulUtilFunctions::allocationFunction() | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) | ||||
| { | ||||
| 	string functionName = | ||||
| 		"convert_" + | ||||
| 		_from.identifier() + | ||||
| 		"_to_" + | ||||
| 		_to.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) -> converted { | ||||
| 				<body> | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		string body; | ||||
| 		auto toCategory = _to.category(); | ||||
| 		auto fromCategory = _from.category(); | ||||
| 		switch (fromCategory) | ||||
| 		{ | ||||
| 		case Type::Category::Address: | ||||
| 			body = | ||||
| 				Whiskers("converted := <convert>(value)") | ||||
| 					("convert", conversionFunction(IntegerType(160), _to)) | ||||
| 					.render(); | ||||
| 			break; | ||||
| 		case Type::Category::Integer: | ||||
| 		case Type::Category::RationalNumber: | ||||
| 		case Type::Category::Contract: | ||||
| 		{ | ||||
| 			if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from)) | ||||
| 				solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); | ||||
| 			if (toCategory == Type::Category::FixedBytes) | ||||
| 			{ | ||||
| 				solAssert( | ||||
| 					fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, | ||||
| 					"Invalid conversion to FixedBytesType requested." | ||||
| 				); | ||||
| 				FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to); | ||||
| 				body = | ||||
| 					Whiskers("converted := <shiftLeft>(<clean>(value))") | ||||
| 						("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) | ||||
| 						("clean", cleanupFunction(_from)) | ||||
| 						.render(); | ||||
| 			} | ||||
| 			else if (toCategory == Type::Category::Enum) | ||||
| 			{ | ||||
| 				solAssert(_from.mobileType(), ""); | ||||
| 				body = | ||||
| 					Whiskers("converted := <cleanEnum>(<cleanInt>(value))") | ||||
| 					("cleanEnum", cleanupFunction(_to)) | ||||
| 					// "mobileType()" returns integer type for rational
 | ||||
| 					("cleanInt", cleanupFunction(*_from.mobileType())) | ||||
| 					.render(); | ||||
| 			} | ||||
| 			else if (toCategory == Type::Category::FixedPoint) | ||||
| 				solUnimplemented("Not yet implemented - FixedPointType."); | ||||
| 			else if (toCategory == Type::Category::Address) | ||||
| 				body = | ||||
| 					Whiskers("converted := <convert>(value)") | ||||
| 						("convert", conversionFunction(_from, IntegerType(160))) | ||||
| 						.render(); | ||||
| 			else | ||||
| 			{ | ||||
| 				solAssert( | ||||
| 					toCategory == Type::Category::Integer || | ||||
| 					toCategory == Type::Category::Contract, | ||||
| 				""); | ||||
| 				IntegerType const addressType(160); | ||||
| 				IntegerType const& to = | ||||
| 					toCategory == Type::Category::Integer ? | ||||
| 					dynamic_cast<IntegerType const&>(_to) : | ||||
| 					addressType; | ||||
| 
 | ||||
| 				// Clean according to the "to" type, except if this is
 | ||||
| 				// a widening conversion.
 | ||||
| 				IntegerType const* cleanupType = &to; | ||||
| 				if (fromCategory != Type::Category::RationalNumber) | ||||
| 				{ | ||||
| 					IntegerType const& from = | ||||
| 						fromCategory == Type::Category::Integer ? | ||||
| 						dynamic_cast<IntegerType const&>(_from) : | ||||
| 						addressType; | ||||
| 					if (to.numBits() > from.numBits()) | ||||
| 						cleanupType = &from; | ||||
| 				} | ||||
| 				body = | ||||
| 					Whiskers("converted := <cleanInt>(value)") | ||||
| 					("cleanInt", cleanupFunction(*cleanupType)) | ||||
| 					.render(); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Bool: | ||||
| 		{ | ||||
| 			solAssert(_from == _to, "Invalid conversion for bool."); | ||||
| 			body = | ||||
| 				Whiskers("converted := <clean>(value)") | ||||
| 				("clean", cleanupFunction(_from)) | ||||
| 				.render(); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::FixedPoint: | ||||
| 			solUnimplemented("Fixed point types not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::Array: | ||||
| 			solUnimplementedAssert(false, "Array conversion not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::Struct: | ||||
| 			solUnimplementedAssert(false, "Struct conversion not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::FixedBytes: | ||||
| 		{ | ||||
| 			FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from); | ||||
| 			if (toCategory == Type::Category::Integer) | ||||
| 				body = | ||||
| 					Whiskers("converted := <convert>(<shift>(value))") | ||||
| 					("shift", shiftRightFunction(256 - from.numBytes() * 8)) | ||||
| 					("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) | ||||
| 					.render(); | ||||
| 			else if (toCategory == Type::Category::Address) | ||||
| 				body = | ||||
| 					Whiskers("converted := <convert>(value)") | ||||
| 						("convert", conversionFunction(_from, IntegerType(160))) | ||||
| 						.render(); | ||||
| 			else | ||||
| 			{ | ||||
| 				// clear for conversion to longer bytes
 | ||||
| 				solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); | ||||
| 				body = | ||||
| 					Whiskers("converted := <clean>(value)") | ||||
| 					("clean", cleanupFunction(from)) | ||||
| 					.render(); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Function: | ||||
| 		{ | ||||
| 			solAssert(false, "Conversion should not be called for function types."); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Enum: | ||||
| 		{ | ||||
| 			solAssert(toCategory == Type::Category::Integer || _from == _to, ""); | ||||
| 			EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from); | ||||
| 			body = | ||||
| 				Whiskers("converted := <clean>(value)") | ||||
| 				("clean", cleanupFunction(enumType)) | ||||
| 				.render(); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Tuple: | ||||
| 		{ | ||||
| 			solUnimplementedAssert(false, "Tuple conversion not implemented."); | ||||
| 			break; | ||||
| 		} | ||||
| 		default: | ||||
| 			solAssert(false, ""); | ||||
| 		} | ||||
| 
 | ||||
| 		solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); | ||||
| 		templ("body", body); | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::cleanupFunction(Type const& _type) | ||||
| { | ||||
| 	string functionName = string("cleanup_") + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) -> cleaned { | ||||
| 				<body> | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		switch (_type.category()) | ||||
| 		{ | ||||
| 		case Type::Category::Address: | ||||
| 			templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); | ||||
| 			break; | ||||
| 		case Type::Category::Integer: | ||||
| 		{ | ||||
| 			IntegerType const& type = dynamic_cast<IntegerType const&>(_type); | ||||
| 			if (type.numBits() == 256) | ||||
| 				templ("body", "cleaned := value"); | ||||
| 			else if (type.isSigned()) | ||||
| 				templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); | ||||
| 			else | ||||
| 				templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::RationalNumber: | ||||
| 			templ("body", "cleaned := value"); | ||||
| 			break; | ||||
| 		case Type::Category::Bool: | ||||
| 			templ("body", "cleaned := iszero(iszero(value))"); | ||||
| 			break; | ||||
| 		case Type::Category::FixedPoint: | ||||
| 			solUnimplemented("Fixed point types not implemented."); | ||||
| 			break; | ||||
| 		case Type::Category::Function: | ||||
| 			solAssert(dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External, ""); | ||||
| 			templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); | ||||
| 			break; | ||||
| 		case Type::Category::Array: | ||||
| 		case Type::Category::Struct: | ||||
| 		case Type::Category::Mapping: | ||||
| 			solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); | ||||
| 			templ("body", "cleaned := value"); | ||||
| 			break; | ||||
| 		case Type::Category::FixedBytes: | ||||
| 		{ | ||||
| 			FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type); | ||||
| 			if (type.numBytes() == 32) | ||||
| 				templ("body", "cleaned := value"); | ||||
| 			else if (type.numBytes() == 0) | ||||
| 				// This is disallowed in the type system.
 | ||||
| 				solAssert(false, ""); | ||||
| 			else | ||||
| 			{ | ||||
| 				size_t numBits = type.numBytes() * 8; | ||||
| 				u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); | ||||
| 				templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Contract: | ||||
| 		{ | ||||
| 			AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ? | ||||
| 				StateMutability::Payable : | ||||
| 				StateMutability::NonPayable | ||||
| 			); | ||||
| 			templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Enum: | ||||
| 		{ | ||||
| 			// Out of range enums cannot be truncated unambigiously and therefore it should be an error.
 | ||||
| 			templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::InaccessibleDynamic: | ||||
| 			templ("body", "cleaned := 0"); | ||||
| 			break; | ||||
| 		default: | ||||
| 			solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); | ||||
| 		} | ||||
| 
 | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) | ||||
| { | ||||
| 	string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) { | ||||
| 				if iszero(<condition>) { <failure> } | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		if (_revertOnFailure) | ||||
| 			templ("failure", "revert(0, 0)"); | ||||
| 		else | ||||
| 			templ("failure", "invalid()"); | ||||
| 
 | ||||
| 		switch (_type.category()) | ||||
| 		{ | ||||
| 		case Type::Category::Address: | ||||
| 		case Type::Category::Integer: | ||||
| 		case Type::Category::RationalNumber: | ||||
| 		case Type::Category::Bool: | ||||
| 		case Type::Category::FixedPoint: | ||||
| 		case Type::Category::Function: | ||||
| 		case Type::Category::Array: | ||||
| 		case Type::Category::Struct: | ||||
| 		case Type::Category::Mapping: | ||||
| 		case Type::Category::FixedBytes: | ||||
| 		case Type::Category::Contract: | ||||
| 		{ | ||||
| 			templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::Enum: | ||||
| 		{ | ||||
| 			size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers(); | ||||
| 			solAssert(members > 0, "empty enum should have caused a parser error."); | ||||
| 			templ("condition", "lt(value, " + to_string(members) + ")"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::InaccessibleDynamic: | ||||
| 			templ("condition", "1"); | ||||
| 			break; | ||||
| 		default: | ||||
| 			solAssert(false, "Validation of type " + _type.identifier() + " requested."); | ||||
| 		} | ||||
| 
 | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) | ||||
| { | ||||
| 	string result; | ||||
|  | ||||
| @ -92,6 +92,27 @@ public: | ||||
| 	/// Return value: pointer
 | ||||
| 	std::string allocationFunction(); | ||||
| 
 | ||||
| 	/// @returns the name of the function that converts a value of type @a _from
 | ||||
| 	/// to a value of type @a _to. The resulting vale is guaranteed to be in range
 | ||||
| 	/// (i.e. "clean"). Asserts on failure.
 | ||||
| 	///
 | ||||
| 	/// This is used for data being encoded or general type conversions in the code.
 | ||||
| 	std::string conversionFunction(Type const& _from, Type const& _to); | ||||
| 
 | ||||
| 	/// @returns the name of the cleanup function for the given type and
 | ||||
| 	/// adds its implementation to the requested functions.
 | ||||
| 	/// The cleanup function defers to the validator function with "assert"
 | ||||
| 	/// if there is no reasonable way to clean a value.
 | ||||
| 	std::string cleanupFunction(Type const& _type); | ||||
| 
 | ||||
| 	/// @returns the name of the validator function for the given type and
 | ||||
| 	/// adds its implementation to the requested functions.
 | ||||
| 	/// @param _revertOnFailure if true, causes revert on invalid data,
 | ||||
| 	/// otherwise an assertion failure.
 | ||||
| 	///
 | ||||
| 	/// This is used for data decoded from external sources.
 | ||||
| 	std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); | ||||
| 
 | ||||
| 	/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
 | ||||
| 	/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
 | ||||
| 	/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user