Merge remote-tracking branch 'upstream/develop' into evmjit

This commit is contained in:
Paweł Bylica 2015-03-16 12:01:47 +01:00
commit 0be8fa49f0
11 changed files with 196 additions and 183 deletions

View File

@ -671,7 +671,7 @@ void IndexAccess::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted.")); BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted."));
m_index->expectType(IntegerType(256)); m_index->expectType(IntegerType(256));
if (type.isByteArray()) if (type.isByteArray())
m_type = make_shared<IntegerType>(8, IntegerType::Modifier::Hash); m_type = make_shared<FixedBytesType>(1);
else else
m_type = type.getBaseType(); m_type = type.getBaseType();
m_isLValue = type.getLocation() != ArrayType::Location::CallData; m_isLValue = type.getLocation() != ArrayType::Location::CallData;

View File

@ -125,6 +125,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false); CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false);
else else
solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString()); solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString());
solAssert(2 + sourceBaseType->getSizeOnStack() <= 16, "Stack too deep.");
m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()); m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack());
StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
} }

View File

@ -228,6 +228,7 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool
{ {
// Retrieve data start offset by adding length to start offset of previous dynamic type // Retrieve data start offset by adding length to start offset of previous dynamic type
unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument; unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument;
solAssert(stackDepth <= 16, "Stack too deep.");
m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth); m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth);
ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true); ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true);
m_context << eth::Instruction::ADD; m_context << eth::Instruction::ADD;
@ -359,6 +360,7 @@ bool Compiler::visit(FunctionDefinition const& _function)
stackLayout.push_back(i); stackLayout.push_back(i);
stackLayout += vector<int>(c_localVariablesSize, -1); stackLayout += vector<int>(c_localVariablesSize, -1);
solAssert(stackLayout.size() <= 17, "Stack too deep.");
while (stackLayout.back() != int(stackLayout.size() - 1)) while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0) if (stackLayout.back() < 0)
{ {

View File

@ -41,14 +41,14 @@ namespace solidity
{ {
const map<string, string> StandardSources = map<string, string>{ const map<string, string> StandardSources = map<string, string>{
{"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(string3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"}, {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(bytes3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"},
{"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"}, {"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"},
{"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}})"}, {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,bytes3 name,uint256 denom){}function register(bytes3 name,uint256 denom){}function unregister(){}})"},
{"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"}, {"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"},
{"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"}, {"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"},
{"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"}, {"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"},
{"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(string32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"}, {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(bytes32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"},
{"NameReg", R"(contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}})"}, {"NameReg", R"(contract NameReg{function register(bytes32 name){}function addressOf(bytes32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(bytes32 name){}})"},
{"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"}, {"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"},
{"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"}, {"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"},
{"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"} {"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"}

View File

@ -138,6 +138,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
{ {
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable)); unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable));
unsigned const size = _variable.getType()->getSizeOnStack(); unsigned const size = _variable.getType()->getSizeOnStack();
solAssert(stackPosition >= size, "Variable size and position mismatch.");
// move variable starting from its top end in the stack // move variable starting from its top end in the stack
if (stackPosition - size + 1 > 16) if (stackPosition - size + 1 > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_variable.getLocation()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_variable.getLocation())
@ -148,8 +149,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
{ {
if (_stackDepth > 16) solAssert(_stackDepth <= 16, "Stack too deep.");
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Stack too deep."));
for (unsigned i = 0; i < _itemSize; ++i) for (unsigned i = 0; i < _itemSize; ++i)
m_context << eth::dupInstruction(_stackDepth); m_context << eth::dupInstruction(_stackDepth);
} }
@ -178,7 +178,7 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
{ {
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
bool leftAligned = _type.getCategory() == Type::Category::String; bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
if (numBytes == 0) if (numBytes == 0)
m_context << eth::Instruction::POP << u256(0); m_context << eth::Instruction::POP << u256(0);
else else
@ -202,7 +202,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
{ {
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
bool leftAligned = _type.getCategory() == Type::Category::String; bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
if (numBytes == 0) if (numBytes == 0)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
else else

View File

@ -123,23 +123,25 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
Type::Category stackTypeCategory = _typeOnStack.getCategory(); Type::Category stackTypeCategory = _typeOnStack.getCategory();
Type::Category targetTypeCategory = _targetType.getCategory(); Type::Category targetTypeCategory = _targetType.getCategory();
if (stackTypeCategory == Type::Category::String) switch (stackTypeCategory)
{ {
StaticStringType const& typeOnStack = dynamic_cast<StaticStringType const&>(_typeOnStack); case Type::Category::FixedBytes:
{
FixedBytesType const& typeOnStack = dynamic_cast<FixedBytesType const&>(_typeOnStack);
if (targetTypeCategory == Type::Category::Integer) if (targetTypeCategory == Type::Category::Integer)
{ {
// conversion from string to hash. no need to clean the high bit // conversion from bytes to integer. no need to clean the high bit
// only to shift right because of opposite alignment // only to shift right because of opposite alignment
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed.");
solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same.");
m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8)
appendTypeConversion(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded);
} }
else else
{ {
// clear lower-order bytes for conversion to shorter strings - we always clean // clear lower-order bytes for conversion to shorter bytes - we always clean
solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType); FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType);
if (targetType.getNumBytes() < typeOnStack.getNumBytes()) if (targetType.getNumBytes() < typeOnStack.getNumBytes())
{ {
if (targetType.getNumBytes() == 0) if (targetType.getNumBytes() == 0)
@ -151,22 +153,24 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
} }
} }
} }
else if (stackTypeCategory == Type::Category::Enum) break;
solAssert(targetTypeCategory == Type::Category::Integer || case Type::Category::Enum:
targetTypeCategory == Type::Category::Enum, ""); solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, "");
else if (stackTypeCategory == Type::Category::Integer || break;
stackTypeCategory == Type::Category::Contract || case Type::Category::Integer:
stackTypeCategory == Type::Category::IntegerConstant) case Type::Category::Contract:
{ case Type::Category::IntegerConstant:
if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) if (targetTypeCategory == Type::Category::FixedBytes)
{ {
// conversion from hash to string. no need to clean the high bit solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant,
"Invalid conversion to FixedBytesType requested.");
// conversion from bytes to string. no need to clean the high bit
// only to shift left because of opposite alignment // only to shift left because of opposite alignment
StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType); FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType);
IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack); if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits())
solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); appendHighBitsCleanup(*typeOnStack);
m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL;
} }
else if (targetTypeCategory == Type::Category::Enum) else if (targetTypeCategory == Type::Category::Enum)
// just clean // just clean
@ -176,7 +180,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, "");
IntegerType addressType(0, IntegerType::Modifier::Address); IntegerType addressType(0, IntegerType::Modifier::Address);
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer IntegerType const& targetType = targetTypeCategory == Type::Category::Integer
? dynamic_cast<IntegerType const&>(_targetType) : addressType; ? dynamic_cast<IntegerType const&>(_targetType) : addressType;
if (stackTypeCategory == Type::Category::IntegerConstant) if (stackTypeCategory == Type::Category::IntegerConstant)
{ {
IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack);
@ -188,7 +192,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
else else
{ {
IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer
? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType; ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType;
// Widening: clean up according to source type width // Widening: clean up according to source type width
// Non-widening and force: clean up according to target type bits // Non-widening and force: clean up according to target type bits
if (targetType.getNumBits() > typeOnStack.getNumBits()) if (targetType.getNumBits() > typeOnStack.getNumBits())
@ -197,10 +201,12 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
appendHighBitsCleanup(targetType); appendHighBitsCleanup(targetType);
} }
} }
} break;
else if (_typeOnStack != _targetType) default:
// All other types should not be convertible to non-equal types. // All other types should not be convertible to non-equal types.
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested.")); solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
break;
}
} }
bool ExpressionCompiler::visit(Assignment const& _assignment) bool ExpressionCompiler::visit(Assignment const& _assignment)
@ -227,9 +233,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
m_currentLValue->retrieveValue(_assignment.getLocation(), true); m_currentLValue->retrieveValue(_assignment.getLocation(), true);
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType());
if (lvalueSize > 0) if (lvalueSize > 0)
{
solAssert(itemSize + lvalueSize <= 16, "Stack too deep.");
// value [lvalue_ref] updated_value // value [lvalue_ref] updated_value
for (unsigned i = 0; i < itemSize; ++i) for (unsigned i = 0; i < itemSize; ++i)
m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP;
}
} }
m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation());
m_currentLValue.reset(); m_currentLValue.reset();
@ -551,10 +560,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case Location::SHA256: case Location::SHA256:
case Location::RIPEMD160: case Location::RIPEMD160:
{ {
_functionCall.getExpression().accept(*this);
static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, static const map<Location, u256> contractAddresses{{Location::ECRecover, 1},
{Location::SHA256, 2}, {Location::SHA256, 2},
{Location::RIPEMD160, 3}}; {Location::RIPEMD160, 3}};
m_context << contractAddresses.find(function.getLocation())->second; m_context << contractAddresses.find(function.getLocation())->second;
for (unsigned i = function.getSizeOnStack(); i > 0; --i)
m_context << eth::swapInstruction(i);
appendExternalFunctionCall(function, arguments, true); appendExternalFunctionCall(function, arguments, true);
break; break;
} }
@ -775,7 +787,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
// no lvalue, just retrieve the value // no lvalue, just retrieve the value
m_context m_context
<< eth::Instruction::ADD << eth::Instruction::CALLDATALOAD << eth::Instruction::ADD << eth::Instruction::CALLDATALOAD
<< u256(0) << eth::Instruction::BYTE; << ((u256(0xff) << (256 - 8))) << eth::Instruction::AND;
break; break;
case ArrayType::Location::Memory: case ArrayType::Location::Memory:
solAssert(false, "Memory lvalues not yet implemented."); solAssert(false, "Memory lvalues not yet implemented.");
@ -870,7 +882,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
{ {
case Type::Category::IntegerConstant: case Type::Category::IntegerConstant:
case Type::Category::Bool: case Type::Category::Bool:
case Type::Category::String: case Type::Category::FixedBytes:
m_context << _literal.getType()->literalValue(&_literal); m_context << _literal.getType()->literalValue(&_literal);
break; break;
default: default:

View File

@ -41,23 +41,23 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
make_shared<MagicVariableDeclaration>("suicide", make_shared<MagicVariableDeclaration>("suicide",
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)), make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)),
make_shared<MagicVariableDeclaration>("sha3", make_shared<MagicVariableDeclaration>("sha3",
make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA3, true)), make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)),
make_shared<MagicVariableDeclaration>("log0", make_shared<MagicVariableDeclaration>("log0",
make_shared<FunctionType>(strings{"hash"},strings{}, FunctionType::Location::Log0)), make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)),
make_shared<MagicVariableDeclaration>("log1", make_shared<MagicVariableDeclaration>("log1",
make_shared<FunctionType>(strings{"hash", "hash"},strings{}, FunctionType::Location::Log1)), make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)),
make_shared<MagicVariableDeclaration>("log2", make_shared<MagicVariableDeclaration>("log2",
make_shared<FunctionType>(strings{"hash", "hash", "hash"},strings{}, FunctionType::Location::Log2)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)),
make_shared<MagicVariableDeclaration>("log3", make_shared<MagicVariableDeclaration>("log3",
make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log3)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)),
make_shared<MagicVariableDeclaration>("log4", make_shared<MagicVariableDeclaration>("log4",
make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log4)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)),
make_shared<MagicVariableDeclaration>("sha256", make_shared<MagicVariableDeclaration>("sha256",
make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA256, true)), make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)),
make_shared<MagicVariableDeclaration>("ecrecover", make_shared<MagicVariableDeclaration>("ecrecover",
make_shared<FunctionType>(strings{"hash", "hash8", "hash", "hash"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)),
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<MagicVariableDeclaration>("ripemd160",
make_shared<FunctionType>(strings(), strings{"hash160"}, FunctionType::Location::RIPEMD160, true))}) make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))})
{ {
} }

