Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth 2020-11-03 13:59:25 +01:00
commit 5ffee049fa
206 changed files with 1420 additions and 862 deletions

View File

@ -25,6 +25,9 @@ AST Changes:
### 0.7.5 (unreleased)
Language Features:
* Ability to select the abi coder using ``pragma abicoder v1`` and ``pragma abicoder v2``.
Compiler Features:
* SMTChecker: Add division by zero checks in the CHC engine.
* SMTChecker: Support ``selector`` for expressions with value known at compile-time.
@ -35,6 +38,10 @@ Bugfixes:
* SMTChecker: Fix internal error on conversion from string literal to byte.
* SMTChecker: Fix internal error when using tuples of rational literals inside the conditional operator.
* SMTChecker: Fix internal error when assigning state variable via contract's name.
* SMTChecker: Fix incorrect counterexamples reported by the CHC engine.
* SMTChecker: Fix false negative in modifier applied multiple times.
* SMTChecker: Fix internal error in the BMC engine when inherited contract from a different source unit has private state variables.
* SMTChecker: Fix internal error when ``array.push()`` is used as the LHS of an assignment.
* Code generator: Fix missing creation dependency tracking for abstract contracts.

View File

@ -1,5 +1,5 @@
# The Solidity Contract-Oriented Programming Language
You can talk to us on [![solidity at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge). Questions, feedback and suggestions are welcome!
You can talk to us on [![solidity at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge). Questions, feedback, and suggestions are welcome!
Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform.
@ -19,10 +19,10 @@ Solidity is a statically typed, contract-oriented, high-level language for imple
Solidity is a statically-typed curly-braces programming language designed for developing smart contracts
that run on the Ethereum Virtual Machine. Smart contracts are programs that are executed inside a peer-to-peer
network where nobody has special authority over the execution, and thus they allow to implement tokens of value,
ownership, voting and other kinds of logics.
ownership, voting, and other kinds of logic.
When deploying contracts, you should use the latest released version of
Solidity. This is because breaking changes as well as new features and bug fixes are
Solidity. This is because breaking changes, as well as new features and bug fixes are
introduced regularly. We currently use a 0.x version
number [to indicate this fast pace of change](https://semver.org/#spec-item-4).
@ -46,7 +46,7 @@ contract HelloWorld {
}
```
To get started with Solidity, you can use [Remix](https://remix.ethereum.org/), which is an
To get started with Solidity, you can use [Remix](https://remix.ethereum.org/), which is a
browser-based IDE. Here are some example contracts:
1. [Voting](https://solidity.readthedocs.io/en/latest/solidity-by-example.html#voting)

View File

@ -132,7 +132,7 @@ Yul Optimizer
Together with the legacy bytecode optimizer, the :doc:`Yul <yul>` optimizer is now enabled by default when you call the compiler
with ``--optimize``. It can be disabled by calling the compiler with ``--no-optimize-yul``.
This mostly affects code that uses ABIEncoderV2.
This mostly affects code that uses ABI coder v2.
C API Changes
~~~~~~~~~~~~~

View File

@ -589,8 +589,8 @@ As an example, the code
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.19 <0.9.0;
pragma experimental ABIEncoderV2;
pragma solidity >0.7.4 <0.9.0;
pragma abicoder v2;
contract Test {
struct S { uint a; uint[] b; T[] c; }

View File

@ -67,8 +67,8 @@ Function parameters can be used as any other local variable and they can also be
An :ref:`external function<external-function-calls>` cannot accept a
multi-dimensional array as an input
parameter. This functionality is possible if you enable the new
``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file.
parameter. This functionality is possible if you enable the ABI coder v2
by adding ``pragma abicoder v2;`` to your source file.
An :ref:`internal function<external-function-calls>` can accept a
multi-dimensional array without enabling the feature.
@ -128,8 +128,8 @@ you must provide return values together with the return statement.
.. note::
You cannot return some types from non-internal functions, notably
multi-dimensional dynamic arrays and structs. If you enable the
new ``ABIEncoderV2`` feature by adding ``pragma experimental
ABIEncoderV2;`` to your source file then more types are available, but
ABI coder v2 by adding ``pragma abicoder v2;``
to your source file then more types are available, but
``mapping`` types are still limited to inside a single contract and you
cannot transfer them.

View File

@ -239,7 +239,7 @@ For macOS builds, ensure that you have the latest version of
`Xcode installed <https://developer.apple.com/xcode/download/>`_.
This contains the `Clang C++ compiler <https://en.wikipedia.org/wiki/Clang>`_, the
`Xcode IDE <https://en.wikipedia.org/wiki/Xcode>`_ and other Apple development
tools which are required for building C++ applications on OS X.
tools that are required for building C++ applications on OS X.
If you are installing Xcode for the first time, or have just installed a new
version then you will need to agree to the license before you can do
command-line builds:

View File

@ -88,6 +88,41 @@ these follow the same syntax used by `npm <https://docs.npmjs.com/misc/semver>`_
required by the pragma. If it does not match, the compiler issues
an error.
ABI Coder Pragma
----------------
By using ``pragma abicoder v1`` or ``pragma abicoder v2`` you can
select between the two implementations of the ABI encoder and decoder.
The new ABI coder (v2) is able to encode and decode arbitrarily nested
arrays and structs. It might produce less optimal code and has not
received as much testing as the old encoder, but is considered
non-experimental as of Solidity 0.6.0. You still have to explicitly
activate it using ``pragma abicoder v2;``. Since it will be
activated by default starting from Solidity 0.8.0, there is the option to select
the old coder using ``pragma abicoder v1;``.
The set of types supported by the new encoder is a strict superset of
the ones supported by the old one. Contracts that use it can interact with ones
that do not without limitations. The reverse is possible only as long as the
non-``abicoder v2`` contract does not try to make calls that would require
decoding types only supported by the new encoder. The compiler can detect this
and will issue an error. Simply enabling ``abicoder v2`` for your contract is
enough to make the error go away.
.. note::
This pragma applies to all the code defined in the file where it is activated,
regardless of where that code ends up eventually. This means that a contract
whose source file is selected to compile with ABI coder v1
can still contain code that uses the new encoder
by inheriting it from another contract. This is allowed if the new types are only
used internally and not in external function signatures.
.. note::
Up to Solidity 0.7.4, it was possible to select the ABI coder v2
by using ``pragma experimental ABIEncoderV2``, but it was not possible
to explicitly select coder v1 because it was the default.
.. index:: ! pragma, experimental
.. _experimental_pragma:
@ -103,28 +138,9 @@ The following experimental pragmas are currently supported:
ABIEncoderV2
~~~~~~~~~~~~
The new ABI encoder is able to encode and decode arbitrarily nested
arrays and structs. It might produce less optimal code and has not
received as much testing as the old encoder, but is considered
non-experimental as of Solidity 0.6.0. You still have to explicitly
activate it using ``pragma experimental ABIEncoderV2;`` - we kept
the same pragma, even though it is not considered experimental
anymore.
The set of types supported by the new encoder is a strict superset of
the ones supported by the old one. Contracts that use it can interact with ones
that do not without limitations. The reverse is possible only as long as the
non-``ABIEncoderV2`` contract does not try to make calls that would require
decoding types only supported by the new encoder. The compiler can detect this
and will issue an error. Simply enabling ``ABIEncoderV2`` for your contract is
enough to make the error go away.
.. note::
This pragma applies to all the code defined in the file where it is activated,
regardless of where that code ends up eventually. This means that a contract
without the ``ABIEncoderV2`` pragma can still contain code that uses the new encoder
by inheriting it from another contract. This is allowed if the new types are only
used internally and not in external function signatures.
Because the ABI coder v2 is not considered experimental anymore,
it can be selected via ``pragma abicoder v2`` (please see above)
since Solidity 0.7.4.
.. _smt_checker:

View File

@ -629,26 +629,32 @@ types.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0;
pragma experimental ABIEncoderV2;
pragma experimental SMTChecker;
// This will report a warning
contract Aliasing
{
uint[] array;
uint[] array1;
uint[][] array2;
function f(
uint[] memory a,
uint[] memory b,
uint[][] memory c,
uint[] storage d
) internal view {
require(array[0] == 42);
require(a[0] == 2);
require(c[0][0] == 2);
require(d[0] == 2);
) internal {
array1[0] = 42;
a[0] = 2;
c[0][0] = 2;
b[0] = 1;
// Erasing knowledge about memory references should not
// erase knowledge about state variables.
assert(array[0] == 42);
assert(array1[0] == 42);
// However, an assignment to a storage reference will erase
// storage knowledge accordingly.
d[0] = 2;
// Fails as false positive because of the assignment above.
assert(array1[0] == 42);
// Fails because `a == b` is possible.
assert(a[0] == 2);
// Fails because `c[i] == b` is possible.
@ -656,6 +662,14 @@ types.
assert(d[0] == 2);
assert(b[0] == 1);
}
function g(
uint[] memory a,
uint[] memory b,
uint[][] memory c,
uint x
) public {
f(a, b, c, array2[x]);
}
}
After the assignment to ``b[0]``, we need to clear knowledge about ``a`` since

View File

@ -290,7 +290,7 @@ Array Members
.. note::
To use arrays of arrays in external (instead of public) functions, you need to
activate ABIEncoderV2.
activate ABI coder v2.
.. note::
In EVM versions before Byzantium, it was not possible to access

View File

@ -231,7 +231,7 @@ Input Description
"cse": false,
// Optimize representation of literal numbers and strings in code.
"constantOptimizer": false,
// The new Yul optimizer. Mostly operates on the code of ABIEncoderV2
// The new Yul optimizer. Mostly operates on the code of ABI coder v2
// and inline assembly.
// It is activated together with the global optimizer setting
// and can be deactivated here.
@ -321,8 +321,8 @@ Input Description
// evm.deployedBytecode.immutableReferences - Map from AST ids to bytecode ranges that reference immutables
// evm.methodIdentifiers - The list of function hashes
// evm.gasEstimates - Function gas estimates
// ewasm.wast - eWASM S-expressions format (not supported at the moment)
// ewasm.wasm - eWASM binary format (not supported at the moment)
// ewasm.wast - Ewasm in WebAssembly S-expressions format
// ewasm.wasm - Ewasm in WebAssembly binary format
//
// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
// target part of that output. Additionally, `*` can be used as a wildcard to request everything.
@ -486,7 +486,7 @@ Output Description
}
}
},
// eWASM related outputs
// Ewasm related outputs
"ewasm": {
// S-expressions format
"wast": "",

View File

@ -9,7 +9,7 @@ Yul
Yul (previously also called JULIA or IULIA) is an intermediate language that can be
compiled to bytecode for different backends.
Support for EVM 1.0, EVM 1.5 and eWASM is planned, and it is designed to
Support for EVM 1.0, EVM 1.5 and Ewasm is planned, and it is designed to
be a usable common denominator of all three
platforms. It can already be used in stand-alone mode and
for "inline assembly" inside Solidity
@ -1028,7 +1028,7 @@ An example Yul Object is shown below:
// executing code is the constructor code)
size := datasize("runtime")
offset := allocate(size)
// This will turn into a memory->memory copy for eWASM and
// This will turn into a memory->memory copy for Ewasm and
// a codecopy for EVM
datacopy(offset, dataoffset("runtime"), size)
return(offset, size)

View File

@ -450,7 +450,7 @@ void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _c
void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _contract)
{
if (_contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
if (*_contract.sourceUnit().annotation().useABICoderV2)
return;
if (_contract.isLibrary())
@ -469,7 +469,7 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _
{
solAssert(func.second->hasDeclaration(), "Function has no declaration?!");
if (!func.second->declaration().sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
if (!*func.second->declaration().sourceUnit().annotation().useABICoderV2)
continue;
auto const& currentLoc = func.second->declaration().location();
@ -489,9 +489,9 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _
errors,
std::string("Contract \"") +
_contract.name() +
"\" does not use ABIEncoderV2 but wants to inherit from a contract " +
"\" does not use ABI coder v2 but wants to inherit from a contract " +
"which uses types that require it. " +
"Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature."
"Use \"pragma abicoder v2;\" for the inheriting contract as well to enable the feature."
);
}

View File

@ -73,6 +73,8 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
// when reporting the warning, print the source name only
m_errorReporter.warning(3420_error, {-1, -1, _sourceUnit.location().source}, errorString);
}
if (!m_sourceUnit->annotation().useABICoderV2.set())
m_sourceUnit->annotation().useABICoderV2 = false;
m_sourceUnit = nullptr;
}
@ -113,9 +115,45 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
m_sourceUnit->annotation().experimentalFeatures.insert(feature);
if (!ExperimentalFeatureWithoutWarning.count(feature))
m_errorReporter.warning(2264_error, _pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments.");
if (feature == ExperimentalFeature::ABIEncoderV2)
{
if (m_sourceUnit->annotation().useABICoderV2.set())
{
if (!*m_sourceUnit->annotation().useABICoderV2)
m_errorReporter.syntaxError(
8273_error,
_pragma.location(),
"ABI coder v1 has already been selected through \"pragma abicoder v1\"."
);
}
else
m_sourceUnit->annotation().useABICoderV2 = true;
}
}
}
}
else if (_pragma.literals()[0] == "abicoder")
{
solAssert(m_sourceUnit, "");
if (
_pragma.literals().size() != 2 ||
!set<string>{"v1", "v2"}.count(_pragma.literals()[1])
)
m_errorReporter.syntaxError(
2745_error,
_pragma.location(),
"Expected either \"pragma abicoder v1\" or \"pragma abicoder v2\"."
);
else if (m_sourceUnit->annotation().useABICoderV2.set())
m_errorReporter.syntaxError(
3845_error,
_pragma.location(),
"ABI coder has already been selected for this source unit."
);
else
m_sourceUnit->annotation().useABICoderV2 = (_pragma.literals()[1] == "v2");
}
else if (_pragma.literals()[0] == "solidity")
{
vector<Token> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
@ -135,6 +173,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
}
else
m_errorReporter.syntaxError(4936_error, _pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
return true;
}

