mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #2276 from chriseth/sol_memoryArrays7
Copy routines for non-byte arrays.
This commit is contained in:
		
						commit
						2e5c52bfab
					
				
							
								
								
									
										177
									
								
								ArrayUtils.cpp
									
									
									
									
									
								
							
							
						
						
									
										177
									
								
								ArrayUtils.cpp
									
									
									
									
									
								
							| @ -231,6 +231,181 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons | |||||||
| 	m_context << u256(0); | 	m_context << u256(0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void ArrayUtils::copyArrayToMemory(const ArrayType& _sourceType, bool _padToWordBoundaries) const | ||||||
|  | { | ||||||
|  | 	solAssert( | ||||||
|  | 		_sourceType.getBaseType()->getCalldataEncodedSize() > 0, | ||||||
|  | 		"Nested arrays not yet implemented here." | ||||||
|  | 	); | ||||||
|  | 	unsigned baseSize = 1; | ||||||
|  | 	if (!_sourceType.isByteArray()) | ||||||
|  | 		// We always pad the elements, regardless of _padToWordBoundaries.
 | ||||||
|  | 		baseSize = _sourceType.getBaseType()->getCalldataEncodedSize(); | ||||||
|  | 
 | ||||||
|  | 	if (_sourceType.location() == DataLocation::CallData) | ||||||
|  | 	{ | ||||||
|  | 		if (!_sourceType.isDynamicallySized()) | ||||||
|  | 			m_context << _sourceType.getLength(); | ||||||
|  | 		if (_sourceType.getBaseType()->getCalldataEncodedSize() > 1) | ||||||
|  | 			m_context << u256(baseSize) << eth::Instruction::MUL; | ||||||
|  | 		// stack: target source_offset source_len
 | ||||||
|  | 		m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; | ||||||
|  | 		// stack: target source_offset source_len source_len source_offset target
 | ||||||
|  | 		m_context << eth::Instruction::CALLDATACOPY; | ||||||
|  | 		m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; | ||||||
|  | 		m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; | ||||||
|  | 	} | ||||||
|  | 	else if (_sourceType.location() == DataLocation::Memory) | ||||||
|  | 	{ | ||||||
|  | 		// memcpy using the built-in contract
 | ||||||
|  | 		retrieveLength(_sourceType); | ||||||
|  | 		if (_sourceType.isDynamicallySized()) | ||||||
|  | 		{ | ||||||
|  | 			// change pointer to data part
 | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; | ||||||
|  | 			m_context << eth::Instruction::SWAP1; | ||||||
|  | 		} | ||||||
|  | 		// convert length to size
 | ||||||
|  | 		if (baseSize > 1) | ||||||
|  | 			m_context << u256(baseSize) << eth::Instruction::MUL; | ||||||
|  | 		// stack: <target> <source> <size>
 | ||||||
|  | 		//@TODO do not use ::CALL if less than 32 bytes?
 | ||||||
|  | 		m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::DUP4; | ||||||
|  | 		CompilerUtils(m_context).memoryCopy(); | ||||||
|  | 
 | ||||||
|  | 		m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; | ||||||
|  | 		// stack: <target> <size>
 | ||||||
|  | 
 | ||||||
|  | 		bool paddingNeeded = false; | ||||||
|  | 		if (_sourceType.isDynamicallySized()) | ||||||
|  | 			paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0); | ||||||
|  | 		else | ||||||
|  | 			paddingNeeded = _padToWordBoundaries && (((_sourceType.getLength() * baseSize) % 32) != 0); | ||||||
|  | 		if (paddingNeeded) | ||||||
|  | 		{ | ||||||
|  | 			// stack: <target> <size>
 | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; | ||||||
|  | 			// stack: <length> <target + size>
 | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; | ||||||
|  | 			// stack: <target + size> <remainder = size % 32>
 | ||||||
|  | 			eth::AssemblyItem skip = m_context.newTag(); | ||||||
|  | 			if (_sourceType.isDynamicallySized()) | ||||||
|  | 			{ | ||||||
|  | 				m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; | ||||||
|  | 				m_context.appendConditionalJumpTo(skip); | ||||||
|  | 			} | ||||||
|  | 			// round off, load from there.
 | ||||||
|  | 			// stack <target + size> <remainder = size % 32>
 | ||||||
|  | 			m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; | ||||||
|  | 			m_context << eth::Instruction::SUB; | ||||||
|  | 			// stack: target+size remainder <target + size - remainder>
 | ||||||
|  | 			m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; | ||||||
|  | 			// Now we AND it with ~(2**(8 * (32 - remainder)) - 1)
 | ||||||
|  | 			m_context << u256(1); | ||||||
|  | 			m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB; | ||||||
|  | 			// stack: ...<v> 1 <32 - remainder>
 | ||||||
|  | 			m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; | ||||||
|  | 			m_context << eth::Instruction::NOT << eth::Instruction::AND; | ||||||
|  | 			// stack: target+size remainder target+size-remainder <v & ...>
 | ||||||
|  | 			m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; | ||||||
|  | 			// stack: target+size remainder target+size-remainder
 | ||||||
|  | 			m_context << u256(32) << eth::Instruction::ADD; | ||||||
|  | 			// stack: target+size remainder <new_padded_end>
 | ||||||
|  | 			m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; | ||||||
|  | 
 | ||||||
|  | 			if (_sourceType.isDynamicallySized()) | ||||||
|  | 				m_context << skip.tag(); | ||||||
|  | 			// stack <target + "size"> <remainder = size % 32>
 | ||||||
|  | 			m_context << eth::Instruction::POP; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 			// stack: <target> <size>
 | ||||||
|  | 			m_context << eth::Instruction::ADD; | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		solAssert(_sourceType.location() == DataLocation::Storage, ""); | ||||||
|  | 		unsigned storageBytes = _sourceType.getBaseType()->getStorageBytes(); | ||||||
|  | 		u256 storageSize = _sourceType.getBaseType()->getStorageSize(); | ||||||
|  | 		solAssert(storageSize > 1 || (storageSize == 1 && storageBytes > 0), ""); | ||||||
|  | 
 | ||||||
|  | 		m_context << eth::Instruction::POP; // remove offset, arrays always start new slot
 | ||||||
|  | 		retrieveLength(_sourceType); | ||||||
|  | 		// stack here: memory_offset storage_offset length
 | ||||||
|  | 		// jump to end if length is zero
 | ||||||
|  | 		m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; | ||||||
|  | 		eth::AssemblyItem loopEnd = m_context.newTag(); | ||||||
|  | 		m_context.appendConditionalJumpTo(loopEnd); | ||||||
|  | 		// compute memory end offset
 | ||||||
|  | 		if (baseSize > 1) | ||||||
|  | 			// convert length to memory size
 | ||||||
|  | 			m_context << u256(baseSize) << eth::Instruction::MUL; | ||||||
|  | 		m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2; | ||||||
|  | 		if (_sourceType.isDynamicallySized()) | ||||||
|  | 		{ | ||||||
|  | 			// actual array data is stored at SHA3(storage_offset)
 | ||||||
|  | 			m_context << eth::Instruction::SWAP1; | ||||||
|  | 			CompilerUtils(m_context).computeHashStatic(); | ||||||
|  | 			m_context << eth::Instruction::SWAP1; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// stack here: memory_end_offset storage_data_offset memory_offset
 | ||||||
|  | 		bool haveByteOffset = !_sourceType.isByteArray() && storageBytes <= 16; | ||||||
|  | 		if (haveByteOffset) | ||||||
|  | 			m_context << u256(0) << eth::Instruction::SWAP1; | ||||||
|  | 		// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
 | ||||||
|  | 		eth::AssemblyItem loopStart = m_context.newTag(); | ||||||
|  | 		m_context << loopStart; | ||||||
|  | 		// load and store
 | ||||||
|  | 		if (_sourceType.isByteArray()) | ||||||
|  | 		{ | ||||||
|  | 			// Packed both in storage and memory.
 | ||||||
|  | 			m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; | ||||||
|  | 			m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; | ||||||
|  | 			// increment storage_data_offset by 1
 | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; | ||||||
|  | 			// increment memory offset by 32
 | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
 | ||||||
|  | 			if (haveByteOffset) | ||||||
|  | 				m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; | ||||||
|  | 			else | ||||||
|  | 				m_context << eth::Instruction::DUP2 << u256(0); | ||||||
|  | 			StorageItem(m_context, *_sourceType.getBaseType()).retrieveValue(SourceLocation(), true); | ||||||
|  | 			CompilerUtils(m_context).storeInMemoryDynamic(*_sourceType.getBaseType()); | ||||||
|  | 			// increment storage_data_offset and byte offset
 | ||||||
|  | 			if (haveByteOffset) | ||||||
|  | 				incrementByteOffset(storageBytes, 2, 3); | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				m_context << eth::Instruction::SWAP1; | ||||||
|  | 				m_context << storageSize << eth::Instruction::ADD; | ||||||
|  | 				m_context << eth::Instruction::SWAP1; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// check for loop condition
 | ||||||
|  | 		m_context << eth::Instruction::DUP1 << eth::dupInstruction(haveByteOffset ? 5 : 4) << eth::Instruction::GT; | ||||||
|  | 		m_context.appendConditionalJumpTo(loopStart); | ||||||
|  | 		// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
 | ||||||
|  | 		if (haveByteOffset) | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; | ||||||
|  | 		if (_padToWordBoundaries && baseSize % 32 != 0) | ||||||
|  | 		{ | ||||||
|  | 			// memory_end_offset - start is the actual length (we want to compute the ceil of).
 | ||||||
|  | 			// memory_offset - start is its next multiple of 32, but it might be off by 32.
 | ||||||
|  | 			// so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31
 | ||||||
|  | 			m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB; | ||||||
|  | 			m_context << u256(31) << eth::Instruction::AND; | ||||||
|  | 			m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; | ||||||
|  | 			m_context << eth::Instruction::SWAP2; | ||||||
|  | 		} | ||||||
|  | 		m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void ArrayUtils::clearArray(ArrayType const& _type) const | void ArrayUtils::clearArray(ArrayType const& _type) const | ||||||
| { | { | ||||||
| 	unsigned stackHeightStart = m_context.getStackHeight(); | 	unsigned stackHeightStart = m_context.getStackHeight(); | ||||||
| @ -499,6 +674,8 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const | |||||||
| 			m_context << _arrayType.getBaseType()->getCalldataEncodedSize() << eth::Instruction::MUL; | 			m_context << _arrayType.getBaseType()->getCalldataEncodedSize() << eth::Instruction::MUL; | ||||||
| 		} | 		} | ||||||
| 		m_context << eth::Instruction::ADD; | 		m_context << eth::Instruction::ADD; | ||||||
|  | 		//@todo we should also load if it is a reference type of dynamic length
 | ||||||
|  | 		// but we should apply special logic if we load from calldata.
 | ||||||
| 		if (_arrayType.getBaseType()->isValueType()) | 		if (_arrayType.getBaseType()->isValueType()) | ||||||
| 			CompilerUtils(m_context).loadFromMemoryDynamic( | 			CompilerUtils(m_context).loadFromMemoryDynamic( | ||||||
| 				*_arrayType.getBaseType(), | 				*_arrayType.getBaseType(), | ||||||
|  | |||||||
| @ -44,6 +44,10 @@ public: | |||||||
| 	/// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset
 | 	/// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset
 | ||||||
| 	/// Stack post: target_reference target_byte_offset
 | 	/// Stack post: target_reference target_byte_offset
 | ||||||
| 	void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; | 	void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; | ||||||
|  | 	/// Copies an array (which cannot be dynamically nested) from anywhere to memory.
 | ||||||
|  | 	/// Stack pre: memory_offset source_item
 | ||||||
|  | 	/// Stack post: memory_offest + length(padded)
 | ||||||
|  | 	void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const; | ||||||
| 	/// Clears the given dynamic or static array.
 | 	/// Clears the given dynamic or static array.
 | ||||||
| 	/// Stack pre: storage_ref storage_byte_offset
 | 	/// Stack pre: storage_ref storage_byte_offset
 | ||||||
| 	/// Stack post:
 | 	/// Stack post:
 | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								Compiler.cpp
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								Compiler.cpp
									
									
									
									
									
								
							| @ -392,9 +392,9 @@ bool Compiler::visit(FunctionDefinition const& _function) | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for (ASTPointer<VariableDeclaration const> const& variable: _function.getReturnParameters()) | 	for (ASTPointer<VariableDeclaration const> const& variable: _function.getReturnParameters()) | ||||||
| 		m_context.addAndInitializeVariable(*variable); | 		appendStackVariableInitialisation(*variable); | ||||||
| 	for (VariableDeclaration const* localVariable: _function.getLocalVariables()) | 	for (VariableDeclaration const* localVariable: _function.getLocalVariables()) | ||||||
| 		m_context.addAndInitializeVariable(*localVariable); | 		appendStackVariableInitialisation(*localVariable); | ||||||
| 
 | 
 | ||||||
| 	if (_function.isConstructor()) | 	if (_function.isConstructor()) | ||||||
| 		if (auto c = m_context.getNextConstructor(dynamic_cast<ContractDefinition const&>(*_function.getScope()))) | 		if (auto c = m_context.getNextConstructor(dynamic_cast<ContractDefinition const&>(*_function.getScope()))) | ||||||
| @ -639,7 +639,7 @@ void Compiler::appendModifierOrFunctionCode() | |||||||
| 							  modifier.getParameters()[i]->getType()); | 							  modifier.getParameters()[i]->getType()); | ||||||
| 		} | 		} | ||||||
| 		for (VariableDeclaration const* localVariable: modifier.getLocalVariables()) | 		for (VariableDeclaration const* localVariable: modifier.getLocalVariables()) | ||||||
| 			m_context.addAndInitializeVariable(*localVariable); | 			appendStackVariableInitialisation(*localVariable); | ||||||
| 
 | 
 | ||||||
| 		unsigned const c_stackSurplus = CompilerUtils::getSizeOnStack(modifier.getParameters()) + | 		unsigned const c_stackSurplus = CompilerUtils::getSizeOnStack(modifier.getParameters()) + | ||||||
| 										CompilerUtils::getSizeOnStack(modifier.getLocalVariables()); | 										CompilerUtils::getSizeOnStack(modifier.getLocalVariables()); | ||||||
| @ -653,6 +653,13 @@ void Compiler::appendModifierOrFunctionCode() | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void Compiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) | ||||||
|  | { | ||||||
|  | 	CompilerContext::LocationSetter location(m_context, _variable); | ||||||
|  | 	m_context.addVariable(_variable); | ||||||
|  | 	ExpressionCompiler(m_context).appendStackVariableInitialisation(*_variable.getType()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) | void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) | ||||||
| { | { | ||||||
| 	ExpressionCompiler expressionCompiler(m_context, m_optimize); | 	ExpressionCompiler expressionCompiler(m_context, m_optimize); | ||||||
|  | |||||||
| @ -84,6 +84,13 @@ private: | |||||||
| 	void registerStateVariables(ContractDefinition const& _contract); | 	void registerStateVariables(ContractDefinition const& _contract); | ||||||
| 	void initializeStateVariables(ContractDefinition const& _contract); | 	void initializeStateVariables(ContractDefinition const& _contract); | ||||||
| 
 | 
 | ||||||
|  | 	/// Initialises all memory arrays in the local variables to point to an empty location.
 | ||||||
|  | 	void initialiseMemoryArrays(std::vector<VariableDeclaration const*> _variables); | ||||||
|  | 	/// Pushes the initialised value of the given type to the stack. If the type is a memory
 | ||||||
|  | 	/// reference type, allocates memory and pushes the memory pointer.
 | ||||||
|  | 	/// Not to be used for storage references.
 | ||||||
|  | 	void initialiseInMemory(Type const& _type); | ||||||
|  | 
 | ||||||
| 	virtual bool visit(VariableDeclaration const& _variableDeclaration) override; | 	virtual bool visit(VariableDeclaration const& _variableDeclaration) override; | ||||||
| 	virtual bool visit(FunctionDefinition const& _function) override; | 	virtual bool visit(FunctionDefinition const& _function) override; | ||||||
| 	virtual bool visit(IfStatement const& _ifStatement) override; | 	virtual bool visit(IfStatement const& _ifStatement) override; | ||||||
| @ -100,6 +107,7 @@ private: | |||||||
| 	/// body itself if the last modifier was reached.
 | 	/// body itself if the last modifier was reached.
 | ||||||
| 	void appendModifierOrFunctionCode(); | 	void appendModifierOrFunctionCode(); | ||||||
| 
 | 
 | ||||||
|  | 	void appendStackVariableInitialisation(VariableDeclaration const& _variable); | ||||||
| 	void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); | 	void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); | ||||||
| 
 | 
 | ||||||
| 	bool const m_optimize; | 	bool const m_optimize; | ||||||
|  | |||||||
| @ -65,15 +65,6 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration) | |||||||
| 	m_localVariables.erase(&_declaration); | 	m_localVariables.erase(&_declaration); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) |  | ||||||
| { |  | ||||||
| 	LocationSetter locationSetter(*this, _declaration); |  | ||||||
| 	addVariable(_declaration); |  | ||||||
| 	int const size = _declaration.getType()->getSizeOnStack(); |  | ||||||
| 	for (int i = 0; i < size; ++i) |  | ||||||
| 		*this << u256(0); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const | bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const | ||||||
| { | { | ||||||
| 	auto ret = m_compiledContracts.find(&_contract); | 	auto ret = m_compiledContracts.find(&_contract); | ||||||
|  | |||||||
| @ -46,7 +46,6 @@ public: | |||||||
| 	void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); | 	void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); | ||||||
| 	void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); | 	void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); | ||||||
| 	void removeVariable(VariableDeclaration const& _declaration); | 	void removeVariable(VariableDeclaration const& _declaration); | ||||||
| 	void addAndInitializeVariable(VariableDeclaration const& _declaration); |  | ||||||
| 
 | 
 | ||||||