View File

@ -167,6 +167,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
// stack: source_ref target_ref member_offset source_member_ref // stack: source_ref target_ref member_offset source_member_ref
StorageItem(m_context, *memberType).retrieveValue(_location, true); StorageItem(m_context, *memberType).retrieveValue(_location, true);
// stack: source_ref target_ref member_offset source_value... // stack: source_ref target_ref member_offset source_value...
solAssert(2 + memberType->getSizeOnStack() <= 16, "Stack too deep.");
m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) m_context << eth::dupInstruction(2 + memberType->getSizeOnStack())
<< eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD;
// stack: source_ref target_ref member_offset source_value... target_member_ref // stack: source_ref target_ref member_offset source_value... target_member_ref
@ -234,7 +235,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
} }
/// Used in StorageByteArrayElement /// Used in StorageByteArrayElement
static IntegerType byteType(8, IntegerType::Modifier::Hash); static FixedBytesType byteType(1);
StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext):
LValue(_compilerContext, byteType) LValue(_compilerContext, byteType)
@ -250,6 +251,7 @@ void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove)
else else
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD
<< eth::Instruction::DUP2 << eth::Instruction::BYTE; << eth::Instruction::DUP2 << eth::Instruction::BYTE;
m_context << (u256(1) << (256 - 8)) << eth::Instruction::MUL;
} }
void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const
@ -265,8 +267,9 @@ void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, boo
m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL
<< eth::Instruction::NOT << eth::Instruction::AND; << eth::Instruction::NOT << eth::Instruction::AND;
// stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte // stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP4 << eth::Instruction::MUL m_context << eth::Instruction::SWAP1;
<< eth::Instruction::OR; m_context << (u256(1) << (256 - 8)) << eth::Instruction::DUP5 << eth::Instruction::DIV
<< eth::Instruction::MUL << eth::Instruction::OR;
// stack: value ref new_full_value // stack: value ref new_full_value
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
if (_move) if (_move)