View File

@ -39,6 +39,7 @@ namespace solidity::frontend
* - issues deprecation warnings for unary '+'
* - issues deprecation warning for throw
* - whether the msize instruction is used and the Yul optimizer is enabled at the same time.
* - selection of the ABI coder through pragmas.
*/
class SyntaxChecker: private ASTConstVisitor
{

View File

@ -398,13 +398,13 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
m_errorReporter.typeError(4103_error, _var.location(), message);
}
else if (
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
!useABICoderV2() &&
!typeSupportedByOldABIEncoder(*type(_var), _function.libraryFunction())
)
{
string message =
"This type is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature.";
"This type is only supported in ABI coder v2. "
"Use \"pragma abicoder v2;\" to enable the feature.";
if (_function.isConstructor())
message +=
" Alternatively, make the contract abstract and supply the "
@ -570,7 +570,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
else if (_variable.visibility() >= Visibility::Public)
{
FunctionType getter(_variable);
if (!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
if (!useABICoderV2())
{
vector<string> unsupportedTypes;
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
@ -580,9 +580,9 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
m_errorReporter.typeError(
2763_error,
_variable.location(),
"The following types are only supported for getters in ABIEncoderV2: " +
"The following types are only supported for getters in ABI coder v2: " +
joinHumanReadable(unsupportedTypes) +
". Either remove \"public\" or use \"pragma experimental ABIEncoderV2;\" to enable the feature."
". Either remove \"public\" or use \"pragma abicoder v2;\" to enable the feature."
);
}
if (!getter.interfaceFunctionType())
@ -695,14 +695,14 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
if (
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
!useABICoderV2() &&
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
)
m_errorReporter.typeError(
3061_error,
var->location(),
"This type is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
"This type is only supported in ABI coder v2. "
"Use \"pragma abicoder v2;\" to enable the feature."
);
}
if (_eventDef.isAnonymous() && numIndexed > 4)
@ -1900,7 +1900,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
bool const isPacked = _functionType->kind() == FunctionType::Kind::ABIEncodePacked;
solAssert(_functionType->padArguments() != isPacked, "ABI function with unexpected padding");
bool const abiEncoderV2 = experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2);
bool const abiEncoderV2 = useABICoderV2();
// Check for named arguments
if (!_functionCall.names().empty())
@ -2192,7 +2192,7 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
_functionType->kind() == FunctionType::Kind::Creation ||
_functionType->kind() == FunctionType::Kind::Event;
if (callRequiresABIEncoding && !experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
if (callRequiresABIEncoding && !useABICoderV2())
{
solAssert(!isVariadic, "");
solAssert(parameterTypes.size() == arguments.size(), "");
@ -2208,8 +2208,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
2443_error,
paramArgMap[i]->location(),
"The type of this parameter, " + parameterTypes[i]->toString(true) + ", "
"is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
"is only supported in ABI coder v2. "
"Use \"pragma abicoder v2;\" to enable the feature."
);
}
@ -2222,8 +2222,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
2428_error,
_functionCall.location(),
"The type of return parameter " + toString(i + 1) + ", " + returnParameterTypes[i]->toString(true) + ", "
"is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
"is only supported in ABI coder v2. "
"Use \"pragma abicoder v2;\" to enable the feature."
);
}
}
@ -2340,7 +2340,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
{
returnTypes = typeCheckABIDecodeAndRetrieveReturnType(
_functionCall,
experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
useABICoderV2()
);
break;
}
@ -3414,10 +3414,11 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
m_errorReporter.typeError(errorId, _expression.location(), description);
}
bool TypeChecker::experimentalFeatureActive(ExperimentalFeature _feature) const
bool TypeChecker::useABICoderV2() const
{
solAssert(m_currentSourceUnit, "");
if (m_currentContract)
solAssert(m_currentSourceUnit == &m_currentContract->sourceUnit(), "");
return m_currentSourceUnit->annotation().experimentalFeatures.count(_feature);
return *m_currentSourceUnit->annotation().useABICoderV2;
}

View File

@ -168,7 +168,7 @@ private:
/// Runs type checks on @a _expression to infer its type and then checks that it is an LValue.
void requireLValue(Expression const& _expression, bool _ordinaryAssignment);
bool experimentalFeatureActive(ExperimentalFeature _feature) const;
bool useABICoderV2() const;
/// @returns the current scope that can have function or type definitions.
/// This is either a contract or a source unit.

View File

@ -84,16 +84,6 @@ TypePointer ImportDirective::type() const
return TypeProvider::module(*annotation().sourceUnit);
}
vector<VariableDeclaration const*> ContractDefinition::stateVariablesIncludingInherited() const
{
vector<VariableDeclaration const*> stateVars;
for (auto const& contract: annotation().linearizedBaseContracts)
for (auto var: contract->stateVariables())
if (*contract == *this || var->isVisibleInDerivedContracts())
stateVars.push_back(var);
return stateVars;
}
bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
{
return util::contains(annotation().linearizedBaseContracts, &_base);

View File

@ -500,7 +500,6 @@ public:
std::vector<StructDefinition const*> definedStructs() const { return filteredNodes<StructDefinition>(m_subNodes); }
std::vector<EnumDefinition const*> definedEnums() const { return filteredNodes<EnumDefinition>(m_subNodes); }
std::vector<VariableDeclaration const*> stateVariables() const { return filteredNodes<VariableDeclaration>(m_subNodes); }
std::vector<VariableDeclaration const*> stateVariablesIncludingInherited() const;
std::vector<ModifierDefinition const*> functionModifiers() const { return filteredNodes<ModifierDefinition>(m_subNodes); }
std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); }
std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); }

View File

@ -94,6 +94,7 @@ struct SourceUnitAnnotation: ASTAnnotation
SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols;
/// Experimental features.
std::set<ExperimentalFeature> experimentalFeatures;
SetOnce<bool> useABICoderV2;
};
struct ScopableAnnotation

View File

@ -1172,7 +1172,7 @@ void ArrayUtils::accessCallDataArrayElement(ArrayType const& _arrayType, bool _d
if (
!_arrayType.isByteArray() &&
_arrayType.baseType()->storageBytes() < 32 &&
m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
m_context.useABICoderV2()
)
{
m_context << u256(32);

View File

@ -77,11 +77,8 @@ public:
langutil::EVMVersion const& evmVersion() const { return m_evmVersion; }
/// Update currently enabled set of experimental features.
void setExperimentalFeatures(std::set<ExperimentalFeature> const& _features) { m_experimentalFeatures = _features; }
std::set<ExperimentalFeature> const& experimentalFeaturesActive() const { return m_experimentalFeatures; }
/// @returns true if the given feature is enabled.
bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
void setUseABICoderV2(bool _value) { m_useABICoderV2 = _value; }
bool useABICoderV2() const { return m_useABICoderV2; }
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addImmutable(VariableDeclaration const& _declaration);
@ -365,8 +362,7 @@ private:
/// Version of the EVM to compile against.
langutil::EVMVersion m_evmVersion;
RevertStrings const m_revertStrings;
/// Activated experimental features.
std::set<ExperimentalFeature> m_experimentalFeatures;
bool m_useABICoderV2 = false;
/// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers;
/// Storage offsets of state variables

View File

@ -230,7 +230,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory)
{
/// Stack: <source_offset> <length>
if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
if (m_context.useABICoderV2())
{
// Use the new Yul-based decoding function
auto stackHeightBefore = m_context.stackHeight();
@ -412,7 +412,7 @@ void CompilerUtils::encodeToMemory(
)
{
// stack: <v1> <v2> ... <vn> <mem>
bool const encoderV2 = m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2);
bool const encoderV2 = m_context.useABICoderV2();
TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes;
solAssert(targetTypes.size() == _givenTypes.size(), "");
for (TypePointer& t: targetTypes)

View File

@ -127,7 +127,7 @@ void ContractCompiler::initializeContext(
map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers
)
{
m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setUseABICoderV2(*_contract.sourceUnit().annotation().useABICoderV2);
m_context.setOtherCompilers(_otherCompilers);
m_context.setMostDerivedContract(_contract);
if (m_runtimeCompiler)
@ -1349,13 +1349,13 @@ void ContractCompiler::appendModifierOrFunctionCode()
{
m_context.setArithmetic(Arithmetic::Checked);
std::set<ExperimentalFeature> experimentalFeaturesOutside = m_context.experimentalFeaturesActive();
m_context.setExperimentalFeatures(codeBlock->sourceUnit().annotation().experimentalFeatures);
bool coderV2Outside = m_context.useABICoderV2();
m_context.setUseABICoderV2(*codeBlock->sourceUnit().annotation().useABICoderV2);
m_returnTags.emplace_back(m_context.newTag(), m_context.stackHeight());
codeBlock->accept(*this);
m_context.setExperimentalFeatures(experimentalFeaturesOutside);
m_context.setUseABICoderV2(coderV2Outside);
solAssert(!m_returnTags.empty(), "");
m_context << m_returnTags.back().first;

View File

@ -1676,7 +1676,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
{
solAssert(memberType->calldataEncodedSize() > 0, "");
solAssert(memberType->storageBytes() <= 32, "");
if (memberType->storageBytes() < 32 && m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
if (memberType->storageBytes() < 32 && m_context.useABICoderV2())
{
m_context << u256(32);
CompilerUtils(m_context).abiDecodeV2({memberType}, false);
@ -2522,7 +2522,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
// memory pointer), but kept references to the return data for
// (statically-sized) arrays
bool needToUpdateFreeMemoryPtr = false;
if (dynamicReturnSize || m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
if (dynamicReturnSize || m_context.useABICoderV2())
needToUpdateFreeMemoryPtr = true;
else
for (auto const& retType: returnTypes)

View File

@ -1556,13 +1556,14 @@ string YulUtilFunctions::clearStorageStructFunction(StructType const& _type)
});
}
string YulUtilFunctions::copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType)
string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
""
);
solUnimplementedAssert(!_fromType.isByteArray(), "");
if (_fromType.isByteArray())
return copyByteArrayToStorageFunction(_fromType, _toType);
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
string functionName = "copy_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
@ -1655,6 +1656,84 @@ string YulUtilFunctions::copyArrayToStorage(ArrayType const& _fromType, ArrayTyp
});
}
string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
{
solAssert(
*_fromType.copyForLocation(_toType.location(), _toType.isPointer()) == dynamic_cast<ReferenceType const&>(_toType),
""
);
solAssert(_fromType.isByteArray(), "");
solAssert(_toType.isByteArray(), "");
solUnimplementedAssert(!_fromType.dataStoredIn(DataLocation::Storage), "");
string functionName = "copy_byte_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
return m_functionCollector.createFunction(functionName, [&](){
Whiskers templ(R"(
function <functionName>(slot, src<?fromCalldata>, len</fromCalldata>) {
let newLen := <arrayLength>(src<?fromCalldata>, len</fromCalldata>)
// Make sure array length is sane
if gt(newLen, 0xffffffffffffffff) { <panic>() }
let oldLen := <byteArrayLength>(sload(slot))
<?fromMemory>
src := add(src, 0x20)
</fromMemory>
// This is not needed in all branches.
let dstDataArea
if or(gt(oldLen, 31), gt(newLen, 31)) {
dstDataArea := <dstDataLocation>(slot)
}
if gt(oldLen, 31) {
// potentially truncate data
let deleteStart := add(dstDataArea, div(add(newLen, 31), 32))
if lt(newLen, 32) { deleteStart := dstDataArea }
<clearStorageRange>(deleteStart, add(dstDataArea, div(add(oldLen, 31), 32)))
}
switch gt(newLen, 31)
case 1 {
let loopEnd := and(newLen, not(0x1f))
let dstPtr := dstDataArea
let i := 0
for { } lt(i, loopEnd) { i := add(i, 32) } {
sstore(dstPtr, <readFromCalldataOrMemory>(add(src, i)))
dstPtr := add(dstPtr, 1)
}
if lt(loopEnd, newLen) {
let lastValue := <readFromCalldataOrMemory>(add(src, i))
sstore(dstPtr, <maskBytes>(lastValue, and(newLen, 0x1f)))
}
sstore(slot, add(mul(newLen, 2), 1))
}
default {
let value := 0
if newLen {
value := <readFromCalldataOrMemory>(src)
}
sstore(slot, <byteArrayCombineShort>(value, newLen))
}
}
)");
templ("functionName", functionName);
bool fromCalldata = _fromType.dataStoredIn(DataLocation::CallData);
templ("fromMemory", _fromType.dataStoredIn(DataLocation::Memory));
templ("fromCalldata", fromCalldata);
templ("arrayLength", arrayLengthFunction(_fromType));
templ("panic", panicFunction(PanicCode::ResourceError));
templ("byteArrayLength", extractByteArrayLengthFunction());
templ("dstDataLocation", arrayDataAreaFunction(_toType));
templ("clearStorageRange", clearStorageRangeFunction(*_toType.baseType()));
templ("readFromCalldataOrMemory", readFromMemoryOrCalldata(*TypeProvider::uint256(), fromCalldata));
templ("maskBytes", maskBytesFunctionDynamic());
templ("byteArrayCombineShort", shortByteArrayEncodeUsedAreaSetLengthFunction());
return templ.render();
});
}
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
@ -2207,8 +2286,9 @@ string YulUtilFunctions::updateStorageValueFunction(
solAssert(_toType.storageBytes() > 0, "Invalid storage bytes size.");
return Whiskers(R"(
function <functionName>(slot, <offset>value) {
sstore(slot, <update>(sload(slot), <offset><prepare>(value)))
function <functionName>(slot, <offset><fromValues>) {
let <toValues> := <convert>(<fromValues>)
sstore(slot, <update>(sload(slot), <offset><prepare>(<toValues>)))
}
)")
@ -2219,6 +2299,9 @@ string YulUtilFunctions::updateStorageValueFunction(
updateByteSliceFunctionDynamic(_toType.storageBytes())
)
("offset", _offset.has_value() ? "" : "offset, ")
("convert", conversionFunction(_fromType, _toType))
("fromValues", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()))
("toValues", suffixedVariableNameList("convertedValue_", 0, _toType.sizeOnStack()))
("prepare", prepareStoreFunction(_toType))
.render();
}
@ -2248,7 +2331,7 @@ string YulUtilFunctions::updateStorageValueFunction(
)");
templ("functionName", functionName);
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
templ("copyArrayToStorage", copyArrayToStorage(
templ("copyArrayToStorage", copyArrayToStorageFunction(
dynamic_cast<ArrayType const&>(_fromType),
dynamic_cast<ArrayType const&>(_toType)
));
@ -2478,9 +2561,16 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
return templ.render();
}
bool leftAligned = false;
if (
_type.category() != Type::Category::Function ||
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
)
leftAligned = _type.leftAligned();
if (storageBytes == 32)
templ("cleaned", "value");
else if (_type.leftAligned())
else if (leftAligned)
templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
else
templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
@ -2491,7 +2581,8 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
string YulUtilFunctions::prepareStoreFunction(Type const& _type)
{
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::Internal, "");
string functionName = "prepare_store_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
@ -2718,12 +2809,13 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(addr, functionId) -> outAddr, outFunctionId {
outAddr := addr
function <functionName>(<?external>addr, </external>functionId) -> <?external>outAddr, </external>outFunctionId {
<?external>outAddr := addr</external>
outFunctionId := functionId
}
)")
("functionName", functionName)
("external", fromType.kind() == FunctionType::Kind::External)
.render();
});
}

