diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index 97f3fdc98..a541a58a2 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -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(); diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 076592053..d8b8c3017 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -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{"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 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; } diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 52c82ff6e..d59bb89ba 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -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 { diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index ff31eb8bc..edcf74405 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -395,7 +395,7 @@ 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()) ) { @@ -567,7 +567,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) else if (_variable.visibility() >= Visibility::Public) { FunctionType getter(_variable); - if (!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + if (!useABICoderV2()) { vector unsupportedTypes; for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes()) @@ -692,7 +692,7 @@ 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( @@ -1897,7 +1897,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()) @@ -2189,7 +2189,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(), ""); @@ -2337,7 +2337,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) { returnTypes = typeCheckABIDecodeAndRetrieveReturnType( _functionCall, - experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) + useABICoderV2() ); break; } @@ -3384,10 +3384,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; + } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 70041766d..b86a19522 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -166,7 +166,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. diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 25fec855f..dbb77aafc 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -94,6 +94,7 @@ struct SourceUnitAnnotation: ASTAnnotation SetOnce>> exportedSymbols; /// Experimental features. std::set experimentalFeatures; + SetOnce useABICoderV2; }; struct ScopableAnnotation diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 14e302bc9..64f92f183 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -1162,7 +1162,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); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index bd37c3686..d32349e89 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -76,11 +76,8 @@ public: langutil::EVMVersion const& evmVersion() const { return m_evmVersion; } - /// Update currently enabled set of experimental features. - void setExperimentalFeatures(std::set const& _features) { m_experimentalFeatures = _features; } - std::set 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); @@ -361,8 +358,7 @@ private: /// Version of the EVM to compile against. langutil::EVMVersion m_evmVersion; RevertStrings const m_revertStrings; - /// Activated experimental features. - std::set m_experimentalFeatures; + bool m_useABICoderV2 = false; /// Other already compiled contracts to be used in contract creation calls. std::map> m_otherCompilers; /// Storage offsets of state variables diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index f9be04c6e..f64b54d8e 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -230,7 +230,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory) { /// Stack: - 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: ... - 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) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index f76613095..14193962b 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -126,7 +126,7 @@ void ContractCompiler::initializeContext( map> 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) @@ -1324,13 +1324,13 @@ void ContractCompiler::appendModifierOrFunctionCode() if (codeBlock) { - std::set 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; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 9dff56afc..444e312ad 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1682,7 +1682,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); @@ -2490,7 +2490,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) diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index a10604aee..4b599da68 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -61,6 +62,7 @@ evmasm::AssemblyItems compileContract(std::shared_ptr _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()); diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 8c0ce780d..4f442919c 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -93,18 +94,20 @@ Declaration const& resolveDeclaration( } bytes compileFirstExpression( - const string& _sourceCode, + string const& _sourceCode, vector> _functions = {}, vector> _localVariables = {} ) { + string sourceCode = "pragma solidity >=0.0; // SPDX-License-Identifier: GPL-3\n" + _sourceCode; + ASTPointer sourceUnit; try { ErrorList errors; ErrorReporter errorReporter(errors); sourceUnit = Parser(errorReporter, solidity::test::CommonOptions::get().evmVersion()).parse( - make_shared(CharStream(_sourceCode, "")) + make_shared(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"); @@ -186,7 +190,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); @@ -199,7 +203,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); @@ -228,7 +232,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; } } @@ -258,7 +262,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); @@ -290,7 +294,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); @@ -321,7 +325,7 @@ BOOST_AUTO_TEST_CASE(arithmetic) { char const* sourceCode = R"( contract test { - function f(uint y) { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } + function f(uint y) public { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } } )"; bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); @@ -402,7 +406,7 @@ BOOST_AUTO_TEST_CASE(unary_operators) { char const* sourceCode = R"( contract test { - function f(int y) { !(~- y == 2); } + function f(int y) public { !(~- y == 2); } } )"; bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); @@ -492,7 +496,7 @@ BOOST_AUTO_TEST_CASE(assignment) { char const* sourceCode = R"( contract test { - function f(uint a, uint b) { (a += b) * 2; } + function f(uint a, uint b) public { (a += b) * 2; } } )"; bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); @@ -530,7 +534,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); @@ -543,7 +547,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); @@ -558,7 +562,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); @@ -571,7 +575,7 @@ BOOST_AUTO_TEST_CASE(blockhash) { char const* sourceCode = R"( contract test { - function f() { + function f() public { blockhash(3); } } @@ -603,7 +607,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; } } diff --git a/test/libsolidity/syntaxTests/abiEncoder/conflicting_settings.sol b/test/libsolidity/syntaxTests/abiEncoder/conflicting_settings.sol new file mode 100644 index 000000000..29488de12 --- /dev/null +++ b/test/libsolidity/syntaxTests/abiEncoder/conflicting_settings.sol @@ -0,0 +1,4 @@ +pragma experimental ABIEncoderV2; +pragma abicoder v1; +// ---- +// SyntaxError 3845: (34-53): ABI coder has already been selected for this source unit. diff --git a/test/libsolidity/syntaxTests/abiEncoder/conflicting_settings_reverse.sol b/test/libsolidity/syntaxTests/abiEncoder/conflicting_settings_reverse.sol new file mode 100644 index 000000000..dbdd0392f --- /dev/null +++ b/test/libsolidity/syntaxTests/abiEncoder/conflicting_settings_reverse.sol @@ -0,0 +1,4 @@ +pragma abicoder v1; +pragma experimental ABIEncoderV2; +// ---- +// SyntaxError 8273: (20-53): ABI coder V1 has already been selected through "pragma abicoder v1". diff --git a/test/libsolidity/syntaxTests/abiEncoder/invalid_pragma_value.sol b/test/libsolidity/syntaxTests/abiEncoder/invalid_pragma_value.sol new file mode 100644 index 000000000..cbfa03b3f --- /dev/null +++ b/test/libsolidity/syntaxTests/abiEncoder/invalid_pragma_value.sol @@ -0,0 +1,3 @@ +pragma abicoder something; +// ---- +// SyntaxError 2745: (0-26): Expected either "pragma abicoder v1" or "pragma abicoder v2". diff --git a/test/libsolidity/syntaxTests/abiEncoder/same_setting_twice.sol b/test/libsolidity/syntaxTests/abiEncoder/same_setting_twice.sol new file mode 100644 index 000000000..0303bcb14 --- /dev/null +++ b/test/libsolidity/syntaxTests/abiEncoder/same_setting_twice.sol @@ -0,0 +1,4 @@ +pragma experimental ABIEncoderV2; +pragma abicoder v2; +// ---- +// SyntaxError 3845: (34-53): ABI coder has already been selected for this source unit. diff --git a/test/libsolidity/syntaxTests/abiEncoder/select_v1.sol b/test/libsolidity/syntaxTests/abiEncoder/select_v1.sol new file mode 100644 index 000000000..ecae09ad6 --- /dev/null +++ b/test/libsolidity/syntaxTests/abiEncoder/select_v1.sol @@ -0,0 +1 @@ +pragma abicoder v1; \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/abiEncoder/selected_twice.sol b/test/libsolidity/syntaxTests/abiEncoder/selected_twice.sol new file mode 100644 index 000000000..424c2c7f2 --- /dev/null +++ b/test/libsolidity/syntaxTests/abiEncoder/selected_twice.sol @@ -0,0 +1,4 @@ +pragma abicoder v1; +pragma abicoder v1; +// ---- +// SyntaxError 3845: (20-39): ABI coder has already been selected for this source unit. diff --git a/test/libsolidity/syntaxTests/abiEncoder/selected_twice_v2.sol b/test/libsolidity/syntaxTests/abiEncoder/selected_twice_v2.sol new file mode 100644 index 000000000..a0d0cf2d8 --- /dev/null +++ b/test/libsolidity/syntaxTests/abiEncoder/selected_twice_v2.sol @@ -0,0 +1,2 @@ +pragma abicoder v2; +pragma experimental ABIEncoderV2; \ No newline at end of file