103
Token.h
View File

@ -252,77 +252,44 @@ namespace solidity
K(UInt240, "uint240", 0) \ K(UInt240, "uint240", 0) \
K(UInt248, "uint248", 0) \ K(UInt248, "uint248", 0) \
K(UInt256, "uint256", 0) \ K(UInt256, "uint256", 0) \
K(Hash, "hash", 0) \ K(Bytes0, "bytes0", 0) \
K(Hash8, "hash8", 0) \ K(Bytes1, "bytes1", 0) \
K(Hash16, "hash16", 0) \ K(Bytes2, "bytes2", 0) \
K(Hash24, "hash24", 0) \ K(Bytes3, "bytes3", 0) \
K(Hash32, "hash32", 0) \ K(Bytes4, "bytes4", 0) \
K(Hash40, "hash40", 0) \ K(Bytes5, "bytes5", 0) \
K(Hash48, "hash48", 0) \ K(Bytes6, "bytes6", 0) \
K(Hash56, "hash56", 0) \ K(Bytes7, "bytes7", 0) \
K(Hash64, "hash64", 0) \ K(Bytes8, "bytes8", 0) \
K(Hash72, "hash72", 0) \ K(Bytes9, "bytes9", 0) \
K(Hash80, "hash80", 0) \ K(Bytes10, "bytes10", 0) \
K(Hash88, "hash88", 0) \ K(Bytes11, "bytes11", 0) \
K(Hash96, "hash96", 0) \ K(Bytes12, "bytes12", 0) \
K(Hash104, "hash104", 0) \ K(Bytes13, "bytes13", 0) \
K(Hash112, "hash112", 0) \ K(Bytes14, "bytes14", 0) \
K(Hash120, "hash120", 0) \ K(Bytes15, "bytes15", 0) \
K(Hash128, "hash128", 0) \ K(Bytes16, "bytes16", 0) \
K(Hash136, "hash136", 0) \ K(Bytes17, "bytes17", 0) \
K(Hash144, "hash144", 0) \ K(Bytes18, "bytes18", 0) \
K(Hash152, "hash152", 0) \ K(Bytes19, "bytes19", 0) \
K(Hash160, "hash160", 0) \ K(Bytes20, "bytes20", 0) \
K(Hash168, "hash168", 0) \ K(Bytes21, "bytes21", 0) \
K(Hash176, "hash178", 0) \ K(Bytes22, "bytes22", 0) \
K(Hash184, "hash184", 0) \ K(Bytes23, "bytes23", 0) \
K(Hash192, "hash192", 0) \ K(Bytes24, "bytes24", 0) \
K(Hash200, "hash200", 0) \ K(Bytes25, "bytes25", 0) \
K(Hash208, "hash208", 0) \ K(Bytes26, "bytes26", 0) \
K(Hash216, "hash216", 0) \ K(Bytes27, "bytes27", 0) \
K(Hash224, "hash224", 0) \ K(Bytes28, "bytes28", 0) \
K(Hash232, "hash232", 0) \ K(Bytes29, "bytes29", 0) \
K(Hash240, "hash240", 0) \ K(Bytes30, "bytes30", 0) \
K(Hash248, "hash248", 0) \ K(Bytes31, "bytes31", 0) \
K(Hash256, "hash256", 0) \ K(Bytes32, "bytes32", 0) \
K(Bytes, "bytes", 0) \
K(Byte, "byte", 0) \
K(Address, "address", 0) \ K(Address, "address", 0) \
K(Bool, "bool", 0) \ K(Bool, "bool", 0) \
K(Bytes, "bytes", 0) \
K(StringType, "string", 0) \ K(StringType, "string", 0) \
K(String0, "string0", 0) \
K(String1, "string1", 0) \
K(String2, "string2", 0) \
K(String3, "string3", 0) \
K(String4, "string4", 0) \
K(String5, "string5", 0) \
K(String6, "string6", 0) \
K(String7, "string7", 0) \
K(String8, "string8", 0) \
K(String9, "string9", 0) \
K(String10, "string10", 0) \
K(String11, "string11", 0) \
K(String12, "string12", 0) \
K(String13, "string13", 0) \
K(String14, "string14", 0) \
K(String15, "string15", 0) \
K(String16, "string16", 0) \
K(String17, "string17", 0) \
K(String18, "string18", 0) \
K(String19, "string19", 0) \
K(String20, "string20", 0) \
K(String21, "string21", 0) \
K(String22, "string22", 0) \
K(String23, "string23", 0) \
K(String24, "string24", 0) \
K(String25, "string25", 0) \
K(String26, "string26", 0) \
K(String27, "string27", 0) \
K(String28, "string28", 0) \
K(String29, "string29", 0) \
K(String30, "string30", 0) \
K(String31, "string31", 0) \
K(String32, "string32", 0) \
K(Text, "text", 0) \
K(Real, "real", 0) \ K(Real, "real", 0) \
K(UReal, "ureal", 0) \ K(UReal, "ureal", 0) \
T(TypesEnd, NULL, 0) /* used as type enum end marker */ \ T(TypesEnd, NULL, 0) /* used as type enum end marker */ \