View File

@ -205,7 +205,11 @@ public:
/// @returns the name of a function that will copy array from calldata or memory to storage
/// signature (to_slot, from_ptr) ->
std::string copyArrayToStorage(ArrayType const& _fromType, ArrayType const& _toType);
std::string copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
/// @returns the name of a function that will copy a byte array from calldata or memory to storage
/// signature (to_slot, from_ptr) ->
std::string copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
/// Returns the name of a function that will convert a given length to the
/// size in memory (number of storage slots or calldata/memory bytes) it

View File

@ -425,7 +425,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
"(" <<
("add(" + mpos + ", " + to_string(i * arrayType.memoryStride()) + ")") <<
", " <<
converted.name() <<
converted.commaSeparatedList() <<
")\n";
}
}
@ -2543,47 +2543,50 @@ string IRGeneratorForStatements::binaryOperation(
!TokenTraits::isShiftOp(_operator),
"Have to use specific shift operation function for shifts."
);
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
string fun;
if (TokenTraits::isBitOp(_operator))
{
string fun;
solAssert(
_type.category() == Type::Category::Integer ||
_type.category() == Type::Category::FixedBytes,
"");
switch (_operator)
{
case Token::BitOr: fun = "or"; break;
case Token::BitXor: fun = "xor"; break;
case Token::BitAnd: fun = "and"; break;
default: break;
}
}
else if (TokenTraits::isArithmeticOp(_operator))
{
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
solAssert(type, "");
bool checked = m_context.arithmetic() == Arithmetic::Checked;
switch (_operator)
{
case Token::Add:
fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type);
break;
case Token::Sub:
fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type);
break;
case Token::Mul:
fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type);
break;
case Token::Div:
fun = checked ? m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type);
break;
case Token::Mod:
fun = m_utils.intModFunction(*type);
break;
case Token::BitOr:
fun = "or";
break;
case Token::BitXor:
fun = "xor";
break;
case Token::BitAnd:
fun = "and";
break;
default:
break;
case Token::Add:
fun = checked ? m_utils.overflowCheckedIntAddFunction(*type) : m_utils.wrappingIntAddFunction(*type);
break;
case Token::Sub:
fun = checked ? m_utils.overflowCheckedIntSubFunction(*type) : m_utils.wrappingIntSubFunction(*type);
break;
case Token::Mul:
fun = checked ? m_utils.overflowCheckedIntMulFunction(*type) : m_utils.wrappingIntMulFunction(*type);
break;
case Token::Div:
fun = checked ? m_utils.overflowCheckedIntDivFunction(*type) : m_utils.wrappingIntDivFunction(*type);
break;
case Token::Mod:
fun = m_utils.intModFunction(*type);
break;
default:
break;
}
solUnimplementedAssert(!fun.empty(), "");
return fun + "(" + _left + ", " + _right + ")\n";
}
else
solUnimplementedAssert(false, "");
return {};
solUnimplementedAssert(!fun.empty(), "Type: " + _type.toString());
return fun + "(" + _left + ", " + _right + ")\n";
}
std::string IRGeneratorForStatements::shiftOperation(

View File

@ -134,7 +134,7 @@ void CHC::endVisit(ContractDefinition const& _contract)
setCurrentBlock(*m_constructorSummaryPredicate);
addAssertVerificationTarget(m_currentContract, m_currentBlock, smtutil::Expression(true), errorFlag().currentValue());
m_queryPlaceholders[&_contract].push_back({smtutil::Expression(true), errorFlag().currentValue(), m_currentBlock});
connectBlocks(m_currentBlock, interface(), errorFlag().currentValue() == 0);
SMTEncoder::endVisit(_contract);
@ -231,16 +231,14 @@ void CHC::endVisit(FunctionDefinition const& _function)
auto assertionError = errorFlag().currentValue();
auto sum = summary(_function);
connectBlocks(m_currentBlock, sum);
auto iface = interface();
setCurrentBlock(*m_interfaces.at(m_currentContract));
auto ifacePre = smt::interfacePre(*m_interfaces.at(m_currentContract), *m_currentContract, m_context);
if (_function.isPublic())
{
auto txConstraints = m_context.state().txConstraints(_function);
addAssertVerificationTarget(&_function, ifacePre, txConstraints && sum, assertionError);
m_queryPlaceholders[&_function].push_back({txConstraints && sum, assertionError, ifacePre});
connectBlocks(ifacePre, iface, txConstraints && sum && (assertionError == 0));
}
}
@ -512,30 +510,15 @@ void CHC::visitAssert(FunctionCall const& _funCall)
solAssert(m_currentContract, "");
solAssert(m_currentFunction, "");
if (m_currentFunction->isConstructor())
m_functionAssertions[m_currentContract].insert(&_funCall);
else
m_functionAssertions[m_currentFunction].insert(&_funCall);
auto previousError = errorFlag().currentValue();
errorFlag().increaseIndex();
connectBlocks(
m_currentBlock,
m_currentFunction->isConstructor() ? summary(*m_currentContract) : summary(*m_currentFunction),
currentPathConditions() && !m_context.expression(*args.front())->currentValue() && (
errorFlag().currentValue() == newErrorId(_funCall)
)
);
m_context.addAssertion(errorFlag().currentValue() == previousError);
auto errorCondition = !m_context.expression(*args.front())->currentValue();
verificationTargetEncountered(&_funCall, VerificationTarget::Type::Assert, errorCondition);
}
void CHC::visitAddMulMod(FunctionCall const& _funCall)
{
solAssert(_funCall.arguments().at(2), "");
addVerificationTarget(_funCall, VerificationTarget::Type::DivByZero, expr(*_funCall.arguments().at(2)) == 0);
verificationTargetEncountered(&_funCall, VerificationTarget::Type::DivByZero, expr(*_funCall.arguments().at(2)) == 0);
SMTEncoder::visitAddMulMod(_funCall);
}
@ -634,7 +617,7 @@ void CHC::makeArrayPopVerificationTarget(FunctionCall const& _arrayPop)
auto symbArray = dynamic_pointer_cast<SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
solAssert(symbArray, "");
addVerificationTarget(_arrayPop, VerificationTarget::Type::PopEmptyArray, symbArray->length() <= 0);
verificationTargetEncountered(&_arrayPop, VerificationTarget::Type::PopEmptyArray, symbArray->length() <= 0);
}
pair<smtutil::Expression, smtutil::Expression> CHC::arithmeticOperation(
@ -646,7 +629,7 @@ pair<smtutil::Expression, smtutil::Expression> CHC::arithmeticOperation(
)
{
if (_op == Token::Mod || _op == Token::Div)
addVerificationTarget(_expression, VerificationTarget::Type::DivByZero, _right == 0);
verificationTargetEncountered(&_expression, VerificationTarget::Type::DivByZero, _right == 0);
auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression);
@ -662,16 +645,16 @@ pair<smtutil::Expression, smtutil::Expression> CHC::arithmeticOperation(
return values;
if (_op == Token::Div)
addVerificationTarget(_expression, VerificationTarget::Type::Overflow, values.second > intType->maxValue());
verificationTargetEncountered(&_expression, VerificationTarget::Type::Overflow, values.second > intType->maxValue());
else if (intType->isSigned())
{
addVerificationTarget(_expression, VerificationTarget::Type::Underflow, values.second < intType->minValue());
addVerificationTarget(_expression, VerificationTarget::Type::Overflow, values.second > intType->maxValue());
verificationTargetEncountered(&_expression, VerificationTarget::Type::Underflow, values.second < intType->minValue());
verificationTargetEncountered(&_expression, VerificationTarget::Type::Overflow, values.second > intType->maxValue());
}
else if (_op == Token::Sub)
addVerificationTarget(_expression, VerificationTarget::Type::Underflow, values.second < intType->minValue());
verificationTargetEncountered(&_expression, VerificationTarget::Type::Underflow, values.second < intType->minValue());
else if (_op == Token::Add || _op == Token::Mul)
addVerificationTarget(_expression, VerificationTarget::Type::Overflow, values.second > intType->maxValue());
verificationTargetEncountered(&_expression, VerificationTarget::Type::Overflow, values.second > intType->maxValue());
else
solAssert(false, "");
return values;
@ -679,11 +662,11 @@ pair<smtutil::Expression, smtutil::Expression> CHC::arithmeticOperation(
void CHC::resetSourceAnalysis()
{
m_verificationTargets.clear();
m_safeTargets.clear();
m_unsafeTargets.clear();
m_functionAssertions.clear();
m_errorIds.clear();
m_functionTargetIds.clear();
m_verificationTargets.clear();
m_queryPlaceholders.clear();
m_callGraph.clear();
m_summaries.clear();
m_interfaces.clear();
@ -713,6 +696,7 @@ void CHC::resetSourceAnalysis()
}
m_context.clear();
m_context.resetUniqueId();
m_context.setAssertionAccumulation(false);
}
@ -759,15 +743,15 @@ void CHC::setCurrentBlock(Predicate const& _block)
m_currentBlock = predicate(_block);
}
set<frontend::Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTNode const* _txRoot)
set<unsigned> CHC::transactionVerificationTargetsIds(ASTNode const* _txRoot)
{
set<Expression const*, IdCompare> assertions;
set<unsigned> verificationTargetsIds;
solidity::util::BreadthFirstSearch<ASTNode const*>{{_txRoot}}.run([&](auto const* function, auto&& _addChild) {
assertions.insert(m_functionAssertions[function].begin(), m_functionAssertions[function].end());
verificationTargetsIds.insert(m_functionTargetIds[function].begin(), m_functionTargetIds[function].end());
for (auto const* called: m_callGraph[function])
_addChild(called);
});
return assertions;
return verificationTargetsIds;
}
SortPointer CHC::sort(FunctionDefinition const& _function)
@ -1101,186 +1085,171 @@ pair<CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression c
return {result, cex};
}
void CHC::addVerificationTarget(
ASTNode const* _scope,
void CHC::verificationTargetEncountered(
ASTNode const* const _errorNode,
VerificationTarget::Type _type,
smtutil::Expression _from,
smtutil::Expression _constraints,
smtutil::Expression _errorId
smtutil::Expression const& _errorCondition
)
{
solAssert(m_currentContract || m_currentFunction, "");
SourceUnit const* source = nullptr;
if (m_currentContract)
source = sourceUnitContaining(*m_currentContract);
else
source = sourceUnitContaining(*m_currentFunction);
SourceUnit const* source = m_currentContract ? sourceUnitContaining(*m_currentContract) : sourceUnitContaining(*m_currentFunction);
solAssert(source, "");
if (!source->annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
return;
m_verificationTargets[_scope].push_back(CHCVerificationTarget{{_type, _from, _constraints}, _errorId});
}
void CHC::addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smtutil::Expression _errorId)
{
solAssert(m_currentContract, "");
if (!m_currentFunction || m_currentFunction->isConstructor())
addVerificationTarget(_scope, _type, summary(*m_currentContract), smtutil::Expression(true), _errorId);
bool scopeIsFunction = m_currentFunction && !m_currentFunction->isConstructor();
auto errorId = newErrorId();
solAssert(m_verificationTargets.count(errorId) == 0, "Error ID is not unique!");
m_verificationTargets.emplace(errorId, CHCVerificationTarget{{_type, _errorCondition, smtutil::Expression(true)}, errorId, _errorNode});
if (scopeIsFunction)
m_functionTargetIds[m_currentFunction].push_back(errorId);
else
{
auto iface = smt::interfacePre(*m_interfaces.at(m_currentContract), *m_currentContract, m_context);
auto sum = summary(*m_currentFunction);
addVerificationTarget(_scope, _type, iface, sum, _errorId);
}
}
void CHC::addVerificationTarget(frontend::Expression const& _scope, VerificationTarget::Type _type, smtutil::Expression const& _target)
{
m_functionTargetIds[m_currentContract].push_back(errorId);
auto previousError = errorFlag().currentValue();
errorFlag().increaseIndex();
addVerificationTarget(&_scope, _type, errorFlag().currentValue());
m_context.addAssertion(
errorFlag().currentValue() == previousError ||
(_target && errorFlag().currentValue() == newErrorId(_scope))
// create an error edge to the summary
connectBlocks(
m_currentBlock,
scopeIsFunction ? summary(*m_currentFunction) : summary(*m_currentContract),
currentPathConditions() && _errorCondition && errorFlag().currentValue() == errorId
);
}
void CHC::addAssertVerificationTarget(ASTNode const* _scope, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId)
{
addVerificationTarget(_scope, VerificationTarget::Type::Assert, _from, _constraints, _errorId);
m_context.addAssertion(errorFlag().currentValue() == previousError);
}
void CHC::checkVerificationTargets()
{
for (auto const& [scope, targets]: m_verificationTargets)
// The verification conditions have been collected per function where they have been encountered (m_verificationTargets).
// Also, all possible contexts in which an external function can be called has been recorded (m_queryPlaceholders).
// Here we combine every context in which an external function can be called with all possible verification conditions
// in its call graph. Each such combination forms a unique verification target.
vector<CHCVerificationTarget> verificationTargets;
for (auto const& [function, placeholders]: m_queryPlaceholders)
{
for (size_t i = 0; i < targets.size(); ++i)
{
auto const& target = targets[i];
if (target.type == VerificationTarget::Type::Assert)
checkAssertTarget(scope, target);
else
auto functionTargets = transactionVerificationTargetsIds(function);
for (auto const& placeholder: placeholders)
for (unsigned id: functionTargets)
{
string satMsg;
string satMsgUnderflow;
string satMsgOverflow;
string unknownMsg;
ErrorId errorReporterId;
ErrorId underflowErrorId = 3944_error;
ErrorId overflowErrorId = 4984_error;
auto const& target = m_verificationTargets.at(id);
verificationTargets.push_back(CHCVerificationTarget{
{target.type, placeholder.fromPredicate, placeholder.constraints && placeholder.errorExpression == target.errorId},
target.errorId,
target.errorNode
});
}
}
if (target.type == VerificationTarget::Type::PopEmptyArray)
{
solAssert(dynamic_cast<FunctionCall const*>(scope), "");
satMsg = "Empty array \"pop\" detected here.";
unknownMsg = "Empty array \"pop\" might happen here.";
errorReporterId = 2529_error;
}
else if (
target.type == VerificationTarget::Type::Underflow ||
target.type == VerificationTarget::Type::Overflow
)
{
auto const* expr = dynamic_cast<Expression const*>(scope);
solAssert(expr, "");
auto const* intType = dynamic_cast<IntegerType const*>(expr->annotation().type);
if (!intType)
intType = TypeProvider::uint256();
set<unsigned> checkedErrorIds;
for (auto const& target: verificationTargets)
{
string errorType;
ErrorId errorReporterId;
satMsgUnderflow = "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")";
satMsgOverflow = "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")";
if (target.type == VerificationTarget::Type::Underflow)
{
satMsg = satMsgUnderflow + " happens here.";
unknownMsg = satMsgUnderflow + " might happen here.";
errorReporterId = underflowErrorId;
}
else if (target.type == VerificationTarget::Type::Overflow)
{
satMsg = satMsgOverflow + " happens here.";
unknownMsg = satMsgOverflow + " might happen here.";
errorReporterId = overflowErrorId;
}
}
else if (target.type == VerificationTarget::Type::DivByZero)
{
satMsg = "Division by zero happens here.";
unknownMsg = "Division by zero might happen here.";
errorReporterId = 4281_error;
}
else
solAssert(false, "");
if (target.type == VerificationTarget::Type::PopEmptyArray)
{
solAssert(dynamic_cast<FunctionCall const*>(target.errorNode), "");
errorType = "Empty array \"pop\"";
errorReporterId = 2529_error;
}
else if (
target.type == VerificationTarget::Type::Underflow ||
target.type == VerificationTarget::Type::Overflow
)
{
auto const* expr = dynamic_cast<Expression const*>(target.errorNode);
solAssert(expr, "");
auto const* intType = dynamic_cast<IntegerType const*>(expr->annotation().type);
if (!intType)
intType = TypeProvider::uint256();
auto it = m_errorIds.find(scope->id());
solAssert(it != m_errorIds.end(), "");
solAssert(i < it->second.size(), "");
unsigned errorId = it->second[i];
checkAndReportTarget(scope, target, errorId, errorReporterId, satMsg, unknownMsg);
if (target.type == VerificationTarget::Type::Underflow)
{
errorType = "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")";
errorReporterId = 3944_error;
}
else if (target.type == VerificationTarget::Type::Overflow)
{
errorType = "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")";
errorReporterId = 4984_error;
}
}
}
}
else if (target.type == VerificationTarget::Type::DivByZero)
{
errorType = "Division by zero";
errorReporterId = 4281_error;
}
else if (target.type == VerificationTarget::Type::Assert)
{
errorType = "Assertion violation";
errorReporterId = 6328_error;
}
else
solAssert(false, "");
void CHC::checkAssertTarget(ASTNode const* _scope, CHCVerificationTarget const& _target)
{
solAssert(_target.type == VerificationTarget::Type::Assert, "");
auto assertions = transactionAssertions(_scope);
for (auto const* assertion: assertions)
{
auto it = m_errorIds.find(assertion->id());
solAssert(it != m_errorIds.end(), "");
solAssert(!it->second.empty(), "");
unsigned errorId = it->second[0];
checkAndReportTarget(assertion, _target, errorId, 6328_error, "Assertion violation happens here.", "Assertion violation might happen here.");
checkAndReportTarget(target, errorReporterId, errorType + " happens here.", errorType + " might happen here.");
checkedErrorIds.insert(target.errorId);
}
// There can be targets in internal functions that are not reachable from the external interface.
// These are safe by definition and are not even checked by the CHC engine, but this information
// must still be reported safe by the BMC engine.
set<unsigned> allErrorIds;
for (auto const& entry: m_functionTargetIds)
for (unsigned id: entry.second)
allErrorIds.insert(id);
set<unsigned> unreachableErrorIds;
set_difference(
allErrorIds.begin(),
allErrorIds.end(),
checkedErrorIds.begin(),
checkedErrorIds.end(),
inserter(unreachableErrorIds, unreachableErrorIds.begin())
);
for (auto id: unreachableErrorIds)
m_safeTargets[m_verificationTargets.at(id).errorNode].insert(m_verificationTargets.at(id).type);
}
void CHC::checkAndReportTarget(
ASTNode const* _scope,
CHCVerificationTarget const& _target,
unsigned _errorId,
ErrorId _errorReporterId,
string _satMsg,
string _unknownMsg
)
{
if (m_unsafeTargets.count(_scope) && m_unsafeTargets.at(_scope).count(_target.type))
if (m_unsafeTargets.count(_target.errorNode) && m_unsafeTargets.at(_target.errorNode).count(_target.type))
return;
createErrorBlock();
connectBlocks(_target.value, error(), _target.constraints && (_target.errorId == _errorId));
auto const& [result, model] = query(error(), _scope->location());
connectBlocks(_target.value, error(), _target.constraints);
auto const& location = _target.errorNode->location();
auto const& [result, model] = query(error(), location);
if (result == CheckResult::UNSATISFIABLE)
m_safeTargets[_scope].insert(_target.type);
m_safeTargets[_target.errorNode].insert(_target.type);
else if (result == CheckResult::SATISFIABLE)
{
solAssert(!_satMsg.empty(), "");
m_unsafeTargets[_scope].insert(_target.type);
m_unsafeTargets[_target.errorNode].insert(_target.type);
auto cex = generateCounterexample(model, error().name);
if (cex)
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
location,
"CHC: " + _satMsg,
SecondarySourceLocation().append("Counterexample:\n" + *cex, SourceLocation{})
);
else
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
location,
"CHC: " + _satMsg
);
}
else if (!_unknownMsg.empty())
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
location,
"CHC: " + _unknownMsg
);
}
@ -1431,14 +1400,13 @@ string CHC::contractSuffix(ContractDefinition const& _contract)
return _contract.name() + "_" + to_string(_contract.id());
}
unsigned CHC::newErrorId(frontend::Expression const& _expr)
unsigned CHC::newErrorId()
{
unsigned errorId = m_context.newUniqueId();
// We need to make sure the error id is not zero,
// because error id zero actually means no error in the CHC encoding.
if (errorId == 0)
errorId = m_context.newUniqueId();
m_errorIds[_expr.id()].push_back(errorId);
return errorId;
}