| 	void setCompiledContracts(std::map<ContractDefinition const*, bytes const*> const& _contracts) { m_compiledContracts = _contracts; } | 	void setCompiledContracts(std::map<ContractDefinition const*, bytes const*> const& _contracts) { m_compiledContracts = _contracts; } | ||||||
| 	bytes const& getCompiledContract(ContractDefinition const& _contract) const; | 	bytes const& getCompiledContract(ContractDefinition const& _contract) const; | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ | |||||||
| #include <libevmcore/Instruction.h> | #include <libevmcore/Instruction.h> | ||||||
| #include <libevmcore/Params.h> | #include <libevmcore/Params.h> | ||||||
| #include <libsolidity/ArrayUtils.h> | #include <libsolidity/ArrayUtils.h> | ||||||
|  | #include <libsolidity/LValue.h> | ||||||
| 
 | 
 | ||||||
| using namespace std; | using namespace std; | ||||||
| 
 | 
 | ||||||
| @ -101,130 +102,10 @@ void CompilerUtils::storeInMemory(unsigned _offset) | |||||||
| void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries) | void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries) | ||||||
| { | { | ||||||
| 	if (_type.getCategory() == Type::Category::Array) | 	if (_type.getCategory() == Type::Category::Array) | ||||||
| 	{ | 		ArrayUtils(m_context).copyArrayToMemory( | ||||||
| 		auto const& type = dynamic_cast<ArrayType const&>(_type); | 			dynamic_cast<ArrayType const&>(_type), | ||||||
| 		solAssert(type.isByteArray(), "Non byte arrays not yet implemented here."); | 			_padToWordBoundaries | ||||||
| 
 | 		); | ||||||
| 		if (type.location() == DataLocation::CallData) |  | ||||||
| 		{ |  | ||||||
| 			if (!type.isDynamicallySized()) |  | ||||||
| 				m_context << type.getLength(); |  | ||||||
| 			// stack: target source_offset source_len
 |  | ||||||
| 			m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; |  | ||||||
| 			// stack: target source_offset source_len source_len source_offset target
 |  | ||||||
| 			m_context << eth::Instruction::CALLDATACOPY; |  | ||||||
| 			m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; |  | ||||||
| 			m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; |  | ||||||
| 		} |  | ||||||
| 		else if (type.location() == DataLocation::Memory) |  | ||||||
| 		{ |  | ||||||
| 			// memcpy using the built-in contract
 |  | ||||||
| 			ArrayUtils(m_context).retrieveLength(type); |  | ||||||
| 			if (type.isDynamicallySized()) |  | ||||||
| 			{ |  | ||||||
| 				// change pointer to data part
 |  | ||||||
| 				m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; |  | ||||||
| 				m_context << eth::Instruction::SWAP1; |  | ||||||
| 			} |  | ||||||
| 			// stack: <target> <source> <length>
 |  | ||||||
| 			// stack for call: outsize target size source value contract gas
 |  | ||||||
| 			m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4; |  | ||||||
| 			m_context << eth::Instruction::DUP2 << eth::Instruction::DUP5; |  | ||||||
| 			m_context << u256(0) << u256(identityContractAddress); |  | ||||||
| 			//@TODO do not use ::CALL if less than 32 bytes?
 |  | ||||||
| 			//@todo in production, we should not have to pair c_callNewAccountGas.
 |  | ||||||
| 			m_context << u256(eth::c_callGas + 15 + eth::c_callNewAccountGas) << eth::Instruction::GAS; |  | ||||||
| 			m_context << eth::Instruction::SUB << eth::Instruction::CALL; |  | ||||||
| 			m_context << eth::Instruction::POP; // ignore return value
 |  | ||||||
| 
 |  | ||||||
| 			m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; |  | ||||||
| 			// stack: <target> <length>
 |  | ||||||
| 
 |  | ||||||
| 			if (_padToWordBoundaries && (type.isDynamicallySized() || (type.getLength()) % 32 != 0)) |  | ||||||
| 			{ |  | ||||||
| 				// stack: <target> <length>
 |  | ||||||
| 				m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; |  | ||||||
| 				// stack: <length> <target + length>
 |  | ||||||
| 				m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; |  | ||||||
| 				// stack: <target + length> <remainder = length % 32>
 |  | ||||||
| 				eth::AssemblyItem skip = m_context.newTag(); |  | ||||||
| 				if (type.isDynamicallySized()) |  | ||||||
| 				{ |  | ||||||
| 					m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; |  | ||||||
| 					m_context.appendConditionalJumpTo(skip); |  | ||||||
| 				} |  | ||||||
| 				// round off, load from there.
 |  | ||||||
| 				// stack <target + length> <remainder = length % 32>
 |  | ||||||
| 				m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; |  | ||||||
| 				m_context << eth::Instruction::SUB; |  | ||||||
| 				// stack: target+length remainder <target + length - remainder>
 |  | ||||||
| 				m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; |  | ||||||
| 				// Now we AND it with ~(2**(8 * (32 - remainder)) - 1)
 |  | ||||||
| 				m_context << u256(1); |  | ||||||
| 				m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB; |  | ||||||
| 				// stack: ...<v> 1 <32 - remainder>
 |  | ||||||
| 				m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; |  | ||||||
| 				m_context << eth::Instruction::NOT << eth::Instruction::AND; |  | ||||||
| 				// stack: target+length remainder target+length-remainder <v & ...>
 |  | ||||||
| 				m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; |  | ||||||
| 				// stack: target+length remainder target+length-remainder
 |  | ||||||
| 				m_context << u256(32) << eth::Instruction::ADD; |  | ||||||
| 				// stack: target+length remainder <new_padded_end>
 |  | ||||||
| 				m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; |  | ||||||
| 
 |  | ||||||
| 				if (type.isDynamicallySized()) |  | ||||||
| 					m_context << skip.tag(); |  | ||||||
| 				// stack <target + "length"> <remainder = length % 32>
 |  | ||||||
| 				m_context << eth::Instruction::POP; |  | ||||||
| 			} |  | ||||||
| 			else |  | ||||||
| 				// stack: <target> <length>
 |  | ||||||
| 				m_context << eth::Instruction::ADD; |  | ||||||
| 		} |  | ||||||
| 		else |  | ||||||
| 		{ |  | ||||||
| 			solAssert(type.location() == DataLocation::Storage, ""); |  | ||||||
| 			m_context << eth::Instruction::POP; // remove offset, arrays always start new slot
 |  | ||||||
| 			m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; |  | ||||||
| 			// stack here: memory_offset storage_offset length_bytes
 |  | ||||||
| 			// jump to end if length is zero
 |  | ||||||
| 			m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; |  | ||||||
| 			eth::AssemblyItem loopEnd = m_context.newTag(); |  | ||||||
| 			m_context.appendConditionalJumpTo(loopEnd); |  | ||||||
| 			// compute memory end offset
 |  | ||||||
| 			m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2; |  | ||||||
| 			// actual array data is stored at SHA3(storage_offset)
 |  | ||||||
| 			m_context << eth::Instruction::SWAP1; |  | ||||||
| 			CompilerUtils(m_context).computeHashStatic(); |  | ||||||
| 			m_context << eth::Instruction::SWAP1; |  | ||||||
| 
 |  | ||||||
| 			// stack here: memory_end_offset storage_data_offset memory_offset
 |  | ||||||
| 			eth::AssemblyItem loopStart = m_context.newTag(); |  | ||||||
| 			m_context << loopStart; |  | ||||||
| 			// load and store
 |  | ||||||
| 			m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; |  | ||||||
| 			m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; |  | ||||||
| 			// increment storage_data_offset by 1
 |  | ||||||
| 			m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; |  | ||||||
| 			// increment memory offset by 32
 |  | ||||||
| 			m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; |  | ||||||
| 			// check for loop condition
 |  | ||||||
| 			m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT; |  | ||||||
| 			m_context.appendConditionalJumpTo(loopStart); |  | ||||||
| 			// stack here: memory_end_offset storage_data_offset memory_offset
 |  | ||||||
| 			if (_padToWordBoundaries) |  | ||||||
| 			{ |  | ||||||
| 				// memory_end_offset - start is the actual length (we want to compute the ceil of).
 |  | ||||||
| 				// memory_offset - start is its next multiple of 32, but it might be off by 32.
 |  | ||||||
| 				// so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31
 |  | ||||||
| 				m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB; |  | ||||||
| 				m_context << u256(31) << eth::Instruction::AND; |  | ||||||
| 				m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; |  | ||||||
| 				m_context << eth::Instruction::SWAP2; |  | ||||||
| 			} |  | ||||||
| 			m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); | 		unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); | ||||||
| @ -339,6 +220,21 @@ void CompilerUtils::encodeToMemory( | |||||||
| 	popStackSlots(argSize + dynPointers + 1); | 	popStackSlots(argSize + dynPointers + 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void CompilerUtils::memoryCopy() | ||||||
|  | { | ||||||
|  | 	// Stack here: size target source
 | ||||||
|  | 	// stack for call: outsize target size source value contract gas
 | ||||||
|  | 	//@TODO do not use ::CALL if less than 32 bytes?
 | ||||||
|  | 	m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1; | ||||||
|  | 	m_context << u256(0) << u256(identityContractAddress); | ||||||
|  | 	// compute gas costs
 | ||||||
|  | 	m_context << u256(32) << eth::Instruction::DUP5 << u256(31) << eth::Instruction::ADD; | ||||||
|  | 	m_context << eth::Instruction::DIV << u256(eth::c_identityWordGas) << eth::Instruction::MUL; | ||||||
|  | 	m_context << u256(eth::c_identityGas) << eth::Instruction::ADD; | ||||||
|  | 	m_context << eth::Instruction::CALL; | ||||||
|  | 	m_context << eth::Instruction::POP; // ignore return value
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) | void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) | ||||||
| { | { | ||||||
| 	// For a type extension, we need to remove all higher-order bits that we might have ignored in
 | 	// For a type extension, we need to remove all higher-order bits that we might have ignored in
 | ||||||
| @ -513,7 +409,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp | |||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
| 		default: | 		default: | ||||||
| 			solAssert(false, "Invalid type conversion requested."); | 			solAssert( | ||||||
|  | 				false, | ||||||
|  | 				"Invalid type conversion " + | ||||||
|  | 				_typeOnStack.toString(false) + | ||||||
|  | 				" to " + | ||||||
|  | 				_targetType.toString(false) + | ||||||
|  | 				" requested." | ||||||
|  | 			); | ||||||
| 		} | 		} | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -71,6 +71,8 @@ public: | |||||||
| 	void storeInMemory(unsigned _offset); | 	void storeInMemory(unsigned _offset); | ||||||
| 	/// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack
 | 	/// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack
 | ||||||
| 	/// and also updates that. For arrays, only copies the data part.
 | 	/// and also updates that. For arrays, only copies the data part.
 | ||||||
|  | 	/// @param _padToWordBoundaries if true, adds zeros to pad to multiple of 32 bytes. Array elements
 | ||||||
|  | 	/// are always padded (except for byte arrays), regardless of this parameter.
 | ||||||
| 	/// Stack pre: memory_offset value...
 | 	/// Stack pre: memory_offset value...
 | ||||||
| 	/// Stack post: (memory_offset+length)
 | 	/// Stack post: (memory_offset+length)
 | ||||||
| 	void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); | 	void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); | ||||||
| @ -93,6 +95,11 @@ public: | |||||||
| 		bool _copyDynamicDataInPlace = false | 		bool _copyDynamicDataInPlace = false | ||||||
| 	); | 	); | ||||||
| 
 | 
 | ||||||