131
Types.cpp
View File

@ -37,26 +37,36 @@ namespace solidity
TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) TypePointer Type::fromElementaryTypeName(Token::Value _typeToken)
{ {
solAssert(Token::isElementaryTypeName(_typeToken), "Elementary type name expected."); char const* tokenCstr = Token::toString(_typeToken);
solAssert(Token::isElementaryTypeName(_typeToken),
"Expected an elementary type name but got " + ((tokenCstr) ? std::string(Token::toString(_typeToken)) : ""));
if (Token::Int <= _typeToken && _typeToken <= Token::Hash256) if (Token::Int <= _typeToken && _typeToken <= Token::Bytes32)
{ {
int offset = _typeToken - Token::Int; int offset = _typeToken - Token::Int;
int bytes = offset % 33; int bytes = offset % 33;
if (bytes == 0) if (bytes == 0 && _typeToken != Token::Bytes0)
bytes = 32; bytes = 32;
int modifier = offset / 33; int modifier = offset / 33;
return make_shared<IntegerType>(bytes * 8, switch(modifier)
modifier == 0 ? IntegerType::Modifier::Signed : {
modifier == 1 ? IntegerType::Modifier::Unsigned : case 0:
IntegerType::Modifier::Hash); return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Signed);
case 1:
return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Unsigned);
case 2:
return make_shared<FixedBytesType>(bytes);
default:
solAssert(false, "Unexpected modifier value. Should never happen");
return TypePointer();
}
} }
else if (_typeToken == Token::Byte)
return make_shared<FixedBytesType>(1);
else if (_typeToken == Token::Address) else if (_typeToken == Token::Address)
return make_shared<IntegerType>(0, IntegerType::Modifier::Address); return make_shared<IntegerType>(0, IntegerType::Modifier::Address);
else if (_typeToken == Token::Bool) else if (_typeToken == Token::Bool)
return make_shared<BoolType>(); return make_shared<BoolType>();
else if (Token::String0 <= _typeToken && _typeToken <= Token::String32)
return make_shared<StaticStringType>(int(_typeToken) - int(Token::String0));
else if (_typeToken == Token::Bytes) else if (_typeToken == Token::Bytes)
return make_shared<ArrayType>(ArrayType::Location::Storage); return make_shared<ArrayType>(ArrayType::Location::Storage);
else else
@ -123,7 +133,7 @@ TypePointer Type::forLiteral(Literal const& _literal)
return make_shared<IntegerConstantType>(_literal); return make_shared<IntegerConstantType>(_literal);
case Token::StringLiteral: case Token::StringLiteral:
//@todo put larger strings into dynamic strings //@todo put larger strings into dynamic strings
return StaticStringType::smallestTypeForLiteral(_literal.getValue()); return FixedBytesType::smallestTypeForLiteral(_literal.getValue());
default: default:
return shared_ptr<Type>(); return shared_ptr<Type>();
} }
@ -159,8 +169,6 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return false; return false;
if (isAddress()) if (isAddress())
return convertTo.isAddress(); return convertTo.isAddress();
else if (isHash())
return convertTo.isHash();
else if (isSigned()) else if (isSigned())
return convertTo.isSigned(); return convertTo.isSigned();
else else
@ -169,14 +177,10 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (_convertTo.getCategory() == Category::String)
{
StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo);
return isHash() && (m_bits == convertTo.getNumBytes() * 8);
}
return _convertTo.getCategory() == getCategory() || return _convertTo.getCategory() == getCategory() ||
_convertTo.getCategory() == Category::Contract || _convertTo.getCategory() == Category::Contract ||
_convertTo.getCategory() == Category::Enum; _convertTo.getCategory() == Category::Enum ||
_convertTo.getCategory() == Category::FixedBytes;
} }
TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
@ -187,16 +191,10 @@ TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
// no further unary operators for addresses // no further unary operators for addresses
else if (isAddress()) else if (isAddress())
return TypePointer(); return TypePointer();
// "~" is ok for all other types // for non-address integers, we allow +, -, ++ and --
else if (_operator == Token::BitNot)
return shared_from_this();
// nothing else for hashes
else if (isHash())
return TypePointer();
// for non-hash integers, we allow +, -, ++ and --
else if (_operator == Token::Add || _operator == Token::Sub || else if (_operator == Token::Add || _operator == Token::Sub ||
_operator == Token::Inc || _operator == Token::Dec || _operator == Token::Inc || _operator == Token::Dec ||
_operator == Token::After) _operator == Token::After || _operator == Token::BitNot)
return shared_from_this(); return shared_from_this();
else else
return TypePointer(); return TypePointer();
@ -214,7 +212,7 @@ string IntegerType::toString() const
{ {
if (isAddress()) if (isAddress())
return "address"; return "address";
string prefix = isHash() ? "hash" : (isSigned() ? "int" : "uint"); string prefix = isSigned() ? "int" : "uint";
return prefix + dev::toString(m_bits); return prefix + dev::toString(m_bits);
} }
@ -230,14 +228,11 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe
// All integer types can be compared // All integer types can be compared
if (Token::isCompareOp(_operator)) if (Token::isCompareOp(_operator))
return commonType; return commonType;
// Nothing else can be done with addresses
// Nothing else can be done with addresses, but hashes can receive bit operators
if (commonType->isAddress()) if (commonType->isAddress())
return TypePointer(); return TypePointer();
else if (commonType->isHash() && !Token::isBitOp(_operator))
return TypePointer(); return commonType;
else
return commonType;
} }
const MemberList IntegerType::AddressMemberList = const MemberList IntegerType::AddressMemberList =
@ -284,8 +279,17 @@ IntegerConstantType::IntegerConstantType(Literal const& _literal)
bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{ {
TypePointer integerType = getIntegerType(); shared_ptr<IntegerType const> integerType = getIntegerType();
return integerType && integerType->isImplicitlyConvertibleTo(_convertTo); if (!integerType)
return false;
if (_convertTo.getCategory() == Category::FixedBytes)
{
FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo);
return convertTo.getNumBytes() * 8 >= integerType->getNumBits();
}
return integerType->isImplicitlyConvertibleTo(_convertTo);
} }
bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const
@ -433,50 +437,73 @@ shared_ptr<IntegerType const> IntegerConstantType::getIntegerType() const
: IntegerType::Modifier::Unsigned); : IntegerType::Modifier::Unsigned);
} }
shared_ptr<StaticStringType> StaticStringType::smallestTypeForLiteral(string const& _literal) shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal)
{ {
if (_literal.length() <= 32) if (_literal.length() <= 32)
return make_shared<StaticStringType>(_literal.length()); return make_shared<FixedBytesType>(_literal.length());
return shared_ptr<StaticStringType>(); return shared_ptr<FixedBytesType>();
} }
StaticStringType::StaticStringType(int _bytes): m_bytes(_bytes) FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes)
{ {
solAssert(m_bytes >= 0 && m_bytes <= 32, solAssert(m_bytes >= 0 && m_bytes <= 32,
"Invalid byte number for static string type: " + dev::toString(m_bytes)); "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes));
} }
bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (_convertTo.getCategory() != getCategory()) if (_convertTo.getCategory() != getCategory())
return false; return false;
StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo); FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo);
return convertTo.m_bytes >= m_bytes; return convertTo.m_bytes >= m_bytes;
} }
bool StaticStringType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (_convertTo.getCategory() == getCategory())
return true;
if (_convertTo.getCategory() == Category::Integer) if (_convertTo.getCategory() == Category::Integer)
{ {
IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits())) if (m_bytes * 8 <= convertTo.getNumBits())
return true; return true;
} }
return false; return _convertTo.getCategory() == Category::Contract ||
_convertTo.getCategory() == getCategory();
} }
bool StaticStringType::operator==(Type const& _other) const TypePointer FixedBytesType::unaryOperatorResult(Token::Value _operator) const
{
// "delete" and "~" is okay for FixedBytesType
if (_operator == Token::Delete)
return make_shared<VoidType>();
else if (_operator == Token::BitNot)
return shared_from_this();
return TypePointer();
}
TypePointer FixedBytesType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
auto commonType = dynamic_pointer_cast<FixedBytesType const>(Type::commonType(shared_from_this(), _other));
if (!commonType)
return TypePointer();
// FixedBytes can be compared and have bitwise operators applied to them
if (Token::isCompareOp(_operator) || Token::isBitOp(_operator))
return commonType;
return TypePointer();
}
bool FixedBytesType::operator==(Type const& _other) const
{ {
if (_other.getCategory() != getCategory()) if (_other.getCategory() != getCategory())
return false; return false;
StaticStringType const& other = dynamic_cast<StaticStringType const&>(_other); FixedBytesType const& other = dynamic_cast<FixedBytesType const&>(_other);
return other.m_bytes == m_bytes; return other.m_bytes == m_bytes;
} }
u256 StaticStringType::literalValue(const Literal* _literal) const u256 FixedBytesType::literalValue(const Literal* _literal) const
{ {
solAssert(_literal, ""); solAssert(_literal, "");
u256 value = 0; u256 value = 0;
@ -1124,7 +1151,7 @@ MagicType::MagicType(MagicType::Kind _kind):
case Kind::Block: case Kind::Block:
m_members = MemberList({{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, m_members = MemberList({{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"timestamp", make_shared<IntegerType>(256)}, {"timestamp", make_shared<IntegerType>(256)},
{"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"hash"}, FunctionType::Location::BlockHash)}, {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)},
{"difficulty", make_shared<IntegerType>(256)}, {"difficulty", make_shared<IntegerType>(256)},
{"number", make_shared<IntegerType>(256)}, {"number", make_shared<IntegerType>(256)},
{"gaslimit", make_shared<IntegerType>(256)}}); {"gaslimit", make_shared<IntegerType>(256)}});

27
Types.h
View File

@ -77,12 +77,12 @@ public:
enum class Category enum class Category
{ {
Integer, IntegerConstant, Bool, Real, Array, Integer, IntegerConstant, Bool, Real, Array,
String, Contract, Struct, Function, Enum, FixedBytes, Contract, Struct, Function, Enum,
Mapping, Void, TypeType, Modifier, Magic Mapping, Void, TypeType, Modifier, Magic
}; };
///@{ /// @{
///@name Factory functions /// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type. /// Factory functions that convert an AST @ref TypeName to a Type.
static TypePointer fromElementaryTypeName(Token::Value _typeToken); static TypePointer fromElementaryTypeName(Token::Value _typeToken);
static TypePointer fromElementaryTypeName(std::string const& _name); static TypePointer fromElementaryTypeName(std::string const& _name);
@ -158,14 +158,14 @@ protected:
}; };
/** /**
* Any kind of integer type including hash and address. * Any kind of integer type (signed, unsigned, address).
*/ */
class IntegerType: public Type class IntegerType: public Type
{ {
public: public:
enum class Modifier enum class Modifier
{ {
Unsigned, Signed, Hash, Address Unsigned, Signed, Address
}; };
virtual Category getCategory() const override { return Category::Integer; } virtual Category getCategory() const override { return Category::Integer; }
@ -186,7 +186,6 @@ public:
virtual std::string toString() const override; virtual std::string toString() const override;
int getNumBits() const { return m_bits; } int getNumBits() const { return m_bits; }
bool isHash() const { return m_modifier == Modifier::Hash || m_modifier == Modifier::Address; }
bool isAddress() const { return m_modifier == Modifier::Address; } bool isAddress() const { return m_modifier == Modifier::Address; }
bool isSigned() const { return m_modifier == Modifier::Signed; } bool isSigned() const { return m_modifier == Modifier::Signed; }
@ -232,27 +231,29 @@ private:
}; };
/** /**
* String type with fixed length, up to 32 bytes. * Bytes type with fixed length of up to 32 bytes.
*/ */
class StaticStringType: public Type class FixedBytesType: public Type
{ {
public: public:
virtual Category getCategory() const override { return Category::String; } virtual Category getCategory() const override { return Category::FixedBytes; }
/// @returns the smallest string type for the given literal or an empty pointer /// @returns the smallest bytes type for the given literal or an empty pointer
/// if no type fits. /// if no type fits.
static std::shared_ptr<StaticStringType> smallestTypeForLiteral(std::string const& _literal); static std::shared_ptr<FixedBytesType> smallestTypeForLiteral(std::string const& _literal);
explicit StaticStringType(int _bytes); explicit FixedBytesType(int _bytes);
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; }
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual std::string toString() const override { return "string" + dev::toString(m_bytes); } virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); }
virtual u256 literalValue(Literal const* _literal) const override; virtual u256 literalValue(Literal const* _literal) const override;
int getNumBytes() const { return m_bytes; } int getNumBytes() const { return m_bytes; }