View File

@ -114,7 +114,7 @@ private:
void eraseKnowledge();
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
void setCurrentBlock(Predicate const& _block);
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
std::set<unsigned> transactionVerificationTargetsIds(ASTNode const* _txRoot);
//@}
/// Sort helpers.
@ -181,19 +181,14 @@ private:
/// @returns <false, model> otherwise.
std::pair<smtutil::CheckResult, smtutil::CHCSolverInterface::CexGraph> query(smtutil::Expression const& _query, langutil::SourceLocation const& _location);
void addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId);
void addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smtutil::Expression _errorId);
void addVerificationTarget(frontend::Expression const& _scope, VerificationTarget::Type _type, smtutil::Expression const& _target);
void addAssertVerificationTarget(ASTNode const* _scope, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId);
void verificationTargetEncountered(ASTNode const* const _errorNode, VerificationTarget::Type _type, smtutil::Expression const& _errorCondition);
void checkVerificationTargets();
// Forward declaration. Definition is below.
struct CHCVerificationTarget;
void checkAssertTarget(ASTNode const* _scope, CHCVerificationTarget const& _target);
void checkAndReportTarget(
ASTNode const* _scope,
CHCVerificationTarget const& _target,
unsigned _errorId,
langutil::ErrorId _errorReporterId,
std::string _satMsg,
std::string _unknownMsg = ""
@ -234,7 +229,7 @@ private:
/// @returns a new unique error id associated with _expr and stores
/// it into m_errorIds.
unsigned newErrorId(Expression const& _expr);
unsigned newErrorId();
smt::SymbolicState& state();
smt::SymbolicIntVariable& errorFlag();
@ -275,12 +270,30 @@ private:
//@{
struct CHCVerificationTarget: VerificationTarget
{
smtutil::Expression errorId;
unsigned const errorId;
ASTNode const* const errorNode;
};
/// Verification targets corresponding to ASTNodes. There can be multiple targets for a single ASTNode,
/// e.g., divByZero and Overflow for signed division.
std::map<ASTNode const*, std::vector<CHCVerificationTarget>, IdCompare> m_verificationTargets;
/// Query placeholder stores information necessary to create the final query edge in the CHC system.
/// It is combined with the unique error id (and error type) to create a complete Verification Target.
struct CHCQueryPlaceholder
{
smtutil::Expression const constraints;
smtutil::Expression const errorExpression;
smtutil::Expression const fromPredicate;
};
/// Query placeholders for constructors, if the key has type ContractDefinition*,
/// or external functions, if the key has type FunctionDefinition*.
/// A placeholder is created for each possible context of a function (e.g. multiple contracts in contract inheritance hierarchy).
std::map<ASTNode const*, std::vector<CHCQueryPlaceholder>, IdCompare> m_queryPlaceholders;
/// Records verification conditions IDs per function encountered during an analysis of that function.
/// The key is the ASTNode of the function where the verification condition has been encountered,
/// or the ASTNode of the contract if the verification condition happens inside an implicit constructor.
std::map<ASTNode const*, std::vector<unsigned>, IdCompare> m_functionTargetIds;
/// Helper mapping unique IDs to actual verification targets.
std::map<unsigned, CHCVerificationTarget> m_verificationTargets;
/// Targets proven safe.
std::map<ASTNode const*, std::set<VerificationTarget::Type>> m_safeTargets;
@ -294,12 +307,6 @@ private:
std::map<ASTNode const*, std::set<ASTNode const*, IdCompare>, IdCompare> m_callGraph;
std::map<ASTNode const*, std::set<Expression const*>, IdCompare> m_functionAssertions;
/// Maps ASTNode ids to error ids.
/// There can be multiple errorIds associated with a single ASTNode.
std::map<unsigned, std::vector<unsigned>> m_errorIds;
/// The current block.
smtutil::Expression m_currentBlock = smtutil::Expression(true);

View File

@ -33,7 +33,6 @@ EncodingContext::EncodingContext():
void EncodingContext::reset()
{
resetAllVariables();
resetUniqueId();
m_expressions.clear();
m_globalContext.clear();
m_state.reset();

View File

@ -348,6 +348,36 @@ void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
}
bool SMTEncoder::visit(Assignment const& _assignment)
{
auto const& left = _assignment.leftHandSide();
auto const& right = _assignment.rightHandSide();
if (auto const* memberAccess = isEmptyPush(left))
{
right.accept(*this);
left.accept(*this);
auto const& memberExpr = memberAccess->expression();
auto& symbArray = dynamic_cast<smt::SymbolicArrayVariable&>(*m_context.expression(memberExpr));
smtutil::Expression oldElements = symbArray.elements();
smtutil::Expression length = symbArray.length();
symbArray.increaseIndex();
m_context.addAssertion(symbArray.elements() == smtutil::Expression::store(
oldElements,
length - 1,
expr(right)
));
m_context.addAssertion(symbArray.length() == length);
arrayPushPopAssign(memberExpr, symbArray.currentValue());
defineExpr(_assignment, expr(left));
return false;
}
return true;
}
void SMTEncoder::endVisit(Assignment const& _assignment)
{
createExpr(_assignment);
@ -355,6 +385,9 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
Token op = _assignment.assignmentOperator();
solAssert(TokenTraits::isAssignmentOp(op), "");
if (isEmptyPush(_assignment.leftHandSide()))
return;
if (!smt::isSupportedType(*_assignment.annotation().type))
{
// Give it a new index anyway to keep the SSA scheme sound.
@ -465,14 +498,14 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
assignment(*decl, newValue);
}
else if (
dynamic_cast<IndexAccess const*>(&_op.subExpression()) ||
dynamic_cast<MemberAccess const*>(&_op.subExpression())
dynamic_cast<IndexAccess const*>(subExpr) ||
dynamic_cast<MemberAccess const*>(subExpr)
)
{
auto innerValue = expr(*subExpr);
auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
indexOrMemberAssignment(_op.subExpression(), newValue);
indexOrMemberAssignment(*subExpr, newValue);
}
else
m_errorReporter.warning(
@ -502,11 +535,12 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
symbVar->increaseIndex();
m_context.setZeroValue(*symbVar);
if (
dynamic_cast<IndexAccess const*>(&_op.subExpression()) ||
dynamic_cast<MemberAccess const*>(&_op.subExpression())
dynamic_cast<IndexAccess const*>(subExpr) ||
dynamic_cast<MemberAccess const*>(subExpr)
)
indexOrMemberAssignment(_op.subExpression(), symbVar->currentValue());
else
indexOrMemberAssignment(*subExpr, symbVar->currentValue());
// Empty push added a zero value anyway, so no need to delete extra.
else if (!isEmptyPush(*subExpr))
solAssert(false, "");
}
break;
@ -1968,7 +2002,7 @@ void SMTEncoder::initializeFunctionCallParameters(CallableDeclaration const& _fu
void SMTEncoder::createStateVariables(ContractDefinition const& _contract)
{
for (auto var: _contract.stateVariablesIncludingInherited())
for (auto var: stateVariablesIncludingInheritedAndPrivate(_contract))
createVariable(*var);
}
@ -2252,7 +2286,7 @@ void SMTEncoder::resetVariableIndices(VariableIndices const& _indices)
void SMTEncoder::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function)
{
solAssert(_contract, "");
for (auto var: _contract->stateVariablesIncludingInherited())
for (auto var: stateVariablesIncludingInheritedAndPrivate(*_contract))
m_context.variable(*var)->resetIndex();
if (_function)
{
@ -2309,7 +2343,7 @@ set<VariableDeclaration const*> SMTEncoder::touchedVariables(ASTNode const& _nod
return m_variableUsage.touchedVariables(_node, callStack);
}
Declaration const* SMTEncoder::expressionToDeclaration(Expression const& _expr)
Declaration const* SMTEncoder::expressionToDeclaration(Expression const& _expr) const
{
if (auto const* identifier = dynamic_cast<Identifier const*>(&_expr))
return identifier->annotation().referencedDeclaration;
@ -2318,7 +2352,7 @@ Declaration const* SMTEncoder::expressionToDeclaration(Expression const& _expr)
return nullptr;
}
VariableDeclaration const* SMTEncoder::identifierToVariable(Expression const& _expr)
VariableDeclaration const* SMTEncoder::identifierToVariable(Expression const& _expr) const
{
// We do not use `expressionToDeclaration` here because we are not interested in
// struct.field, for example.
@ -2331,6 +2365,20 @@ VariableDeclaration const* SMTEncoder::identifierToVariable(Expression const& _e
return nullptr;
}
MemberAccess const* SMTEncoder::isEmptyPush(Expression const& _expr) const
{
if (
auto const* funCall = dynamic_cast<FunctionCall const*>(&_expr);
funCall && funCall->arguments().empty()
)
{
auto const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
if (funType.kind() == FunctionType::Kind::ArrayPush)
return &dynamic_cast<MemberAccess const&>(funCall->expression());
}
return nullptr;
}
string SMTEncoder::extraComment()
{
string extra;

View File

@ -90,6 +90,7 @@ protected:
bool visit(WhileStatement const&) override { return false; }
bool visit(ForStatement const&) override { return false; }
void endVisit(VariableDeclarationStatement const& _node) override;
bool visit(Assignment const& _node) override;
void endVisit(Assignment const& _node) override;
void endVisit(TupleExpression const& _node) override;
bool visit(UnaryOperation const& _node) override;
@ -282,10 +283,14 @@ protected:
/// @returns the declaration referenced by _expr, if any,
/// and nullptr otherwise.
Declaration const* expressionToDeclaration(Expression const& _expr);
Declaration const* expressionToDeclaration(Expression const& _expr) const;
/// @returns the VariableDeclaration referenced by an Expression or nullptr.
VariableDeclaration const* identifierToVariable(Expression const& _expr);
VariableDeclaration const* identifierToVariable(Expression const& _expr) const;
/// @returns the MemberAccess <expression>.push if _expr is an empty array push call,
/// otherwise nullptr.
MemberAccess const* isEmptyPush(Expression const& _expr) const;
/// Creates symbolic expressions for the returned values
/// and set them as the components of the symbolic tuple.

View File

@ -332,12 +332,12 @@ smtutil::Expression SymbolicArrayVariable::valueAtIndex(unsigned _index) const
return m_pair.valueAtIndex(_index);
}
smtutil::Expression SymbolicArrayVariable::elements()
smtutil::Expression SymbolicArrayVariable::elements() const
{
return m_pair.component(0);
}
smtutil::Expression SymbolicArrayVariable::length()
smtutil::Expression SymbolicArrayVariable::length() const
{
return m_pair.component(1);
}

View File

@ -260,8 +260,8 @@ public:
smtutil::Expression resetIndex() override { SymbolicVariable::resetIndex(); return m_pair.resetIndex(); }
smtutil::Expression setIndex(unsigned _index) override { SymbolicVariable::setIndex(_index); return m_pair.setIndex(_index); }
smtutil::Expression increaseIndex() override { SymbolicVariable::increaseIndex(); return m_pair.increaseIndex(); }
smtutil::Expression elements();
smtutil::Expression length();
smtutil::Expression elements() const;
smtutil::Expression length() const;
smtutil::SortPointer tupleSort() { return m_pair.sort(); }

View File

@ -73,28 +73,6 @@ BOOST_AUTO_TEST_CASE(value_types)
)
}
BOOST_AUTO_TEST_CASE(enums)
{
string sourceCode = R"(
contract C {
enum E { A, B }
function f(E e) public pure returns (uint x) {
assembly { x := e }
}
}
)";
bool newDecoder = solidity::test::CommonOptions::get().useABIEncoderV2;
BOTH_ENCODERS(
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("f(uint8)", 0), encodeArgs(u256(0)));
ABI_CHECK(callContractFunction("f(uint8)", 1), encodeArgs(u256(1)));
// The old decoder was not as strict about enums
ABI_CHECK(callContractFunction("f(uint8)", 2), (newDecoder ? encodeArgs() : encodeArgs(2)));
ABI_CHECK(callContractFunction("f(uint8)", u256(-1)), (newDecoder? encodeArgs() : encodeArgs(u256(0xff))));
newDecoder = true;
)
}
BOOST_AUTO_TEST_CASE(cleanup)
{
string sourceCode = R"(
@ -184,113 +162,6 @@ BOOST_AUTO_TEST_CASE(fixed_arrays)
)
}
BOOST_AUTO_TEST_CASE(dynamic_arrays)
{
string sourceCode = R"(
contract C {
function f(uint a, uint16[] memory b, uint c)
public pure returns (uint, uint, uint) {
return (b.length, b[a], c);
}
}
)";
BOTH_ENCODERS(
compileAndRun(sourceCode);
bytes args = encodeArgs(
6, 0x60, 9,
7,
11, 12, 13, 14, 15, 16, 17
);
ABI_CHECK(
callContractFunction("f(uint256,uint16[],uint256)", args),
encodeArgs(u256(7), u256(17), u256(9))
);
)
}
BOOST_AUTO_TEST_CASE(dynamic_nested_arrays)
{
string sourceCode = R"(
contract C {
function f(uint a, uint16[][] memory b, uint[2][][3] memory c, uint d)
public pure returns (uint, uint, uint, uint, uint, uint, uint) {
return (a, b.length, b[1].length, b[1][1], c[1].length, c[1][1][1], d);
}
function test() public view returns (uint, uint, uint, uint, uint, uint, uint) {
uint16[][] memory b = new uint16[][](3);
b[0] = new uint16[](2);
b[0][0] = 0x55;
b[0][1] = 0x56;
b[1] = new uint16[](4);
b[1][0] = 0x65;
b[1][1] = 0x66;
b[1][2] = 0x67;
b[1][3] = 0x68;
uint[2][][3] memory c;
c[0] = new uint[2][](1);
c[0][0][1] = 0x75;
c[1] = new uint[2][](5);
c[1][1][1] = 0x85;
return this.f(0x12, b, c, 0x13);
}
}
)";
NEW_ENCODER(
compileAndRun(sourceCode);
bytes args = encodeArgs(
0x12, 4 * 0x20, 17 * 0x20, 0x13,
// b
3, 3 * 0x20, 6 * 0x20, 11 * 0x20,
2, 85, 86,
4, 101, 102, 103, 104,
0,
// c
3 * 0x20, 6 * 0x20, 17 * 0x20,
1, 0, 117,
5, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0,
0
);
bytes expectation = encodeArgs(0x12, 3, 4, 0x66, 5, 0x85, 0x13);
ABI_CHECK(callContractFunction("test()"), expectation);
ABI_CHECK(callContractFunction("f(uint256,uint16[][],uint256[2][][3],uint256)", args), expectation);
)
}
BOOST_AUTO_TEST_CASE(byte_arrays)
{
string sourceCode = R"(
contract C {
function f(uint a, bytes memory b, uint c)
public pure returns (uint, uint, byte, uint) {
return (a, b.length, b[3], c);
}
function f_external(uint a, bytes calldata b, uint c)
external pure returns (uint, uint, byte, uint) {
return (a, b.length, b[3], c);
}
}
)";
BOTH_ENCODERS(
compileAndRun(sourceCode);
bytes args = encodeArgs(
6, 0x60, 9,
7, "abcdefg"
);
ABI_CHECK(
callContractFunction("f(uint256,bytes,uint256)", args),
encodeArgs(u256(6), u256(7), "d", 9)
);
ABI_CHECK(
callContractFunction("f_external(uint256,bytes,uint256)", args),
encodeArgs(u256(6), u256(7), "d", 9)
);
)
}
BOOST_AUTO_TEST_CASE(calldata_arrays_too_large)
{
string sourceCode = R"(

View File

@ -34,6 +34,7 @@
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/analysis/SyntaxChecker.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/test/unit_test.hpp>
@ -61,6 +62,7 @@ evmasm::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode)
BOOST_CHECK(!!sourceUnit);
Scoper::assignScopes(*sourceUnit);
BOOST_REQUIRE(SyntaxChecker(errorReporter, false).checkSyntax(*sourceUnit));
GlobalContext globalContext;
NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), errorReporter);
DeclarationTypeChecker declarationTypeChecker(errorReporter, solidity::test::CommonOptions::get().evmVersion());