|  | 	/// Uses a CALL to the identity contract to perform a memory-to-memory copy.
 | ||||||
|  | 	/// Stack pre: <size> <target> <source>
 | ||||||
|  | 	/// Stack post:
 | ||||||
|  | 	void memoryCopy(); | ||||||
|  | 
 | ||||||
| 	/// Appends code for an implicit or explicit type conversion. This includes erasing higher
 | 	/// Appends code for an implicit or explicit type conversion. This includes erasing higher
 | ||||||
| 	/// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory
 | 	/// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory
 | ||||||
| 	/// if a reference type is converted from calldata or storage to memory.
 | 	/// if a reference type is converted from calldata or storage to memory.
 | ||||||
|  | |||||||
| @ -56,6 +56,62 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c | |||||||
| 	StorageItem(m_context, _varDecl).storeValue(*_varDecl.getType(), _varDecl.getLocation(), true); | 	StorageItem(m_context, _varDecl).storeValue(*_varDecl.getType(), _varDecl.getLocation(), true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void ExpressionCompiler::appendStackVariableInitialisation(Type const& _type, bool _toMemory) | ||||||
|  | { | ||||||
|  | 	CompilerUtils utils(m_context); | ||||||
|  | 	auto const* referenceType = dynamic_cast<ReferenceType const*>(&_type); | ||||||
|  | 	if (!referenceType || referenceType->location() == DataLocation::Storage) | ||||||
|  | 	{ | ||||||
|  | 		for (size_t i = 0; i < _type.getSizeOnStack(); ++i) | ||||||
|  | 			m_context << u256(0); | ||||||
|  | 		if (_toMemory) | ||||||
|  | 			utils.storeInMemoryDynamic(_type); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	solAssert(referenceType->location() == DataLocation::Memory, ""); | ||||||
|  | 	if (!_toMemory) | ||||||
|  | 	{ | ||||||
|  | 		// allocate memory
 | ||||||
|  | 		utils.fetchFreeMemoryPointer(); | ||||||
|  | 		m_context << eth::Instruction::DUP1 << u256(max(32u, _type.getCalldataEncodedSize())); | ||||||
|  | 		m_context << eth::Instruction::ADD; | ||||||
|  | 		utils.storeFreeMemoryPointer(); | ||||||
|  | 		m_context << eth::Instruction::DUP1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (auto structType = dynamic_cast<StructType const*>(&_type)) | ||||||
|  | 		for (auto const& member: structType->getMembers()) | ||||||
|  | 			appendStackVariableInitialisation(*member.type, true); | ||||||
|  | 	else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) | ||||||
|  | 	{ | ||||||
|  | 		if (arrayType->isDynamicallySized()) | ||||||
|  | 		{ | ||||||
|  | 			// zero length
 | ||||||
|  | 			m_context << u256(0); | ||||||
|  | 			CompilerUtils(m_context).storeInMemoryDynamic(IntegerType(256)); | ||||||
|  | 		} | ||||||
|  | 		else if (arrayType->getLength() > 0) | ||||||
|  | 		{ | ||||||
|  | 			m_context << arrayType->getLength() << eth::Instruction::SWAP1; | ||||||
|  | 			// stack: items_to_do memory_pos
 | ||||||
|  | 			auto repeat = m_context.newTag(); | ||||||
|  | 			m_context << repeat; | ||||||
|  | 			appendStackVariableInitialisation(*arrayType->getBaseType(), true); | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::SWAP1; | ||||||
|  | 			m_context << eth::Instruction::SUB << eth::Instruction::SWAP1; | ||||||
|  | 			m_context << eth::Instruction::DUP2; | ||||||
|  | 			m_context.appendConditionalJumpTo(repeat); | ||||||
|  | 			m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); | ||||||
|  | 
 | ||||||
|  | 	if (!_toMemory) | ||||||
|  | 		// remove the updated memory pointer
 | ||||||
|  | 		m_context << eth::Instruction::POP; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) | void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) | ||||||
| { | { | ||||||
| 	CompilerContext::LocationSetter locationSetter(m_context, _varDecl); | 	CompilerContext::LocationSetter locationSetter(m_context, _varDecl); | ||||||
|  | |||||||
| @ -64,6 +64,13 @@ public: | |||||||
| 	/// Appends code to set a state variable to its initial value/expression.
 | 	/// Appends code to set a state variable to its initial value/expression.
 | ||||||
| 	void appendStateVariableInitialization(VariableDeclaration const& _varDecl); | 	void appendStateVariableInitialization(VariableDeclaration const& _varDecl); | ||||||
| 
 | 
 | ||||||
|  | 	/// Appends code to initialise a local variable.
 | ||||||
|  | 	/// If @a _toMemory is false, leaves the value on the stack. For memory references, this
 | ||||||
|  | 	/// allocates new memory.
 | ||||||
|  | 	/// If @a _toMemory is true, directly stores the data in the memory pos on the stack and
 | ||||||
|  | 	/// updates it.
 | ||||||
|  | 	void appendStackVariableInitialisation(Type const& _type, bool _toMemory = false); | ||||||
|  | 
 | ||||||
| 	/// Appends code for a State Variable accessor function
 | 	/// Appends code for a State Variable accessor function
 | ||||||
| 	void appendStateVariableAccessor(VariableDeclaration const& _varDecl); | 	void appendStateVariableAccessor(VariableDeclaration const& _varDecl); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								Types.cpp
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								Types.cpp
									
									
									
									
									
								
							| @ -721,9 +721,13 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const | |||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		// Require that the base type is the same, not only convertible.
 | 		// Conversion to storage pointer or to memory, we de not copy element-for-element here, so
 | ||||||
| 		// This disallows assignment of nested arrays from storage to memory for now.
 | 		// require that the base type is the same, not only convertible.
 | ||||||
| 		if (*getBaseType() != *convertTo.getBaseType()) | 		// This disallows assignment of nested dynamic arrays from storage to memory for now.
 | ||||||
|  | 		if ( | ||||||
|  | 			*copyForLocationIfReference(location(), getBaseType()) != | ||||||
|  | 			*copyForLocationIfReference(location(), convertTo.getBaseType()) | ||||||
|  | 		) | ||||||
| 			return false; | 			return false; | ||||||
| 		if (isDynamicallySized() != convertTo.isDynamicallySized()) | 		if (isDynamicallySized() != convertTo.isDynamicallySized()) | ||||||
| 			return false; | 			return false; | ||||||
| @ -822,16 +826,16 @@ string ArrayType::toString(bool _short) const | |||||||
| TypePointer ArrayType::externalType() const | TypePointer ArrayType::externalType() const | ||||||
| { | { | ||||||
| 	if (m_arrayKind != ArrayKind::Ordinary) | 	if (m_arrayKind != ArrayKind::Ordinary) | ||||||
| 		return this->copyForLocation(DataLocation::CallData, true); | 		return this->copyForLocation(DataLocation::Memory, true); | ||||||
| 	if (!m_baseType->externalType()) | 	if (!m_baseType->externalType()) | ||||||
| 		return TypePointer(); | 		return TypePointer(); | ||||||
| 	if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized()) | 	if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized()) | ||||||
| 		return TypePointer(); | 		return TypePointer(); | ||||||
| 
 | 
 | ||||||
| 	if (isDynamicallySized()) | 	if (isDynamicallySized()) | ||||||
| 		return std::make_shared<ArrayType>(DataLocation::CallData, m_baseType->externalType()); | 		return std::make_shared<ArrayType>(DataLocation::Memory, m_baseType->externalType()); | ||||||
| 	else | 	else | ||||||
| 		return std::make_shared<ArrayType>(DataLocation::CallData, m_baseType->externalType(), m_length); | 		return std::make_shared<ArrayType>(DataLocation::Memory, m_baseType->externalType(), m_length); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const | TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const | ||||||
| @ -970,6 +974,22 @@ bool StructType::operator==(Type const& _other) const | |||||||
| 	return ReferenceType::operator==(other) && other.m_struct == m_struct; | 	return ReferenceType::operator==(other) && other.m_struct == m_struct; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | unsigned StructType::getCalldataEncodedSize(bool _padded) const | ||||||
|  | { | ||||||
|  | 	unsigned size = 0; | ||||||
|  | 	for (auto const& member: getMembers()) | ||||||
|  | 		if (!member.type->canLiveOutsideStorage()) | ||||||
|  | 			return 0; | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			unsigned memberSize = member.type->getCalldataEncodedSize(_padded); | ||||||
|  | 			if (memberSize == 0) | ||||||
|  | 				return 0; | ||||||
|  | 			size += memberSize; | ||||||
|  | 		} | ||||||
|  | 	return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| u256 StructType::getStorageSize() const | u256 StructType::getStorageSize() const | ||||||
| { | { | ||||||
| 	return max<u256>(1, getMembers().getStorageSize()); | 	return max<u256>(1, getMembers().getStorageSize()); | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								Types.h
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Types.h
									
									
									
									
									
								
							| @ -542,6 +542,7 @@ public: | |||||||
| 	virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; | 	virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; | ||||||
| 	virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; | 	virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; | ||||||
| 	virtual bool operator==(Type const& _other) const override; | 	virtual bool operator==(Type const& _other) const override; | ||||||
|  | 	virtual unsigned getCalldataEncodedSize(bool _padded) const override; | ||||||
| 	virtual u256 getStorageSize() const override; | 	virtual u256 getStorageSize() const override; | ||||||
| 	virtual bool canLiveOutsideStorage() const override; | 	virtual bool canLiveOutsideStorage() const override; | ||||||
| 	virtual unsigned getSizeOnStack() const override { return 2; } | 	virtual unsigned getSizeOnStack() const override { return 2; } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user