View File

@ -537,39 +537,6 @@ BOOST_AUTO_TEST_CASE(for_loop)
)
}
BOOST_AUTO_TEST_CASE(for_loop_empty)
{
char const* sourceCode = R"(
contract test {
function f() public returns(uint ret) {
ret = 1;
for (;;) {
ret += 1;
if (ret >= 10) break;
}
}
}
)";
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
auto for_loop_empty_cpp = []() -> u256
{
u256 ret = 1;
for (;;)
{
ret += 1;
if (ret >= 10) break;
}
return ret;
};
testContractAgainstCpp("f()", for_loop_empty_cpp);
)
}
BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr)
{
char const* sourceCode = R"(
@ -649,85 +616,6 @@ BOOST_AUTO_TEST_CASE(for_loop_break_continue)
);
}
BOOST_AUTO_TEST_CASE(calling_other_functions)
{
char const* sourceCode = R"(
contract collatz {
function run(uint x) public returns(uint y) {
while ((y = x) > 1) {
if (x % 2 == 0) x = evenStep(x);
else x = oddStep(x);
}
}
function evenStep(uint x) public returns(uint y) {
return x / 2;
}
function oddStep(uint x) public returns(uint y) {
return 3 * x + 1;
}
}
)";
auto evenStep_cpp = [](u256 const& n) -> u256
{
return n / 2;
};
auto oddStep_cpp = [](u256 const& n) -> u256
{
return 3 * n + 1;
};
auto collatz_cpp = [&evenStep_cpp, &oddStep_cpp](u256 n) -> u256
{
u256 y;
while ((y = n) > 1)
{
if (n % 2 == 0)
n = evenStep_cpp(n);
else
n = oddStep_cpp(n);
}
return y;
};
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
testContractAgainstCpp("run(uint256)", collatz_cpp, u256(0));
testContractAgainstCpp("run(uint256)", collatz_cpp, u256(1));
testContractAgainstCpp("run(uint256)", collatz_cpp, u256(2));
testContractAgainstCpp("run(uint256)", collatz_cpp, u256(8));
testContractAgainstCpp("run(uint256)", collatz_cpp, u256(127));
)
}
BOOST_AUTO_TEST_CASE(many_local_variables)
{
char const* sourceCode = R"(
contract test {
function run(uint x1, uint x2, uint x3) public returns(uint y) {
uint8 a = 0x1; uint8 b = 0x10; uint16 c = 0x100;
y = a + b + c + x1 + x2 + x3;
y += b + x2;
}
}
)";
auto f = [](u256 const& x1, u256 const& x2, u256 const& x3) -> u256
{
u256 a = 0x1;
u256 b = 0x10;
u256 c = 0x100;
u256 y = a + b + c + x1 + x2 + x3;
return y + b + x2;
};
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
testContractAgainstCpp("run(uint256,uint256,uint256)", f, u256(0x1000), u256(0x10000), u256(0x100000));
)
}
BOOST_AUTO_TEST_CASE(short_circuiting)
{
char const* sourceCode = R"(
@ -826,149 +714,6 @@ BOOST_AUTO_TEST_CASE(small_unsigned_types)
testContractAgainstCpp("run()", small_unsigned_types_cpp);
}
BOOST_AUTO_TEST_CASE(small_signed_types)
{
char const* sourceCode = R"(
contract test {
function run() public returns(int256 y) {
return -int32(10) * -int64(20);
}
}
)";
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
auto small_signed_types_cpp = []() -> u256
{
return -int32_t(10) * -int64_t(20);
};
testContractAgainstCpp("run()", small_signed_types_cpp);
);
}
BOOST_AUTO_TEST_CASE(compound_assign)
{
char const* sourceCode = R"(
contract test {
uint value1;
uint value2;
function f(uint x, uint y) public returns (uint w) {
uint value3 = y;
value1 += x;
value3 *= x;
value2 *= value3 + value1;
return value2 += 7;
}
}
)";
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
u256 value1;
u256 value2;
auto f = [&](u256 const& _x, u256 const& _y) -> u256
{
u256 value3 = _y;
value1 += _x;
value3 *= _x;
value2 *= value3 + value1;
return value2 += 7;
};
testContractAgainstCpp("f(uint256,uint256)", f, u256(0), u256(6));
testContractAgainstCpp("f(uint256,uint256)", f, u256(1), u256(3));
testContractAgainstCpp("f(uint256,uint256)", f, u256(2), u256(25));
testContractAgainstCpp("f(uint256,uint256)", f, u256(3), u256(69));
testContractAgainstCpp("f(uint256,uint256)", f, u256(4), u256(84));
testContractAgainstCpp("f(uint256,uint256)", f, u256(5), u256(2));
testContractAgainstCpp("f(uint256,uint256)", f, u256(6), u256(51));
testContractAgainstCpp("f(uint256,uint256)", f, u256(7), u256(48));
)
}
BOOST_AUTO_TEST_CASE(mapping_state)
{
char const* sourceCode = R"(
contract Ballot {
mapping(address => bool) canVote;
mapping(address => uint) voteCount;
mapping(address => bool) voted;
function getVoteCount(address addr) public returns (uint retVoteCount) {
return voteCount[addr];
}
function grantVoteRight(address addr) public {
canVote[addr] = true;
}
function vote(address voter, address vote) public returns (bool success) {
if (!canVote[voter] || voted[voter]) return false;
voted[voter] = true;
voteCount[vote] = voteCount[vote] + 1;
return true;
}
}
)";
class Ballot
{
public:
u256 getVoteCount(u160 _address) { return m_voteCount[_address]; }
void grantVoteRight(u160 _address) { m_canVote[_address] = true; }
bool vote(u160 _voter, u160 _vote)
{
if (!m_canVote[_voter] || m_voted[_voter]) return false;
m_voted[_voter] = true;
m_voteCount[_vote]++;
return true;
}
private:
map<u160, bool> m_canVote;
map<u160, u256> m_voteCount;
map<u160, bool> m_voted;
};
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
Ballot ballot;
auto getVoteCount = bind(&Ballot::getVoteCount, &ballot, _1);
auto grantVoteRight = bind(&Ballot::grantVoteRight, &ballot, _1);
auto vote = bind(&Ballot::vote, &ballot, _1, _2);
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(2));
// voting without vote right should be rejected
testContractAgainstCpp("vote(address,address)", vote, u160(0), u160(2));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(2));
// grant vote rights
testContractAgainstCpp("grantVoteRight(address)", grantVoteRight, u160(0));
testContractAgainstCpp("grantVoteRight(address)", grantVoteRight, u160(1));
// vote, should increase 2's vote count
testContractAgainstCpp("vote(address,address)", vote, u160(0), u160(2));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(2));
// vote again, should be rejected
testContractAgainstCpp("vote(address,address)", vote, u160(0), u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(2));
// vote without right to vote
testContractAgainstCpp("vote(address,address)", vote, u160(2), u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(2));
// grant vote right and now vote again
testContractAgainstCpp("grantVoteRight(address)", grantVoteRight, u160(2));
testContractAgainstCpp("vote(address,address)", vote, u160(2), u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
testContractAgainstCpp("getVoteCount(address)", getVoteCount, u160(2));
)
}
BOOST_AUTO_TEST_CASE(mapping_state_inc_dec)
{
char const* sourceCode = R"(
@ -2764,11 +2509,14 @@ BOOST_AUTO_TEST_CASE(delete_removes_bytes_data)
bytes data;
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("---", 7), bytes());
BOOST_CHECK(!storageEmpty(m_contractAddress));
ABI_CHECK(callContractFunction("del()", 7), encodeArgs(true));
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("---", 7), bytes());
BOOST_CHECK(!storageEmpty(m_contractAddress));
ABI_CHECK(callContractFunction("del()", 7), encodeArgs(true));
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(copy_from_calldata_removes_bytes_data)
@ -2780,13 +2528,16 @@ BOOST_AUTO_TEST_CASE(copy_from_calldata_removes_bytes_data)
bytes data;
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("set()", 1, 2, 3, 4, 5), encodeArgs(true));
BOOST_CHECK(!storageEmpty(m_contractAddress));
sendMessage(bytes(), false);
BOOST_CHECK(m_transactionSuccessful);
BOOST_CHECK(m_output.empty());
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("set()", 1, 2, 3, 4, 5), encodeArgs(true));
BOOST_CHECK(!storageEmpty(m_contractAddress));
sendMessage(bytes(), false);
BOOST_CHECK(m_transactionSuccessful);
BOOST_CHECK(m_output.empty());
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(copy_removes_bytes_data)
@ -3032,17 +2783,21 @@ BOOST_AUTO_TEST_CASE(bytes_in_arguments)
}
}
)";
compileAndRun(sourceCode);
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
string innercalldata1 = asString(FixedHash<4>(util::keccak256("f(uint256,uint256)")).asBytes() + encodeArgs(8, 9));
string innercalldata2 = asString(FixedHash<4>(util::keccak256("g(uint256)")).asBytes() + encodeArgs(3));
bytes calldata = encodeArgs(
12, 32 * 4, u256(32 * 4 + 32 + (innercalldata1.length() + 31) / 32 * 32), 13,
u256(innercalldata1.length()), innercalldata1,
u256(innercalldata2.length()), innercalldata2);
ABI_CHECK(
callContractFunction("test(uint256,bytes,bytes,uint256)", calldata),
encodeArgs(12, (8 + 9) * 3, 13, u256(innercalldata1.length()))
compileAndRun(sourceCode);
string innercalldata1 = asString(FixedHash<4>(util::keccak256("f(uint256,uint256)")).asBytes() + encodeArgs(8, 9));
string innercalldata2 = asString(FixedHash<4>(util::keccak256("g(uint256)")).asBytes() + encodeArgs(3));
bytes calldata = encodeArgs(
12, 32 * 4, u256(32 * 4 + 32 + (innercalldata1.length() + 31) / 32 * 32), 13,
u256(innercalldata1.length()), innercalldata1,
u256(innercalldata2.length()), innercalldata2);
ABI_CHECK(
callContractFunction("test(uint256,bytes,bytes,uint256)", calldata),
encodeArgs(12, (8 + 9) * 3, 13, u256(innercalldata1.length()))
);
);
}
@ -3146,12 +2901,16 @@ BOOST_AUTO_TEST_CASE(dynamic_multi_array_cleanup)
function clear() public { delete data; }
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(storageEmpty(m_contractAddress));
ABI_CHECK(callContractFunction("fill()"), encodeArgs(8));
BOOST_CHECK(!storageEmpty(m_contractAddress));
ABI_CHECK(callContractFunction("clear()"), bytes());
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
BOOST_CHECK(storageEmpty(m_contractAddress));
ABI_CHECK(callContractFunction("fill()"), encodeArgs(8));
BOOST_CHECK(!storageEmpty(m_contractAddress));
ABI_CHECK(callContractFunction("clear()"), bytes());
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(array_copy_storage_storage_dyn_dyn)
@ -3268,20 +3027,24 @@ BOOST_AUTO_TEST_CASE(array_copy_storage_abi)
}
}
)";
compileAndRun(sourceCode);
bytes valueSequence;
for (size_t i = 0; i < 101; ++i)
valueSequence += toBigEndian(u256(i));
ABI_CHECK(callContractFunction("test1()"), encodeArgs(0x20, 101) + valueSequence);
ABI_CHECK(callContractFunction("test2()"), encodeArgs(0x20, 101) + valueSequence);
ABI_CHECK(callContractFunction("test3()"), encodeArgs(0x20, 101) + valueSequence);
ABI_CHECK(callContractFunction("test4()"),
encodeArgs(0x20, 5, 0xa0, 0xa0 + 102 * 32 * 1, 0xa0 + 102 * 32 * 2, 0xa0 + 102 * 32 * 3, 0xa0 + 102 * 32 * 4) +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
bytes valueSequence;
for (size_t i = 0; i < 101; ++i)
valueSequence += toBigEndian(u256(i));
ABI_CHECK(callContractFunction("test1()"), encodeArgs(0x20, 101) + valueSequence);
ABI_CHECK(callContractFunction("test2()"), encodeArgs(0x20, 101) + valueSequence);
ABI_CHECK(callContractFunction("test3()"), encodeArgs(0x20, 101) + valueSequence);
ABI_CHECK(callContractFunction("test4()"),
encodeArgs(0x20, 5, 0xa0, 0xa0 + 102 * 32 * 1, 0xa0 + 102 * 32 * 2, 0xa0 + 102 * 32 * 3, 0xa0 + 102 * 32 * 4) +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence +
encodeArgs(101) + valueSequence
);
);
}
@ -3307,9 +3070,12 @@ BOOST_AUTO_TEST_CASE(array_pop_uint16_transition)
}
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs(38, 28, 18));
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs(38, 28, 18));
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(array_pop_uint24_transition)
@ -3334,9 +3100,12 @@ BOOST_AUTO_TEST_CASE(array_pop_uint24_transition)
}
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs(20, 10));
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs(20, 10));
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(array_pop_array_transition)
@ -3382,9 +3151,12 @@ BOOST_AUTO_TEST_CASE(array_pop_storage_empty)
}
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs());
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs());
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(byte_array_pop_storage_empty)
@ -3402,9 +3174,12 @@ BOOST_AUTO_TEST_CASE(byte_array_pop_storage_empty)
}
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs());
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs());
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(byte_array_pop_long_storage_empty)
@ -3427,9 +3202,12 @@ BOOST_AUTO_TEST_CASE(byte_array_pop_long_storage_empty)
}
}
)";
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs(true));
BOOST_CHECK(storageEmpty(m_contractAddress));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("test()"), encodeArgs(true));
BOOST_CHECK(storageEmpty(m_contractAddress));
);
}
BOOST_AUTO_TEST_CASE(byte_array_pop_long_storage_empty_garbage_ref)
@ -3505,15 +3283,19 @@ BOOST_AUTO_TEST_CASE(bytes_index_access)
}
}
)";
compileAndRun(sourceCode);
string array{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33};
ABI_CHECK(callContractFunction("direct(bytes,uint256)", 64, 33, u256(array.length()), array), encodeArgs(33));
ABI_CHECK(callContractFunction("storageCopyRead(bytes,uint256)", 64, 33, u256(array.length()), array), encodeArgs(33));
ABI_CHECK(callContractFunction("storageWrite()"), encodeArgs(0x193));
ALSO_VIA_YUL(
DISABLE_EWASM_TESTRUN()
compileAndRun(sourceCode);
ABI_CHECK(callContractFunction("direct(bytes,uint256)", 64, 33, u256(array.length()), array), encodeArgs(33));
ABI_CHECK(callContractFunction("storageCopyRead(bytes,uint256)", 64, 33, u256(array.length()), array), encodeArgs(33));
ABI_CHECK(callContractFunction("storageWrite()"), encodeArgs(0x193));
);
}
BOOST_AUTO_TEST_CASE(array_copy_calldata_storage)

View File

@ -27,6 +27,7 @@
#include <libsolidity/parsing/Parser.h>
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/analysis/Scoper.h>
#include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/analysis/DeclarationTypeChecker.h>
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
@ -93,18 +94,20 @@ Declaration const& resolveDeclaration(
}
bytes compileFirstExpression(
const string& _sourceCode,
string const& _sourceCode,
vector<vector<string>> _functions = {},
vector<vector<string>> _localVariables = {}
)
{
string sourceCode = "pragma solidity >=0.0; // SPDX-License-Identifier: GPL-3\n" + _sourceCode;
ASTPointer<SourceUnit> sourceUnit;
try
{
ErrorList errors;
ErrorReporter errorReporter(errors);
sourceUnit = Parser(errorReporter, solidity::test::CommonOptions::get().evmVersion()).parse(
make_shared<Scanner>(CharStream(_sourceCode, ""))
make_shared<Scanner>(CharStream(sourceCode, ""))
);
if (!sourceUnit)
return bytes();
@ -119,6 +122,7 @@ bytes compileFirstExpression(
ErrorReporter errorReporter(errors);
GlobalContext globalContext;
Scoper::assignScopes(*sourceUnit);
BOOST_REQUIRE(SyntaxChecker(errorReporter, false).checkSyntax(*sourceUnit));
NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), errorReporter);
resolver.registerDeclarations(*sourceUnit);
BOOST_REQUIRE_MESSAGE(resolver.resolveNamesAndTypes(*sourceUnit), "Resolving names failed");
@ -187,7 +191,7 @@ BOOST_AUTO_TEST_CASE(literal_false)
{
char const* sourceCode = R"(
contract test {
function f() { bool x = false; }
function f() public { bool x = false; }
}
)";
bytes code = compileFirstExpression(sourceCode);
@ -200,7 +204,7 @@ BOOST_AUTO_TEST_CASE(int_literal)
{
char const* sourceCode = R"(
contract test {
function f() { uint x = 0x12345678901234567890; }
function f() public { uint x = 0x12345678901234567890; }
}
)";
bytes code = compileFirstExpression(sourceCode);
@ -229,7 +233,7 @@ BOOST_AUTO_TEST_CASE(int_with_gwei_ether_subdenomination)
{
char const* sourceCode = R"(
contract test {
function test () {
function f() public {
uint x = 1 gwei;
}
}
@ -259,7 +263,7 @@ BOOST_AUTO_TEST_CASE(comparison)
{
char const* sourceCode = R"(
contract test {
function f() { bool x = (0x10aa < 0x11aa) != true; }
function f() public { bool x = (0x10aa < 0x11aa) != true; }
}
)";
bytes code = compileFirstExpression(sourceCode);
@ -291,7 +295,7 @@ BOOST_AUTO_TEST_CASE(short_circuiting)
{
char const* sourceCode = R"(
contract test {
function f() { bool x = true != (4 <= 8 + 10 || 9 != 2); }
function f() public { bool x = true != (4 <= 8 + 10 || 9 != 2); }
}
)";
bytes code = compileFirstExpression(sourceCode);
@ -322,7 +326,7 @@ BOOST_AUTO_TEST_CASE(arithmetic)
{
char const* sourceCode = R"(
contract test {
function f(uint y) { unchecked { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } }
function f(uint y) public { unchecked { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } }
}
)";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}});
@ -418,7 +422,7 @@ BOOST_AUTO_TEST_CASE(unary_operators)
{
char const* sourceCode = R"(
contract test {
function f(int y) { unchecked { !(~- y == 2); } }
function f(int y) public { unchecked { !(~- y == 2); } }
}
)";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}});
@ -508,7 +512,7 @@ BOOST_AUTO_TEST_CASE(assignment)
{
char const* sourceCode = R"(
contract test {
function f(uint a, uint b) { unchecked { (a += b) * 2; } }
function f(uint a, uint b) public { unchecked { (a += b) * 2; } }
}
)";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}});
@ -546,7 +550,7 @@ BOOST_AUTO_TEST_CASE(negative_literals_8bits)
{
char const* sourceCode = R"(
contract test {
function f() { int8 x = -0x80; }
function f() public { int8 x = -0x80; }
}
)";
bytes code = compileFirstExpression(sourceCode);
@ -559,7 +563,7 @@ BOOST_AUTO_TEST_CASE(negative_literals_16bits)
{
char const* sourceCode = R"(
contract test {
function f() { int64 x = ~0xabc; }
function f() public { int64 x = ~0xabc; }
}
)";
bytes code = compileFirstExpression(sourceCode);
@ -574,7 +578,7 @@ BOOST_AUTO_TEST_CASE(intermediately_overflowing_literals)
// have been applied
char const* sourceCode = R"(
contract test {
function f() { uint8 x = (0x00ffffffffffffffffffffffffffffffffffffffff * 0xffffffffffffffffffffffffff01) & 0xbf; }
function f() public { uint8 x = (0x00ffffffffffffffffffffffffffffffffffffffff * 0xffffffffffffffffffffffffff01) & 0xbf; }
}
)";
bytes code = compileFirstExpression(sourceCode);
@ -587,7 +591,7 @@ BOOST_AUTO_TEST_CASE(blockhash)
{
char const* sourceCode = R"(
contract test {
function f() {
function f() public {
blockhash(3);
}
}
@ -619,7 +623,7 @@ BOOST_AUTO_TEST_CASE(selfbalance)
{
char const* sourceCode = R"(
contract test {
function f() returns (uint) {
function f() public returns (uint) {
return address(this).balance;
}
}

View File

@ -20,5 +20,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x20, 0x8, 0x40, 0x3, 0x9, 0xa, 0xb

View File

@ -0,0 +1,16 @@
contract C {
function f(uint a, bytes memory b, uint c)
public pure returns (uint, uint, byte, uint) {
return (a, b.length, b[3], c);
}
function f_external(uint a, bytes calldata b, uint c)
external pure returns (uint, uint, byte, uint) {
return (a, b.length, b[3], c);
}
}
// ====
// compileViaYul: also
// ----
// f(uint256,bytes,uint256): 6, 0x60, 9, 7, "abcdefg" -> 6, 7, "d", 9
// f_external(uint256,bytes,uint256): 6, 0x60, 9, 7, "abcdefg" -> 6, 7, "d", 9

View File

@ -0,0 +1,10 @@
contract C {
function f(uint a, uint16[] memory b, uint c)
public pure returns (uint, uint, uint) {
return (b.length, b[a], c);
}
}
// ====
// compileViaYul: also
// ----
// f(uint256,uint16[],uint256): 6, 0x60, 9, 7, 11, 12, 13, 14, 15, 16, 17 -> 7, 17, 9

View File

@ -0,0 +1,13 @@
contract C {
enum E { A, B }
function f(E e) public pure returns (uint x) {
assembly { x := e }
}
}
// ====
// ABIEncoderV1Only: true
// ----
// f(uint8): 0 -> 0
// f(uint8): 1 -> 1
// f(uint8): 2 -> 2
// f(uint8): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> 0xff

View File

@ -0,0 +1,18 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint a, bytes memory b, uint c)
public pure returns (uint, uint, byte, uint) {
return (a, b.length, b[3], c);
}
function f_external(uint a, bytes calldata b, uint c)
external pure returns (uint, uint, byte, uint) {
return (a, b.length, b[3], c);
}
}
// ====
// compileViaYul: also
// ----
// f(uint256,bytes,uint256): 6, 0x60, 9, 7, "abcdefg" -> 6, 7, "d", 9
// f_external(uint256,bytes,uint256): 6, 0x60, 9, 7, "abcdefg" -> 6, 7, "d", 9

View File

@ -41,6 +41,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// EVMVersion: >homestead
// ----
// g() -> 32, 132, hex"15cfcc01", 32, 32, 1, 42, hex"00000000000000000000000000000000000000000000000000000000"

View File

@ -0,0 +1,12 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint a, uint16[] memory b, uint c)
public pure returns (uint, uint, uint) {
return (b.length, b[a], c);
}
}
// ====
// compileViaYul: also
// ----
// f(uint256,uint16[],uint256): 6, 0x60, 9, 7, 11, 12, 13, 14, 15, 16, 17 -> 7, 17, 9

View File

@ -0,0 +1,32 @@
pragma experimental ABIEncoderV2;
contract C {
function f(uint a, uint16[][] memory b, uint[2][][3] memory c, uint d)
public pure returns (uint, uint, uint, uint, uint, uint, uint) {
return (a, b.length, b[1].length, b[1][1], c[1].length, c[1][1][1], d);
}
function test() public view returns (uint, uint, uint, uint, uint, uint, uint) {
uint16[][] memory b = new uint16[][](3);
b[0] = new uint16[](2);
b[0][0] = 0x55;
b[0][1] = 0x56;
b[1] = new uint16[](4);
b[1][0] = 0x65;
b[1][1] = 0x66;
b[1][2] = 0x67;
b[1][3] = 0x68;
uint[2][][3] memory c;
c[0] = new uint[2][](1);
c[0][0][1] = 0x75;
c[1] = new uint[2][](5);
c[1][1][1] = 0x85;
return this.f(12, b, c, 13);
}
}
// ====
// compileViaYul: also
// ----
// test() -> 12, 3, 4, 0x66, 5, 0x85, 13
// f(uint256,uint16[][],uint256[2][][3],uint256): 12, 0x80, 0x220, 13, 3, 0x60, 0xC0, 0x160, 2, 85, 86, 4, 101, 102, 103, 104, 0, 0x60, 0xC0, 0x220, 1, 0, 117, 5, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, 0 -> 12, 3, 4, 0x66, 5, 0x85, 13

View File

@ -0,0 +1,15 @@
pragma experimental ABIEncoderV2;
contract C {
enum E { A, B }
function f(E e) public pure returns (uint x) {
assembly { x := e }
}
}
// ====
// compileViaYul: also
// ----
// f(uint8): 0 -> 0
// f(uint8): 1 -> 1
// f(uint8): 2 -> FAILURE
// f(uint8): 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> FAILURE

View File

@ -7,5 +7,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f(bytes): 0x20, 0x80, 0x21, 0x40, 0x7, "abcdefg" -> 0x21, 0x40, 0x7, "abcdefg"

View File

@ -14,5 +14,7 @@ contract c {
uint8(data[97]) == 97;
}
}
// ====
// compileViaYul: also
// ----
// test1() -> true

View File

@ -10,6 +10,8 @@ contract c {
bytes data;
}
// ====
// compileViaYul: also
// ----
// getLength() -> 0
// set(): 1, 2 -> true

View File

@ -6,5 +6,7 @@ contract C {
return s[0];
}
}
// ====
// compileViaYul: also
// ----
// f() -> "a"

View File

@ -5,5 +5,7 @@ contract C {
return s[0];
}
}
// ====
// compileViaYul: also
// ----
// f(bytes): 0x20, 0x08, "abcdefgh" -> "a"

View File

@ -0,0 +1,42 @@
pragma experimental ABIEncoderV2;
struct S {
uint16 x;
bytes a;
uint16 y;
bytes b;
}
contract C {
uint padding;
S data;
function f() public returns (bytes memory, bytes memory) {
S memory x;
x.x = 7;
x.b = "1234567890123456789012345678901 1234567890123456789012345678901 123456789";
x.a = "abcdef";
x.y = 9;
data = x;
return (data.a, data.b);
}
function g() public returns (bytes memory, bytes memory) {
S memory x;
x.x = 7;
x.b = "12345678923456789";
x.a = "1234567890123456789012345678901 1234567890123456789012345678901 123456789";
x.y = 9;
data = x;
return (data.a, data.b);
}
function h() public returns (bytes memory, bytes memory) {
S memory x;
data = x;
return (data.a, data.b);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x40, 0x80, 6, 0x6162636465660000000000000000000000000000000000000000000000000000, 0x49, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738390000000000000000000000000000000000000000000000
// g() -> 0x40, 0xc0, 0x49, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738390000000000000000000000000000000000000000000000, 0x11, 0x3132333435363738393233343536373839000000000000000000000000000000
// h() -> 0x40, 0x60, 0x00, 0x00
// storage: empty

View File

@ -0,0 +1,50 @@
function dataslot() pure returns (bytes32) {
return keccak256(abi.encode(1));
}
function readDataSlot(uint offset) view returns (bytes32 r) {
bytes32 s = dataslot();
assembly { r := sload(add(s, offset)) }
}
function readDataSlot() view returns (bytes32) {
return readDataSlot(0);
}
function readHead() view returns (bytes32 r) {
assembly { r := sload(1) }
}
contract C {
uint padding;
bytes data;
function f() public returns (uint) {
bytes32 zero;
if (!(readDataSlot() == zero)) return 1;
data = "abc";
if (!(readDataSlot() == zero)) return 2;
data = "1234567890123456789012345678901234567890123456789012345678901234567890";
if (!(readDataSlot() != zero)) return 3;
if (!(readDataSlot(1) != zero)) return 4;
if (!(readDataSlot(2) != zero)) return 5;
if (!(readDataSlot(3) == zero)) return 6;
if (!(readDataSlot(4) == zero)) return 7;
data = "abc";
if (!(readDataSlot() == zero)) return 8;
if (!(readDataSlot(1) == zero)) return 9;
if (!(readDataSlot(2) == zero)) return 10;
if (!(readDataSlot(3) == zero)) return 11;
data = "1234567890123456789012345678901234567890123456789012345678901234567890";
data = "123456789012345678901234567890123456";
if (!(readDataSlot() != zero)) return 12;
if (!(readDataSlot(1) != zero)) return 13;
if (!(readDataSlot(2) == zero)) return 14;
if (!(readDataSlot(3) == zero)) return 15;
return 0xff;
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0xff

View File

@ -17,6 +17,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// one() -> 3
// two() -> FAILURE, hex"4e487b71", 0x51

View File

@ -5,6 +5,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// f(uint256): 0 -> 0x20, 0x4, "This"
// f(uint256): 1 -> 0x20, 0x2, "is"

View File

@ -13,5 +13,7 @@ contract C {
return this.h(a);
}
}
// ====
// compileViaYul: also
// ----
// g() -> 0x0700000000000000000000000000000000000000000000000000000000000000

View File

@ -16,6 +16,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// use(uint256): 3 -> 6
// result_in_constructor() -> 4

View File

@ -0,0 +1,25 @@
contract C {
uint16 public result_in_constructor;
function(uint16) returns (uint16) internal x;
uint16 public other = 0x1fff;
constructor() {
x = doubleInv;
result_in_constructor = use(2);
}
function doubleInv(uint16 _arg) public returns (uint16 _ret) {
_ret = ~(_arg * 2);
}
function use(uint16 _arg) public returns (uint16) {
return x(_arg);
}
}
// ====
// compileViaYul: also
// ----
// use(uint16): 3 -> 0xfff9
// result_in_constructor() -> 0xfffb
// other() -> 0x1fff

View File

@ -14,5 +14,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// t() -> 7

View File

@ -17,5 +17,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// t() -> 7

View File

@ -0,0 +1,13 @@
contract test {
function f() public returns(uint ret) {
ret = 1;
for (;;) {
ret += 1;
if (ret >= 10) break;
}
}
}
// ====
// compileViaYul: also
// ----
// f() -> 10

View File

@ -0,0 +1,22 @@
contract collatz {
function run(uint x) public returns(uint y) {
while ((y = x) > 1) {
if (x % 2 == 0) x = evenStep(x);
else x = oddStep(x);
}
}
function evenStep(uint x) public returns(uint y) {
return x / 2;
}
function oddStep(uint x) public returns(uint y) {
return 3 * x + 1;
}
}
// ====
// compileViaYul: also
// ----
// run(uint256): 0 -> 0
// run(uint256): 1 -> 1
// run(uint256): 2 -> 1
// run(uint256): 8 -> 1
// run(uint256): 127 -> 1

View File

@ -16,5 +16,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// t() -> FAILURE, hex"4e487b71", 0x51

View File

@ -20,6 +20,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// set() -> 7
// ca() -> 7

View File

@ -6,5 +6,7 @@ contract C {
return [this.f, this.g][0]{value: 1}();
}
}
// ====
// compileViaYul: also
// ----
// h(), 1 ether -> 1

View File

@ -25,6 +25,8 @@ contract Flow {
}
}
// ====
// compileViaYul: also
// ----
// success() -> false
// f() -> 7

View File

@ -11,5 +11,7 @@ contract C {
return x + 1;
}
}
// ====
// compileViaYul: also
// ----
// f(uint256): 7 -> 8

View File

@ -14,5 +14,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// test() -> true

View File

@ -0,0 +1,34 @@
struct S {
uint16 a;
function() returns (uint) x;
uint16 b;
}
contract Flow {
S[2] t;
function X() internal pure returns (uint) {
return 1;
}
function Y() internal pure returns (uint) {
return 2;
}
constructor() {
t[0].a = 0xff07;
t[0].b = 0xff07;
t[1].x = Y;
t[1].a = 0xff07;
t[1].b = 0xff07;
t[0].x = X;
}
function f() public returns (uint, uint) {
return (t[0].x(), t[1].x());
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1, 2

View File

@ -7,5 +7,7 @@ contract Test {
}
}
// ====
// compileViaYul: also
// ----
// f() -> FAILURE, hex"4e487b71", 0x51

View File

@ -0,0 +1,11 @@
contract test {
function run(uint x1, uint x2, uint x3) public returns(uint y) {
uint8 a = 0x1; uint8 b = 0x10; uint16 c = 0x100;
y = a + b + c + x1 + x2 + x3;
y += b + x2;
}
}
// ====
// compileViaYul: also
// ----
// run(uint256,uint256,uint256): 0x1000, 0x10000, 0x100000 -> 0x121121

View File

@ -0,0 +1,9 @@
contract test {
function run() public returns(int256 y) {
return -int32(10) * -int64(20);
}
}
// ====
// compileViaYul: also
// ----
// run() -> 200

View File

@ -29,6 +29,8 @@ contract Homer is ERC165, Simpson {
}
}
// ====
// compileViaYul: also
// ----
// supportsInterface(bytes4): left(0x01ffc9a0) -> false
// supportsInterface(bytes4): left(0x01ffc9a7) -> true

View File

@ -40,6 +40,8 @@ contract Lisa is ERC165MappingImplementation, Simpson {
}
}
// ====
// compileViaYul: also
// ----
// supportsInterface(bytes4): left(0x01ffc9a0) -> false
// supportsInterface(bytes4): left(0x01ffc9a7) -> true

View File

@ -14,6 +14,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// g() -> 2
// h() -> FAILURE, hex"4e487b71", 0x51

View File

@ -0,0 +1,22 @@
contract test {
uint value1;
uint value2;
function f(uint x, uint y) public returns (uint w) {
uint value3 = y;
value1 += x;
value3 *= x;
value2 *= value3 + value1;
return value2 += 7;
}
}
// ====
// compileViaYul: also
// ----
// f(uint256,uint256): 0, 6 -> 7
// f(uint256,uint256): 1, 3 -> 0x23
// f(uint256,uint256): 2, 25 -> 0x0746
// f(uint256,uint256): 3, 69 -> 396613
// f(uint256,uint256): 4, 84 -> 137228105
// f(uint256,uint256): 5, 2 -> 0xcc7c5e28
// f(uint256,uint256): 6, 51 -> 1121839760671
// f(uint256,uint256): 7, 48 -> 408349672884251

Some files were not shown because too many files have changed in this diff Show More