From 4ae6d1cfe47ac91db6455f05e57e7de7b8b68fed Mon Sep 17 00:00:00 2001 From: rodiazet Date: Mon, 22 Jul 2024 11:10:20 +0200 Subject: [PATCH 01/15] prep: Extend `StandardCompiler` settings with `eofVersion` flag. Fix typo in input parsing Co-authored-by: Daniel Kirchner --- libsolidity/interface/StandardCompiler.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index b209987ddd3f..0c1ccb6b7653 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -426,7 +426,7 @@ std::optional checkAuxiliaryInputKeys(Json const& _input) std::optional checkSettingsKeys(Json const& _input) { - static std::set keys{"debug", "evmVersion", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; + static std::set keys{"debug", "evmVersion", "eofVersion", "libraries", "metadata", "modelChecker", "optimizer", "outputSelection", "remappings", "stopAfter", "viaIR"}; return checkKeys(_input, keys, "settings"); } @@ -837,7 +837,7 @@ std::variant StandardCompiler::parseI { if (!settings["eofVersion"].is_number_unsigned()) return formatFatalError(Error::Type::JSONError, "eofVersion must be an unsigned integer."); - auto eofVersion = settings["evmVersion"].get(); + auto eofVersion = settings["eofVersion"].get(); if (eofVersion != 1) return formatFatalError(Error::Type::JSONError, "Invalid EOF version requested."); ret.eofVersion = 1; @@ -1308,6 +1308,7 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); compilerStack.setViaIR(_inputsAndSettings.viaIR); compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); + compilerStack.setEOFVersion(_inputsAndSettings.eofVersion); compilerStack.setRemappings(std::move(_inputsAndSettings.remappings)); compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings); From edc3b401eba7cc774cf7013c4feb45b0c1df1a3e Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 25 Jul 2024 16:37:30 +0200 Subject: [PATCH 02/15] prep: Pass `_eofVersion` to `libevmasm/Assembly` class Co-authored-by: Daniel Kirchner --- libevmasm/Assembly.cpp | 22 ++++++++++++++----- libevmasm/Assembly.h | 11 ++++++++-- libsolidity/codegen/Compiler.h | 6 ++--- libsolidity/codegen/CompilerContext.h | 3 ++- libsolidity/interface/CompilerStack.cpp | 10 ++++++++- libyul/YulStack.cpp | 2 +- libyul/backends/evm/AbstractAssembly.h | 2 +- libyul/backends/evm/EVMObjectCompiler.cpp | 4 ++-- libyul/backends/evm/EthAssemblyAdapter.cpp | 4 ++-- libyul/backends/evm/EthAssemblyAdapter.h | 2 +- libyul/backends/evm/NoOutputAssembly.cpp | 2 +- libyul/backends/evm/NoOutputAssembly.h | 2 +- test/libevmasm/Assembler.cpp | 21 +++++++++--------- test/libevmasm/Optimiser.cpp | 4 ++-- test/libsolidity/Assembly.cpp | 1 + .../SolidityExpressionCompiler.cpp | 1 + test/libyul/EVMCodeTransformTest.cpp | 2 +- test/tools/fuzzer_common.cpp | 2 +- 18 files changed, 65 insertions(+), 36 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 91773a8e8403..3b5d702f95b3 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -549,7 +549,7 @@ std::pair, std::vector> Assembly::fromJSO "Member 'sourceList' may only be present in the root JSON object." ); - auto result = std::make_shared(EVMVersion{}, _level == 0 /* _creation */, "" /* _name */); + auto result = std::make_shared(EVMVersion{}, _level == 0 /* _creation */, std::nullopt, "" /* _name */); std::vector parsedSourceList; if (_json.contains("sourceList")) { @@ -735,7 +735,7 @@ std::map const& Assembly::optimiseInternal( { count = 0; - if (_settings.runInliner) + if (_settings.runInliner && !m_eofVersion.has_value()) Inliner{ m_items, _tagsReferencedFromOutside, @@ -762,7 +762,7 @@ std::map const& Assembly::optimiseInternal( } // This only modifies PushTags, we have to run again to actually remove code. - if (_settings.runDeduplicate) + if (_settings.runDeduplicate && !m_eofVersion.has_value()) { BlockDeduplicator deduplicator{m_items}; if (deduplicator.deduplicate()) @@ -787,7 +787,8 @@ std::map const& Assembly::optimiseInternal( } } - if (_settings.runCSE) + // TODO: investigate for EOF + if (_settings.runCSE && !m_eofVersion.has_value()) { // Control flow graph optimization has been here before but is disabled because it // assumes we only jump to tags that are pushed. This is not the case anymore with @@ -839,7 +840,8 @@ std::map const& Assembly::optimiseInternal( } } - if (_settings.runConstantOptimiser) + // TODO: investigate for EOF + if (_settings.runConstantOptimiser && !m_eofVersion.has_value()) ConstantOptimisationMethod::optimiseConstants( isCreation(), isCreation() ? 1 : _settings.expectedExecutionsPerDeployment, @@ -862,6 +864,8 @@ LinkerObject const& Assembly::assemble() const LinkerObject& ret = m_assembledObject; + bool const eof = m_eofVersion.has_value(); + size_t subTagSize = 1; std::map>> immutableReferencesBySub; for (auto const& sub: m_subs) @@ -957,6 +961,7 @@ LinkerObject const& Assembly::assemble() const } case PushTag: { + assertThrow(!eof, AssemblyException, "PushTag in EOF code"); ret.bytecode.push_back(tagPush); tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); @@ -968,6 +973,7 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); break; case PushSub: + assertThrow(!eof, AssemblyException, "PushSub in EOF code"); assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, ""); ret.bytecode.push_back(dataRefPush); subRef.insert(std::make_pair(static_cast(i.data()), ret.bytecode.size())); @@ -975,6 +981,7 @@ LinkerObject const& Assembly::assemble() const break; case PushSubSize: { + assertThrow(!eof, AssemblyException, "PushSubSize in EOF code"); assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, ""); auto s = subAssemblyById(static_cast(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); @@ -987,6 +994,7 @@ LinkerObject const& Assembly::assemble() const } case PushProgramSize: { + assertThrow(!eof, AssemblyException, "PushProgramSize in EOF code"); ret.bytecode.push_back(dataRefPush); sizeRef.push_back(static_cast(ret.bytecode.size())); ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); @@ -998,6 +1006,7 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + 20); break; case PushImmutable: + assertThrow(!eof, AssemblyException, "PushImmutable in EOF code"); ret.bytecode.push_back(static_cast(Instruction::PUSH32)); // Maps keccak back to the "identifier" std::string of that immutable. ret.immutableReferences[i.data()].first = m_immutables.at(i.data()); @@ -1011,6 +1020,7 @@ LinkerObject const& Assembly::assemble() const break; case AssignImmutable: { + assertThrow(!eof, AssemblyException, "AssignImmutable in EOF code"); // Expect 2 elements on stack (source, dest_base) auto const& offsets = immutableReferencesBySub[i.data()].second; for (size_t i = 0; i < offsets.size(); ++i) @@ -1063,7 +1073,7 @@ LinkerObject const& Assembly::assemble() const "Some immutables were read from but never assigned, possibly because of optimization." ); - if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty()) + if (!eof && (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty())) // Append an INVALID here to help tests find miscompilation. ret.bytecode.push_back(static_cast(Instruction::INVALID)); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 123d7f3ddd8c..2b0ef93df220 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -48,8 +48,14 @@ using AssemblyPointer = std::shared_ptr; class Assembly { public: - Assembly(langutil::EVMVersion _evmVersion, bool _creation, std::string _name): m_evmVersion(_evmVersion), m_creation(_creation), m_name(std::move(_name)) { } - + Assembly(langutil::EVMVersion _evmVersion, bool _creation, std::optional _eofVersion, std::string _name): + m_evmVersion(_evmVersion), + m_creation(_creation), + m_eofVersion(_eofVersion), + m_name(std::move(_name)) + {} + + std::optional eofVersion() const { return m_eofVersion; } AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } /// Returns a tag identified by the given name. Creates it if it does not yet exist. @@ -242,6 +248,7 @@ class Assembly int m_deposit = 0; /// True, if the assembly contains contract creation code. bool const m_creation = false; + std::optional m_eofVersion; /// Internal name of the assembly object, only used with the Yul backend /// currently std::string m_name; diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 1267c1a2d92c..63e362e3ff0e 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -37,10 +37,10 @@ namespace solidity::frontend class Compiler { public: - Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings): + Compiler(langutil::EVMVersion _evmVersion, std::optional _eofVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings): m_optimiserSettings(std::move(_optimiserSettings)), - m_runtimeContext(_evmVersion, _revertStrings), - m_context(_evmVersion, _revertStrings, &m_runtimeContext) + m_runtimeContext(_evmVersion, _eofVersion, _revertStrings), + m_context(_evmVersion, _eofVersion, _revertStrings, &m_runtimeContext) { } /// Compiles a contract. diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index c6972f92b83b..80fee14575df 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -62,10 +62,11 @@ class CompilerContext public: explicit CompilerContext( langutil::EVMVersion _evmVersion, + std::optional _eofVersion, RevertStrings _revertStrings, CompilerContext* _runtimeContext = nullptr ): - m_asm(std::make_shared(_evmVersion, _runtimeContext != nullptr, std::string{})), + m_asm(std::make_shared(_evmVersion, _runtimeContext != nullptr, _eofVersion, std::string{})), m_evmVersion(_evmVersion), m_revertStrings(_revertStrings), m_reservedMemory{0}, diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index dd5d20126ebe..da5d1268fed8 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -863,6 +863,10 @@ std::string const* CompilerStack::sourceMapping(std::string const& _contractName { solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); + // TODO + if (m_eofVersion.has_value()) + return nullptr; + Contract const& c = contract(_contractName); if (!c.sourceMapping) { @@ -876,6 +880,10 @@ std::string const* CompilerStack::runtimeSourceMapping(std::string const& _contr { solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); + // TODO + if (m_eofVersion.has_value()) + return nullptr; + Contract const& c = contract(_contractName); if (!c.runtimeSourceMapping) { @@ -1412,7 +1420,7 @@ void CompilerStack::compileContract( Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); - std::shared_ptr compiler = std::make_shared(m_evmVersion, m_revertStrings, m_optimiserSettings); + std::shared_ptr compiler = std::make_shared(m_evmVersion, m_eofVersion, m_revertStrings, m_optimiserSettings); compiledContract.compiler = compiler; solAssert(!m_viaIR, ""); diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index dbe30bb136d5..77fd666e92b8 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -305,7 +305,7 @@ YulStack::assembleEVMWithDeployed(std::optional _deployName) yulAssert(m_parserResult->hasCode(), ""); yulAssert(m_parserResult->analysisInfo, ""); - evmasm::Assembly assembly(m_evmVersion, true, {}); + evmasm::Assembly assembly(m_evmVersion, true, m_eofVersion, {}); EthAssemblyAdapter adapter(assembly); // NOTE: We always need stack optimization when Yul optimizer is disabled (unless code contains diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index fe45008962c8..30b42045f44f 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -99,7 +99,7 @@ class AbstractAssembly /// Append the assembled size as a constant. virtual void appendAssemblySize() = 0; /// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset. - virtual std::pair, SubID> createSubAssembly(bool _creation, std::string _name = "") = 0; + virtual std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = "") = 0; /// Appends the offset of the given sub-assembly or data. virtual void appendDataOffset(std::vector const& _subPath) = 0; /// Appends the size of the given sub-assembly or data. diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp index ca9f90dfae34..c5b363293f39 100644 --- a/libyul/backends/evm/EVMObjectCompiler.cpp +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -56,7 +56,7 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) if (auto* subObject = dynamic_cast(subNode.get())) { bool isCreation = !boost::ends_with(subObject->name, "_deployed"); - auto subAssemblyAndID = m_assembly.createSubAssembly(isCreation, subObject->name); + auto subAssemblyAndID = m_assembly.createSubAssembly(isCreation, m_eofVersion, subObject->name); context.subIDs[subObject->name] = subAssemblyAndID.second; subObject->subId = subAssemblyAndID.second; compile(*subObject, *subAssemblyAndID.first, m_dialect, _optimize, m_eofVersion); @@ -75,7 +75,7 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) yulAssert(_object.hasCode(), "No code."); if (m_eofVersion.has_value()) yulAssert( - _optimize && (m_dialect.evmVersion() == langutil::EVMVersion()), + _optimize && (m_dialect.evmVersion() >= langutil::EVMVersion::prague()), "Experimental EOF support is only available for optimized via-IR compilation and the most recent EVM version." ); if (_optimize && m_dialect.evmVersion().canOverchargeGasForCall()) diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index 79146f859c26..0bb680516f96 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -121,9 +121,9 @@ void EthAssemblyAdapter::appendAssemblySize() m_assembly.appendProgramSize(); } -std::pair, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly(bool _creation, std::string _name) +std::pair, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name) { - std::shared_ptr assembly{std::make_shared(m_assembly.evmVersion(), _creation, std::move(_name))}; + std::shared_ptr assembly{std::make_shared(m_assembly.evmVersion(), _creation, _eofVersion, std::move(_name))}; auto sub = m_assembly.newSub(assembly); return {std::make_shared(*assembly), static_cast(sub.data())}; } diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index 011081dedaac..1349d120606c 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -55,7 +55,7 @@ class EthAssemblyAdapter: public AbstractAssembly void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override; void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override; void appendAssemblySize() override; - std::pair, SubID> createSubAssembly(bool _creation, std::string _name = {}) override; + std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = {}) override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 2a3cca52cc32..eedadb3ef680 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -97,7 +97,7 @@ void NoOutputAssembly::appendAssemblySize() appendInstruction(evmasm::Instruction::PUSH1); } -std::pair, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly(bool, std::string) +std::pair, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly(bool, std::optional, std::string) { yulAssert(false, "Sub assemblies not implemented."); return {}; diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 8d7dda0bb50c..ce720027e7e5 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -65,7 +65,7 @@ class NoOutputAssembly: public AbstractAssembly void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override; void appendAssemblySize() override; - std::pair, SubID> createSubAssembly(bool _creation, std::string _name = "") override; + std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = "") override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index afad9d13ddc5..06c7fb6a97eb 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -61,15 +62,15 @@ BOOST_AUTO_TEST_CASE(all_assembly_items) { "verbatim.asm", 2 } }; EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); - Assembly _assembly{evmVersion, false, {}}; + Assembly _assembly{evmVersion, false, solidity::test::CommonOptions::get().eofVersion(), {}}; auto root_asm = std::make_shared("root.asm"); _assembly.setSourceLocation({1, 3, root_asm}); - Assembly _subAsm{evmVersion, false, {}}; + Assembly _subAsm{evmVersion, false, solidity::test::CommonOptions::get().eofVersion(), {}}; auto sub_asm = std::make_shared("sub.asm"); _subAsm.setSourceLocation({6, 8, sub_asm}); - Assembly _verbatimAsm(evmVersion, true, ""); + Assembly _verbatimAsm(evmVersion, true, solidity::test::CommonOptions::get().eofVersion(), ""); auto verbatim_asm = std::make_shared("verbatim.asm"); _verbatimAsm.setSourceLocation({8, 18, verbatim_asm}); @@ -246,7 +247,7 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps) { *subName, 1 } }; - auto subAsm = std::make_shared(evmVersion, false, std::string{}); + auto subAsm = std::make_shared(evmVersion, false, solidity::test::CommonOptions::get().eofVersion(), std::string{}); for (char i = 0; i < numImmutables; ++i) { for (int r = 0; r < numActualRefs; ++r) @@ -256,7 +257,7 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps) } } - Assembly assembly{evmVersion, true, {}}; + Assembly assembly{evmVersion, true, solidity::test::CommonOptions::get().eofVersion(), {}}; for (char i = 1; i <= numImmutables; ++i) { assembly.setSourceLocation({10*i, 10*i + 3+i, assemblyName}); @@ -306,11 +307,11 @@ BOOST_AUTO_TEST_CASE(immutable) { "sub.asm", 1 } }; EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); - Assembly _assembly{evmVersion, true, {}}; + Assembly _assembly{evmVersion, true, solidity::test::CommonOptions::get().eofVersion(), {}}; auto root_asm = std::make_shared("root.asm"); _assembly.setSourceLocation({1, 3, root_asm}); - Assembly _subAsm{evmVersion, false, {}}; + Assembly _subAsm{evmVersion, false, solidity::test::CommonOptions::get().eofVersion(), {}}; auto sub_asm = std::make_shared("sub.asm"); _subAsm.setSourceLocation({6, 8, sub_asm}); _subAsm.appendImmutable("someImmutable"); @@ -404,10 +405,10 @@ BOOST_AUTO_TEST_CASE(immutable) BOOST_AUTO_TEST_CASE(subobject_encode_decode) { EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion(); - Assembly assembly{evmVersion, true, {}}; + Assembly assembly{evmVersion, true, solidity::test::CommonOptions::get().eofVersion(), {}}; - std::shared_ptr subAsmPtr = std::make_shared(evmVersion, false, std::string{}); - std::shared_ptr subSubAsmPtr = std::make_shared(evmVersion, false, std::string{}); + std::shared_ptr subAsmPtr = std::make_shared(evmVersion, false, solidity::test::CommonOptions::get().eofVersion(), std::string{}); + std::shared_ptr subSubAsmPtr = std::make_shared(evmVersion, false, solidity::test::CommonOptions::get().eofVersion(), std::string{}); assembly.appendSubroutine(subAsmPtr); subAsmPtr->appendSubroutine(subSubAsmPtr); diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 13fa58bf4bdf..13d9d6f2bb6f 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1346,8 +1346,8 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies) settings.evmVersion = solidity::test::CommonOptions::get().evmVersion(); settings.expectedExecutionsPerDeployment = OptimiserSettings{}.expectedExecutionsPerDeployment; - Assembly main{settings.evmVersion, false, {}}; - AssemblyPointer sub = std::make_shared(settings.evmVersion, true, std::string{}); + Assembly main{settings.evmVersion, false, solidity::test::CommonOptions::get().eofVersion(), {}}; + AssemblyPointer sub = std::make_shared(settings.evmVersion, true, solidity::test::CommonOptions::get().eofVersion(), std::string{}); sub->append(u256(1)); auto t1 = sub->newTag(); diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 7def76e69fe2..e25ac55c2762 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -86,6 +86,7 @@ evmasm::AssemblyItems compileContract(std::shared_ptr _sourceCode) { Compiler compiler( solidity::test::CommonOptions::get().evmVersion(), + solidity::test::CommonOptions::get().eofVersion(), RevertStrings::Default, solidity::test::CommonOptions::get().optimize ? OptimiserSettings::standard() : OptimiserSettings::minimal() ); diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 4810b01439b2..09e893facad4 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -139,6 +139,7 @@ bytes compileFirstExpression( CompilerContext context( solidity::test::CommonOptions::get().evmVersion(), + solidity::test::CommonOptions::get().eofVersion(), RevertStrings::Default ); context.resetVisitedNodes(contract); diff --git a/test/libyul/EVMCodeTransformTest.cpp b/test/libyul/EVMCodeTransformTest.cpp index 4c2eecab05fe..e54ab9c8f002 100644 --- a/test/libyul/EVMCodeTransformTest.cpp +++ b/test/libyul/EVMCodeTransformTest.cpp @@ -67,7 +67,7 @@ TestCase::TestResult EVMCodeTransformTest::run(std::ostream& _stream, std::strin return TestResult::FatalError; } - evmasm::Assembly assembly{solidity::test::CommonOptions::get().evmVersion(), false, {}}; + evmasm::Assembly assembly{solidity::test::CommonOptions::get().evmVersion(), false, std::nullopt, {}}; EthAssemblyAdapter adapter(assembly); EVMObjectCompiler::compile( *stack.parserResult(), diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index adf5a064bef7..1caa5357e534 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -190,7 +190,7 @@ void FuzzerUtil::testConstantOptimizer(std::string const& _input, bool _quiet) for (bool isCreation: {false, true}) { - Assembly assembly{langutil::EVMVersion{}, isCreation, {}}; + Assembly assembly{langutil::EVMVersion{}, isCreation, std::nullopt, {}}; for (u256 const& n: numbers) { if (!_quiet) From 07528fc3e3c743113f7d87b71fb20cf16f87dc47 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Mon, 22 Jul 2024 15:16:34 +0200 Subject: [PATCH 03/15] prep: Change `toBigEndian` interface to support rvalue ref. Co-authored-by: Daniel Kirchner --- libsolutil/Numeric.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libsolutil/Numeric.h b/libsolutil/Numeric.h index be9f0bea243a..f4e88de3c17d 100644 --- a/libsolutil/Numeric.h +++ b/libsolutil/Numeric.h @@ -96,13 +96,13 @@ bool fitsPrecisionBaseX(bigint const& _mantissa, double _log2OfBase, uint32_t _e /// @a Out will typically be either std::string or bytes. /// @a T will typically by unsigned, u160, u256 or bigint. template -inline void toBigEndian(T _val, Out& o_out) +inline void toBigEndian(T _val, Out&& o_out) { static_assert(std::is_same::value || !std::numeric_limits::is_signed, "only unsigned types or bigint supported"); //bigint does not carry sign bit on shift - for (auto i = o_out.size(); i != 0; _val >>= 8, i--) + for (auto i = o_out.size(); i != 0u; _val >>= 8u, i--) { - T v = _val & (T)0xff; - o_out[i - 1] = (typename Out::value_type)(uint8_t)v; + T v = _val & (T)0xffu; + o_out[i - 1u] = (typename std::remove_reference_t::value_type)(uint8_t)v; } } From c0a839e8bb43c3e9b8220ba99a178c363ab3ba69 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 10:46:44 +0200 Subject: [PATCH 04/15] eof: Introduce EOF container format support in `Assembly::assemble` (EIP-3540) Co-authored-by: Daniel Kirchner --- libevmasm/Assembly.cpp | 625 +++++++++++++++--------- libevmasm/Assembly.h | 32 +- libevmasm/ConstantOptimiser.cpp | 73 +-- libevmasm/EVMAssemblyStack.cpp | 6 +- libsolidity/interface/CompilerStack.cpp | 8 +- libyul/YulStack.cpp | 10 +- test/libevmasm/Assembler.cpp | 2 +- test/libevmasm/Optimiser.cpp | 4 +- test/libsolidity/Assembly.cpp | 2 +- 9 files changed, 488 insertions(+), 274 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 3b5d702f95b3..0b50ed06fb21 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -58,11 +58,13 @@ AssemblyItem const& Assembly::append(AssemblyItem _i) { assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); m_deposit += static_cast(_i.deposit()); - m_items.emplace_back(std::move(_i)); - if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid()) - m_items.back().setLocation(m_currentSourceLocation); - m_items.back().m_modifierDepth = m_currentModifierDepth; - return m_items.back(); + solAssert(m_currentCodeSection < m_codeSections.size()); + auto& currentItems = m_codeSections.at(m_currentCodeSection).items; + currentItems.emplace_back(std::move(_i)); + if (!currentItems.back().location().isValid() && m_currentSourceLocation.isValid()) + currentItems.back().setLocation(m_currentSourceLocation); + currentItems.back().m_modifierDepth = m_currentModifierDepth; + return currentItems.back(); } unsigned Assembly::codeSize(unsigned subTagSize) const @@ -70,11 +72,10 @@ unsigned Assembly::codeSize(unsigned subTagSize) const for (unsigned tagSize = subTagSize; true; ++tagSize) { size_t ret = 1; - for (auto const& i: m_data) - ret += i.second.size(); - for (AssemblyItem const& i: m_items) - ret += i.bytesRequired(tagSize, m_evmVersion, Precision::Approximate); + for (auto const& codeSection: m_codeSections) + for (AssemblyItem const& i: codeSection.items) + ret += i.bytesRequired(tagSize, m_evmVersion, Precision::Approximate); if (numberEncodingSize(ret) <= tagSize) return static_cast(ret); } @@ -82,11 +83,14 @@ unsigned Assembly::codeSize(unsigned subTagSize) const void Assembly::importAssemblyItemsFromJSON(Json const& _code, std::vector const& _sourceList) { - solAssert(m_items.empty()); + solAssert(m_codeSections.empty()); + m_codeSections.resize(1); + // TODO: Add support for EOF and more than one code sections. + solUnimplementedAssert(!m_eofVersion.has_value(), "Assembly output for EOF is not yet implemented."); solRequire(_code.is_array(), AssemblyImportException, "Supplied JSON is not an array."); for (auto jsonItemIter = std::begin(_code); jsonItemIter != std::end(_code); ++jsonItemIter) { - AssemblyItem const& newItem = m_items.emplace_back(createAssemblyItemFromJSON(*jsonItemIter, _sourceList)); + AssemblyItem const& newItem = m_codeSections[0].items.emplace_back(createAssemblyItemFromJSON(*jsonItemIter, _sourceList)); if (newItem == Instruction::JUMPDEST) solThrow(AssemblyImportException, "JUMPDEST instruction without a tag"); else if (newItem.type() == AssemblyItemType::Tag) @@ -396,7 +400,9 @@ void Assembly::assemblyStream( { Functionalizer f(_out, _prefix, _sourceCodes, *this); - for (auto const& i: m_items) + // TODO: support EOF + solUnimplementedAssert(!m_eofVersion.has_value(), "Assembly output for EOF is not yet implemented."); + for (auto const& i: m_codeSections.front().items) f.feed(i, _debugInfoSelection); f.flush(); @@ -434,7 +440,10 @@ Json Assembly::assemblyJSON(std::map const& _sourceIndice Json root; root[".code"] = Json::array(); Json& code = root[".code"]; - for (AssemblyItem const& item: m_items) + // TODO: support EOF + solUnimplementedAssert(!m_eofVersion.has_value(), "Assembly output for EOF is not yet implemented."); + solAssert(m_codeSections.size() == 1); + for (AssemblyItem const& item: m_codeSections[0].items) { int sourceIndex = -1; if (item.location().sourceName) @@ -704,7 +713,9 @@ AssemblyItem Assembly::newImmutableAssignment(std::string const& _identifier) Assembly& Assembly::optimise(OptimiserSettings const& _settings) { - optimiseInternal(_settings, {}); + // TODO: implement and verify `optimiseInternal` implementation for EOF. + if (!m_eofVersion.has_value()) + optimiseInternal(_settings, {}); return *this; } @@ -717,16 +728,21 @@ std::map const& Assembly::optimiseInternal( return *m_tagReplacements; // Run optimisation for sub-assemblies. + // TODO: verify and double-check this for EOF. for (size_t subId = 0; subId < m_subs.size(); ++subId) { OptimiserSettings settings = _settings; Assembly& sub = *m_subs[subId]; + std::set referencedTags; + for (auto& codeSection: m_codeSections) + referencedTags += JumpdestRemover::referencedTags(codeSection.items, subId); std::map const& subTagReplacements = sub.optimiseInternal( settings, - JumpdestRemover::referencedTags(m_items, subId) + referencedTags ); // Apply the replacements (can be empty). - BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); + for (auto& codeSection: m_codeSections) + BlockDeduplicator::applyTagReplacement(codeSection.items, subTagReplacements, subId); } std::map tagReplacements; @@ -735,57 +751,68 @@ std::map const& Assembly::optimiseInternal( { count = 0; + // TODO: verify this for EOF. if (_settings.runInliner && !m_eofVersion.has_value()) Inliner{ - m_items, + m_codeSections.front().items, _tagsReferencedFromOutside, _settings.expectedExecutionsPerDeployment, isCreation(), _settings.evmVersion }.optimise(); - if (_settings.runJumpdestRemover) + // TODO: verify this for EOF. + if (_settings.runJumpdestRemover && !m_eofVersion.has_value()) { - JumpdestRemover jumpdestOpt{m_items}; - if (jumpdestOpt.optimise(_tagsReferencedFromOutside)) - count++; + for (auto& codeSection: m_codeSections) + { + JumpdestRemover jumpdestOpt{codeSection.items}; + if (jumpdestOpt.optimise(_tagsReferencedFromOutside)) + count++; + } } - if (_settings.runPeephole) + // TODO: verify this for EOF. + if (_settings.runPeephole && !m_eofVersion.has_value()) { - PeepholeOptimiser peepOpt{m_items, m_evmVersion}; - while (peepOpt.optimise()) + for (auto& codeSection: m_codeSections) { - count++; - assertThrow(count < 64000, OptimizerException, "Peephole optimizer seems to be stuck."); + PeepholeOptimiser peepOpt{codeSection.items, m_evmVersion}; + while (peepOpt.optimise()) + { + count++; + assertThrow(count < 64000, OptimizerException, "Peephole optimizer seems to be stuck."); + } } } // This only modifies PushTags, we have to run again to actually remove code. + // TODO: implement for EOF. if (_settings.runDeduplicate && !m_eofVersion.has_value()) - { - BlockDeduplicator deduplicator{m_items}; - if (deduplicator.deduplicate()) + for (auto& section: m_codeSections) { - for (auto const& replacement: deduplicator.replacedTags()) + BlockDeduplicator deduplicator{section.items}; + if (deduplicator.deduplicate()) { - assertThrow( - replacement.first <= std::numeric_limits::max() && replacement.second <= std::numeric_limits::max(), - OptimizerException, - "Invalid tag replacement." - ); - assertThrow( - !tagReplacements.count(replacement.first), - OptimizerException, - "Replacement already known." - ); - tagReplacements[replacement.first] = replacement.second; - if (_tagsReferencedFromOutside.erase(static_cast(replacement.first))) - _tagsReferencedFromOutside.insert(static_cast(replacement.second)); + for (auto const& replacement: deduplicator.replacedTags()) + { + assertThrow( + replacement.first <= std::numeric_limits::max() && replacement.second <= std::numeric_limits::max(), + OptimizerException, + "Invalid tag replacement." + ); + assertThrow( + !tagReplacements.count(replacement.first), + OptimizerException, + "Replacement already known." + ); + tagReplacements[replacement.first] = replacement.second; + if (_tagsReferencedFromOutside.erase(static_cast(replacement.first))) + _tagsReferencedFromOutside.insert(static_cast(replacement.second)); + } + count++; } - count++; } - } // TODO: investigate for EOF if (_settings.runCSE && !m_eofVersion.has_value()) @@ -795,17 +822,18 @@ std::map const& Assembly::optimiseInternal( // function types that can be stored in storage. AssemblyItems optimisedItems; - bool usesMSize = ranges::any_of(m_items, [](AssemblyItem const& _i) { + auto& items = m_codeSections.front().items; + bool usesMSize = ranges::any_of(items, [](AssemblyItem const& _i) { return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode; }); - auto iter = m_items.begin(); - while (iter != m_items.end()) + auto iter = items.begin(); + while (iter != items.end()) { KnownState emptyState; CommonSubexpressionEliminator eliminator{emptyState}; auto orig = iter; - iter = eliminator.feedItems(iter, m_items.end(), usesMSize); + iter = eliminator.feedItems(iter, items.end(), usesMSize); bool shouldReplace = false; AssemblyItems optimisedChunk; try @@ -832,9 +860,9 @@ std::map const& Assembly::optimiseInternal( else copy(orig, iter, back_inserter(optimisedItems)); } - if (optimisedItems.size() < m_items.size()) + if (optimisedItems.size() < items.size()) { - m_items = std::move(optimisedItems); + items = std::move(optimisedItems); count++; } } @@ -888,14 +916,15 @@ LinkerObject const& Assembly::assemble() const bool setsImmutables = false; bool pushesImmutables = false; - for (auto const& i: m_items) - if (i.type() == AssignImmutable) - { - i.setImmutableOccurrences(immutableReferencesBySub[i.data()].second.size()); - setsImmutables = true; - } - else if (i.type() == PushImmutable) - pushesImmutables = true; + for (auto const& codeSection: m_codeSections) + for (auto const& i: codeSection.items) + if (i.type() == AssignImmutable) + { + i.setImmutableOccurrences(immutableReferencesBySub[i.data()].second.size()); + setsImmutables = true; + } + else if (i.type() == PushImmutable) + pushesImmutables = true; if (setsImmutables || pushesImmutables) assertThrow( setsImmutables != pushesImmutables, @@ -903,166 +932,272 @@ LinkerObject const& Assembly::assemble() const "Cannot push and assign immutables in the same assembly subroutine." ); + assertThrow(!m_codeSections.empty(), AssemblyException, "Expected at least one code section."); + assertThrow(eof || m_codeSections.size() == 1, AssemblyException, "Expected exactly one code section in non-EOF code."); + assertThrow( + m_codeSections.front().inputs == 0 && m_codeSections.front().outputs == 0x80, AssemblyException, + "Expected the first code section to have zero inputs and be non-returning." + ); + + // TODO: assert zero inputs/outputs on code section zero + // TODO: assert one code section being present and *only* one being present unless EOF + + unsigned bytesRequiredForSubs = 0; + // TODO: consider fully producing all sub and data refs in this pass already. + for (auto&& codeSection: m_codeSections) + for (AssemblyItem const& i: codeSection.items) + if (i.type() == PushSub) + bytesRequiredForSubs += static_cast(subAssemblyById(static_cast(i.data()))->assemble().bytecode.size()); + unsigned bytesRequiredForDataUpperBound = static_cast(m_auxiliaryData.size()); + + // Some of these may be unreferenced and not actually end up in data. + for (auto const& dataItem: m_data) + bytesRequiredForDataUpperBound += static_cast(dataItem.second.size()); + unsigned bytesRequiredForDataAndSubsUpperBound = bytesRequiredForDataUpperBound + bytesRequiredForSubs; + + static auto setBigEndian = [](bytes& _dest, size_t _offset, size_t _size, auto _value) { + assertThrow(numberEncodingSize(_value) <= _size, AssemblyException, ""); + toBigEndian(_value, bytesRef(_dest.data() + _offset, _size)); + }; + static auto appendBigEndian = [](bytes& _dest, size_t _size, auto _value) { + _dest.resize(_dest.size() + _size); + setBigEndian(_dest, _dest.size() - _size, _size, _value); + }; + static auto appendBigEndianUint16 = [](bytes& _dest, auto _value) { + static_assert(!std::numeric_limits::is_signed, "only unsigned types or bigint supported"); + assertThrow(_value <= 0xFFFF, AssemblyException, ""); + appendBigEndian(_dest, 2, static_cast(_value)); + }; + std::vector codeSectionSizeOffsets; + auto setCodeSectionSize = [&](size_t _section, size_t _size) { + if (eof) + toBigEndian(_size, bytesRef(ret.bytecode.data() + codeSectionSizeOffsets.at(_section), 2)); + }; + std::optional dataSectionSizeOffset; + auto setDataSectionSize = [&](size_t _size) { + if (eof) + { + assertThrow(dataSectionSizeOffset.has_value(), AssemblyException, ""); + assertThrow(_size <= 0xFFFF, AssemblyException, "Invalid data section size."); + toBigEndian(_size, bytesRef(ret.bytecode.data() + *dataSectionSizeOffset, 2)); + } + }; + + size_t startOfContainerSectionHeader = 0; + + // Insert EOF1 header. + if (eof) + { + ret.bytecode.push_back(0xef); + ret.bytecode.push_back(0x00); + ret.bytecode.push_back(0x01); // version 1 + + ret.bytecode.push_back(0x01); // kind=type + appendBigEndianUint16(ret.bytecode, m_codeSections.size() * 4u); // length of type section + + ret.bytecode.push_back(0x02); // kind=code + appendBigEndianUint16(ret.bytecode, m_codeSections.size()); // placeholder for number of code sections + + for (auto const& codeSection: m_codeSections) + { + (void) codeSection; + codeSectionSizeOffsets.emplace_back(ret.bytecode.size()); + appendBigEndianUint16(ret.bytecode, 0u); // placeholder for length of code + } + + startOfContainerSectionHeader = ret.bytecode.size(); + + ret.bytecode.push_back(0x04); // kind=data + dataSectionSizeOffset = ret.bytecode.size(); + appendBigEndianUint16(ret.bytecode, 0u); // length of data + + ret.bytecode.push_back(0x00); // terminator + + for (auto const& codeSection: m_codeSections) + { + ret.bytecode.push_back(codeSection.inputs); + ret.bytecode.push_back(codeSection.outputs); + appendBigEndianUint16(ret.bytecode, 0xFFFFu); // TODO: Add stack heigh calculation + } + } + + unsigned headerSize = static_cast(ret.bytecode.size()); unsigned bytesRequiredForCode = codeSize(static_cast(subTagSize)); m_tagPositionsInBytecode = std::vector(m_usedTags, std::numeric_limits::max()); std::map> tagRef; std::multimap dataRef; std::multimap subRef; std::vector sizeRef; ///< Pointers to code locations where the size of the program is inserted - unsigned bytesPerTag = numberEncodingSize(bytesRequiredForCode); + unsigned bytesPerTag = numberEncodingSize(headerSize + bytesRequiredForCode + bytesRequiredForDataUpperBound); // Adjust bytesPerTag for references to sub assemblies. - for (AssemblyItem const& i: m_items) - if (i.type() == PushTag) - { - auto [subId, tagId] = i.splitForeignPushTag(); - if (subId == std::numeric_limits::max()) - continue; - assertThrow(subId < m_subs.size(), AssemblyException, "Invalid sub id"); - auto subTagPosition = m_subs[subId]->m_tagPositionsInBytecode.at(tagId); - assertThrow(subTagPosition != std::numeric_limits::max(), AssemblyException, "Reference to tag without position."); - bytesPerTag = std::max(bytesPerTag, numberEncodingSize(subTagPosition)); - } + for (auto&& codeSection: m_codeSections) + for (AssemblyItem const& i: codeSection.items) + if (i.type() == PushTag) + { + auto [subId, tagId] = i.splitForeignPushTag(); + if (subId == std::numeric_limits::max()) + continue; + assertThrow(subId < m_subs.size(), AssemblyException, "Invalid sub id"); + auto subTagPosition = m_subs[subId]->m_tagPositionsInBytecode.at(tagId); + assertThrow(subTagPosition != std::numeric_limits::max(), AssemblyException, "Reference to tag without position."); + bytesPerTag = std::max(bytesPerTag, numberEncodingSize(subTagPosition)); + } uint8_t tagPush = static_cast(pushInstruction(bytesPerTag)); - unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1 + static_cast(m_auxiliaryData.size()); - for (auto const& sub: m_subs) - bytesRequiredIncludingData += static_cast(sub->assemble().bytecode.size()); + if (eof) + { + bytesPerTag = 2; + tagPush = static_cast(Instruction::INVALID); + } + else + ++bytesRequiredForCode; ///< Additional INVALID marker. - unsigned bytesPerDataRef = numberEncodingSize(bytesRequiredIncludingData); + unsigned bytesRequiredIncludingDataAndSubsUpperBound = headerSize + bytesRequiredForCode + bytesRequiredForDataAndSubsUpperBound; + unsigned bytesPerDataRef = !eof ? numberEncodingSize(bytesRequiredIncludingDataAndSubsUpperBound) : 1; uint8_t dataRefPush = static_cast(pushInstruction(bytesPerDataRef)); - ret.bytecode.reserve(bytesRequiredIncludingData); + ret.bytecode.reserve(bytesRequiredIncludingDataAndSubsUpperBound); - for (AssemblyItem const& i: m_items) + for (auto&& [codeSectionIndex, codeSection]: m_codeSections | ranges::views::enumerate) { - // store position of the invalid jump destination - if (i.type() != Tag && m_tagPositionsInBytecode[0] == std::numeric_limits::max()) - m_tagPositionsInBytecode[0] = ret.bytecode.size(); + auto const sectionStart = ret.bytecode.size(); - switch (i.type()) - { - case Operation: - ret.bytecode.push_back(static_cast(i.instruction())); - break; - case Push: - { - unsigned b = numberEncodingSize(i.data()); - if (b == 0 && !m_evmVersion.hasPush0()) - { - b = 1; - } - ret.bytecode.push_back(static_cast(pushInstruction(b))); - if (b > 0) - { - ret.bytecode.resize(ret.bytecode.size() + b); - bytesRef byr(&ret.bytecode.back() + 1 - b, b); - toBigEndian(i.data(), byr); - } - break; - } - case PushTag: - { - assertThrow(!eof, AssemblyException, "PushTag in EOF code"); - ret.bytecode.push_back(tagPush); - tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); - ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); - break; - } - case PushData: - ret.bytecode.push_back(dataRefPush); - dataRef.insert(std::make_pair(h256(i.data()), ret.bytecode.size())); - ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); - break; - case PushSub: - assertThrow(!eof, AssemblyException, "PushSub in EOF code"); - assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, ""); - ret.bytecode.push_back(dataRefPush); - subRef.insert(std::make_pair(static_cast(i.data()), ret.bytecode.size())); - ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); - break; - case PushSubSize: - { - assertThrow(!eof, AssemblyException, "PushSubSize in EOF code"); - assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, ""); - auto s = subAssemblyById(static_cast(i.data()))->assemble().bytecode.size(); - i.setPushedValue(u256(s)); - unsigned b = std::max(1, numberEncodingSize(s)); - ret.bytecode.push_back(static_cast(pushInstruction(b))); - ret.bytecode.resize(ret.bytecode.size() + b); - bytesRef byr(&ret.bytecode.back() + 1 - b, b); - toBigEndian(s, byr); - break; - } - case PushProgramSize: + for (AssemblyItem const& i: codeSection.items) { - assertThrow(!eof, AssemblyException, "PushProgramSize in EOF code"); - ret.bytecode.push_back(dataRefPush); - sizeRef.push_back(static_cast(ret.bytecode.size())); - ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); - break; - } - case PushLibraryAddress: - ret.bytecode.push_back(static_cast(Instruction::PUSH20)); - ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data()); - ret.bytecode.resize(ret.bytecode.size() + 20); - break; - case PushImmutable: - assertThrow(!eof, AssemblyException, "PushImmutable in EOF code"); - ret.bytecode.push_back(static_cast(Instruction::PUSH32)); - // Maps keccak back to the "identifier" std::string of that immutable. - ret.immutableReferences[i.data()].first = m_immutables.at(i.data()); - // Record the bytecode offset of the PUSH32 argument. - ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size()); - // Advance bytecode by 32 bytes (default initialized). - ret.bytecode.resize(ret.bytecode.size() + 32); - break; - case VerbatimBytecode: - ret.bytecode += i.verbatimData(); - break; - case AssignImmutable: - { - assertThrow(!eof, AssemblyException, "AssignImmutable in EOF code"); - // Expect 2 elements on stack (source, dest_base) - auto const& offsets = immutableReferencesBySub[i.data()].second; - for (size_t i = 0; i < offsets.size(); ++i) + // store position of the invalid jump destination + if (i.type() != Tag && m_tagPositionsInBytecode[0] == std::numeric_limits::max()) + m_tagPositionsInBytecode[0] = ret.bytecode.size(); + + switch (i.type()) { - if (i != offsets.size() - 1) + case Operation: + ret.bytecode.push_back(static_cast(i.instruction())); + break; + case Push: { - ret.bytecode.push_back(uint8_t(Instruction::DUP2)); - ret.bytecode.push_back(uint8_t(Instruction::DUP2)); + unsigned b = numberEncodingSize(i.data()); + if (b == 0 && !m_evmVersion.hasPush0()) + { + b = 1; + } + ret.bytecode.push_back(static_cast(pushInstruction(b))); + if (b > 0) + { + ret.bytecode.resize(ret.bytecode.size() + b); + bytesRef byr(&ret.bytecode.back() + 1 - b, b); + toBigEndian(i.data(), byr); + } + break; } - // TODO: should we make use of the constant optimizer methods for pushing the offsets? - bytes offsetBytes = toCompactBigEndian(u256(offsets[i])); - ret.bytecode.push_back(static_cast(pushInstruction(static_cast(offsetBytes.size())))); - ret.bytecode += offsetBytes; - ret.bytecode.push_back(uint8_t(Instruction::ADD)); - ret.bytecode.push_back(uint8_t(Instruction::MSTORE)); - } - if (offsets.empty()) - { - ret.bytecode.push_back(uint8_t(Instruction::POP)); - ret.bytecode.push_back(uint8_t(Instruction::POP)); + case PushTag: + { + assertThrow(!eof, AssemblyException, "PushTag in EOF code"); + ret.bytecode.push_back(tagPush); + tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); + ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); + break; + } + case PushData: + // assertThrow(!eof, AssemblyException, "Push data in EOF code"); + ret.bytecode.push_back(dataRefPush); + dataRef.insert(std::make_pair(h256(i.data()), ret.bytecode.size())); + ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); + break; + case PushSub: + assertThrow(!eof, AssemblyException, "PushSub in EOF code"); + assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, ""); + ret.bytecode.push_back(dataRefPush); + subRef.insert(std::make_pair(static_cast(i.data()), ret.bytecode.size())); + ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); + break; + case PushSubSize: + { + assertThrow(!eof, AssemblyException, "PushSubSize in EOF code"); + assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, ""); + auto s = subAssemblyById(static_cast(i.data()))->assemble().bytecode.size(); + i.setPushedValue(u256(s)); + unsigned b = std::max(1, numberEncodingSize(s)); + ret.bytecode.push_back(static_cast(pushInstruction(b))); + ret.bytecode.resize(ret.bytecode.size() + b); + bytesRef byr(&ret.bytecode.back() + 1 - b, b); + toBigEndian(s, byr); + break; + } + case PushProgramSize: + { + assertThrow(!eof, AssemblyException, "Push program size in EOF code"); + ret.bytecode.push_back(dataRefPush); + sizeRef.push_back(static_cast(ret.bytecode.size())); + ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); + break; + } + case PushLibraryAddress: + ret.bytecode.push_back(static_cast(Instruction::PUSH20)); + ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data()); + ret.bytecode.resize(ret.bytecode.size() + 20); + break; + case PushImmutable: + assertThrow(!eof, AssemblyException, "Push immutable in EOF code"); + ret.bytecode.push_back(static_cast(Instruction::PUSH32)); + // Maps keccak back to the "identifier" std::string of that immutable. + ret.immutableReferences[i.data()].first = m_immutables.at(i.data()); + // Record the bytecode offset of the PUSH32 argument. + ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size()); + // Advance bytecode by 32 bytes (default initialized). + ret.bytecode.resize(ret.bytecode.size() + 32); + break; + case VerbatimBytecode: + ret.bytecode += i.verbatimData(); + break; + case AssignImmutable: + { + assertThrow(!eof, AssemblyException, "Assign immutable in EOF code"); + // Expect 2 elements on stack (source, dest_base) + auto const& offsets = immutableReferencesBySub[i.data()].second; + for (auto [j, offset]: offsets | ranges::views::enumerate) + { + if (j != offsets.size() - 1) + { + ret.bytecode.push_back(uint8_t(Instruction::DUP2)); + ret.bytecode.push_back(uint8_t(Instruction::DUP2)); + } + // TODO: should we make use of the constant optimizer methods for pushing the offsets? + bytes offsetBytes = toCompactBigEndian(u256(offset)); + ret.bytecode.push_back(static_cast(pushInstruction(static_cast(offsetBytes.size())))); + ret.bytecode += offsetBytes; + ret.bytecode.push_back(uint8_t(Instruction::ADD)); + ret.bytecode.push_back(uint8_t(Instruction::MSTORE)); + } + if (offsets.empty()) + { + ret.bytecode.push_back(uint8_t(Instruction::POP)); + ret.bytecode.push_back(uint8_t(Instruction::POP)); + } + immutableReferencesBySub.erase(i.data()); + break; + } + case PushDeployTimeAddress: + ret.bytecode.push_back(static_cast(Instruction::PUSH20)); + ret.bytecode.resize(ret.bytecode.size() + 20); + break; + case Tag: + { + assertThrow(i.data() != 0, AssemblyException, "Invalid tag position."); + assertThrow(i.splitForeignPushTag().first == std::numeric_limits::max(), AssemblyException, "Foreign tag."); + size_t tagId = static_cast(i.data()); + assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); + assertThrow(m_tagPositionsInBytecode[tagId] == std::numeric_limits::max(), AssemblyException, "Duplicate tag position."); + m_tagPositionsInBytecode[tagId] = ret.bytecode.size(); + if (!eof) + ret.bytecode.push_back(static_cast(Instruction::JUMPDEST)); + break; + } + default: + assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling."); } - immutableReferencesBySub.erase(i.data()); - break; - } - case PushDeployTimeAddress: - ret.bytecode.push_back(static_cast(Instruction::PUSH20)); - ret.bytecode.resize(ret.bytecode.size() + 20); - break; - case Tag: - { - assertThrow(i.data() != 0, AssemblyException, "Invalid tag position."); - assertThrow(i.splitForeignPushTag().first == std::numeric_limits::max(), AssemblyException, "Foreign tag."); - size_t tagId = static_cast(i.data()); - assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); - assertThrow(m_tagPositionsInBytecode[tagId] == std::numeric_limits::max(), AssemblyException, "Duplicate tag position."); - m_tagPositionsInBytecode[tagId] = ret.bytecode.size(); - ret.bytecode.push_back(static_cast(Instruction::JUMPDEST)); - break; - } - default: - assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling."); } + + auto sectionEnd = ret.bytecode.size(); + setCodeSectionSize(codeSectionIndex, sectionEnd - sectionStart); } if (!immutableReferencesBySub.empty()) @@ -1077,7 +1212,8 @@ LinkerObject const& Assembly::assemble() const // Append an INVALID here to help tests find miscompilation. ret.bytecode.push_back(static_cast(Instruction::INVALID)); - std::map subAssemblyOffsets; + std::map> subAssemblyOffsets; + std::map containerSizes; for (auto const& [subIdPath, bytecodeOffset]: subRef) { LinkerObject subObject = subAssemblyById(subIdPath)->assemble(); @@ -1085,16 +1221,27 @@ LinkerObject const& Assembly::assemble() const // In order for de-duplication to kick in, not only must the bytecode be identical, but // link and immutables references as well. - if (size_t* subAssemblyOffset = util::valueOrNullptr(subAssemblyOffsets, subObject)) - toBigEndian(*subAssemblyOffset, r); + const auto it = subAssemblyOffsets.find(subObject); + if (it != subAssemblyOffsets.end()) + { + if (!eof) + toBigEndian(it->second.first, r); + else + r[0] = static_cast(it->second.second); + } else { - toBigEndian(ret.bytecode.size(), r); - subAssemblyOffsets[subObject] = ret.bytecode.size(); + if (!eof) + toBigEndian(ret.bytecode.size(), r); + else + r[0] = static_cast(subIdPath); + + subAssemblyOffsets[subObject] = {ret.bytecode.size(), subIdPath}; ret.bytecode += subObject.bytecode; + containerSizes[subIdPath] = subObject.bytecode.size(); } for (auto const& ref: subObject.linkReferences) - ret.linkReferences[ref.first + subAssemblyOffsets[subObject]] = ref.second; + ret.linkReferences[ref.first + subAssemblyOffsets[subObject].first] = ref.second; } for (auto const& i: tagRef) { @@ -1117,12 +1264,13 @@ LinkerObject const& Assembly::assemble() const { size_t position = m_tagPositionsInBytecode.at(tagInfo.id); std::optional tagIndex; - for (auto&& [index, item]: m_items | ranges::views::enumerate) - if (item.type() == Tag && static_cast(item.data()) == tagInfo.id) - { - tagIndex = index; - break; - } + for (auto& codeSection: m_codeSections) + for (auto&& [index, item]: codeSection.items | ranges::views::enumerate) + if (item.type() == Tag && static_cast(item.data()) == tagInfo.id) + { + tagIndex = index; + break; + } ret.functionDebugData[name] = { position == std::numeric_limits::max() ? std::nullopt : std::optional{position}, tagIndex, @@ -1132,26 +1280,65 @@ LinkerObject const& Assembly::assemble() const }; } + if (eof) + { + // Fill the num_container_sections + const auto numContainers = subAssemblyOffsets.size(); + if (numContainers > 0) + { + bytes containerSectionHeader = {}; + containerSectionHeader.push_back(0x03); + appendBigEndianUint16(containerSectionHeader, numContainers); + + assertThrow(numContainers == containerSizes.size(), AssemblyException, "Invalid container size"); + + for (auto s: containerSizes) + appendBigEndianUint16(containerSectionHeader, s.second); + + ret.bytecode.insert(ret.bytecode.begin() + static_cast(startOfContainerSectionHeader), + containerSectionHeader.begin(), containerSectionHeader.end()); + + assertThrow(dataSectionSizeOffset.has_value(), AssemblyException, "Invalid data section size offset"); + dataSectionSizeOffset.value() += containerSectionHeader.size(); + + // We inserted some bytecode before first code section so all link references have to be updated too. + auto oldLinkRefs = ret.linkReferences; + std::map newLinkRefs; + + for (auto const& ref: oldLinkRefs) + newLinkRefs[ref.first + containerSectionHeader.size()] = ref.second; + + ret.linkReferences = newLinkRefs; + } + else + assertThrow(0 == containerSizes.size(), AssemblyException, "Invalid container size"); + } + + auto const dataStart = ret.bytecode.size(); + for (auto const& dataItem: m_data) { auto references = dataRef.equal_range(dataItem.first); if (references.first == references.second) continue; for (auto ref = references.first; ref != references.second; ++ref) - { - bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); - toBigEndian(ret.bytecode.size(), r); - } + toBigEndian(ret.bytecode.size(), bytesRef(ret.bytecode.data() + ref->second, bytesPerDataRef)); ret.bytecode += dataItem.second; } ret.bytecode += m_auxiliaryData; for (unsigned pos: sizeRef) - { - bytesRef r(ret.bytecode.data() + pos, bytesPerDataRef); - toBigEndian(ret.bytecode.size(), r); - } + setBigEndian(ret.bytecode, pos, bytesPerDataRef, ret.bytecode.size()); + + auto dataLength = ret.bytecode.size() - dataStart; + assertThrow( + bytesRequiredForDataAndSubsUpperBound >= dataLength, + AssemblyException, + "More data than expected. " + std::to_string(dataLength) + " > " + std::to_string(bytesRequiredForDataUpperBound) + ); + setDataSectionSize(dataLength); + return ret; } diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 2b0ef93df220..a31e84a364d2 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -53,7 +53,10 @@ class Assembly m_creation(_creation), m_eofVersion(_eofVersion), m_name(std::move(_name)) - {} + { + // Code section number 0 has to be non-returning. + m_codeSections.emplace_back(CodeSection{0, 0x80, {}}); + } std::optional eofVersion() const { return m_eofVersion; } AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } @@ -103,12 +106,6 @@ class Assembly /// Appends @a _data literally to the very end of the bytecode. void appendToAuxiliaryData(bytes const& _data) { m_auxiliaryData += _data; } - /// Returns the assembly items. - AssemblyItems const& items() const { return m_items; } - - /// Returns the mutable assembly items. Use with care! - AssemblyItems& items() { return m_items; } - int deposit() const { return m_deposit; } void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } void setDeposit(int _deposit) { m_deposit = _deposit; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } @@ -181,12 +178,30 @@ class Assembly bool isCreation() const { return m_creation; } + struct CodeSection + { + uint8_t inputs = 0; + uint8_t outputs = 0; + AssemblyItems items{}; + }; + + std::vector& codeSections() + { + return m_codeSections; + } + + std::vector const& codeSections() const + { + return m_codeSections; + } + protected: /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. Also takes an argument containing the tags of this assembly /// that are referenced in a super-assembly. std::map const& optimiseInternal(OptimiserSettings const& _settings, std::set _tagsReferencedFromOutside); + /// For EOF and legacy it calculates approximate size of "pure" code without data. unsigned codeSize(unsigned subTagSize) const; /// Add all assembly items from given JSON array. This function imports the items by iterating through @@ -223,11 +238,12 @@ class Assembly }; std::map m_namedTags; - AssemblyItems m_items; std::map m_data; /// Data that is appended to the very end of the contract. bytes m_auxiliaryData; std::vector> m_subs; + std::vector m_codeSections; + uint16_t m_currentCodeSection = 0; std::map m_strings; std::map m_libraries; ///< Identifiers of libraries to be linked. std::map m_immutables; ///< Identifiers of immutables. diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index 360fb781f4d7..6fdb42bd76d0 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -35,46 +35,49 @@ unsigned ConstantOptimisationMethod::optimiseConstants( ) { // TODO: design the optimiser in a way this is not needed - AssemblyItems& _items = _assembly.items(); - unsigned optimisations = 0; - std::map pushes; - for (AssemblyItem const& item: _items) - if (item.type() == Push) - pushes[item]++; - std::map pendingReplacements; - for (auto it: pushes) + for (auto& codeSection: _assembly.codeSections()) { - AssemblyItem const& item = it.first; - if (item.data() < 0x100) - continue; - Params params; - params.multiplicity = it.second; - params.isCreation = _isCreation; - params.runs = _runs; - params.evmVersion = _evmVersion; - LiteralMethod lit(params, item.data()); - bigint literalGas = lit.gasNeeded(); - CodeCopyMethod copy(params, item.data()); - bigint copyGas = copy.gasNeeded(); - ComputeMethod compute(params, item.data()); - bigint computeGas = compute.gasNeeded(); - AssemblyItems replacement; - if (copyGas < literalGas && copyGas < computeGas) - { - replacement = copy.execute(_assembly); - optimisations++; - } - else if (computeGas < literalGas && computeGas <= copyGas) + AssemblyItems& _items = codeSection.items; + + std::map pushes; + for (AssemblyItem const& item: _items) + if (item.type() == Push) + pushes[item]++; + std::map pendingReplacements; + for (auto it: pushes) { - replacement = compute.execute(_assembly); - optimisations++; + AssemblyItem const& item = it.first; + if (item.data() < 0x100) + continue; + Params params; + params.multiplicity = it.second; + params.isCreation = _isCreation; + params.runs = _runs; + params.evmVersion = _evmVersion; + LiteralMethod lit(params, item.data()); + bigint literalGas = lit.gasNeeded(); + CodeCopyMethod copy(params, item.data()); + bigint copyGas = copy.gasNeeded(); + ComputeMethod compute(params, item.data()); + bigint computeGas = compute.gasNeeded(); + AssemblyItems replacement; + if (copyGas < literalGas && copyGas < computeGas) + { + replacement = copy.execute(_assembly); + optimisations++; + } + else if (computeGas < literalGas && computeGas <= copyGas) + { + replacement = compute.execute(_assembly); + optimisations++; + } + if (!replacement.empty()) + pendingReplacements[item.data()] = replacement; } - if (!replacement.empty()) - pendingReplacements[item.data()] = replacement; + if (!pendingReplacements.empty()) + replaceConstants(_items, pendingReplacements); } - if (!pendingReplacements.empty()) - replaceConstants(_items, pendingReplacements); return optimisations; } diff --git a/libevmasm/EVMAssemblyStack.cpp b/libevmasm/EVMAssemblyStack.cpp index 20605ee354a0..ea1bd8a9b8aa 100644 --- a/libevmasm/EVMAssemblyStack.cpp +++ b/libevmasm/EVMAssemblyStack.cpp @@ -56,12 +56,14 @@ void EVMAssemblyStack::assemble() solAssert(!m_evmRuntimeAssembly); m_object = m_evmAssembly->assemble(); - m_sourceMapping = AssemblyItem::computeSourceMapping(m_evmAssembly->items(), sourceIndices()); + // TODO: Check for EOF + m_sourceMapping = AssemblyItem::computeSourceMapping(m_evmAssembly->codeSections().front().items, sourceIndices()); if (m_evmAssembly->numSubs() > 0) { m_evmRuntimeAssembly = std::make_shared(m_evmAssembly->sub(0)); solAssert(m_evmRuntimeAssembly && !m_evmRuntimeAssembly->isCreation()); - m_runtimeSourceMapping = AssemblyItem::computeSourceMapping(m_evmRuntimeAssembly->items(), sourceIndices()); + // TODO: Check for EOF + m_runtimeSourceMapping = AssemblyItem::computeSourceMapping(m_evmRuntimeAssembly->codeSections().front().items, sourceIndices()); m_runtimeObject = m_evmRuntimeAssembly->assemble(); } } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index da5d1268fed8..d357444d1a53 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -807,7 +807,9 @@ evmasm::AssemblyItems const* CompilerStack::assemblyItems(std::string const& _co solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); Contract const& currentContract = contract(_contractName); - return currentContract.evmAssembly ? ¤tContract.evmAssembly->items() : nullptr; + if (currentContract.evmAssembly) + solAssert(currentContract.evmAssembly->codeSections().size() == 1, "Expected a single code section in legacy codegen."); + return currentContract.evmAssembly ? ¤tContract.evmAssembly->codeSections().front().items : nullptr; } evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(std::string const& _contractName) const @@ -815,7 +817,9 @@ evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(std::string con solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful."); Contract const& currentContract = contract(_contractName); - return currentContract.evmRuntimeAssembly ? ¤tContract.evmRuntimeAssembly->items() : nullptr; + if (currentContract.evmRuntimeAssembly) + solAssert(currentContract.evmRuntimeAssembly->codeSections().size() == 1, "Expected a single code section in legacy codegen."); + return currentContract.evmRuntimeAssembly ? ¤tContract.evmRuntimeAssembly->codeSections().front().items : nullptr; } Json CompilerStack::generatedSources(std::string const& _contractName, bool _runtime) const diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index 77fd666e92b8..b4412ed31995 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -270,11 +270,13 @@ YulStack::assembleWithDeployed(std::optional _deployName) creationObject.bytecode = std::make_shared(creationAssembly->assemble()); yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables."); creationObject.assembly = creationAssembly; + solAssert(creationAssembly->codeSections().size() == 1); creationObject.sourceMappings = std::make_unique( + // TODO: fix for EOF evmasm::AssemblyItem::computeSourceMapping( - creationAssembly->items(), + creationAssembly->codeSections().front().items, {{m_charStream->name(), 0}} - ) + ) ); if (deployedAssembly) @@ -283,9 +285,9 @@ YulStack::assembleWithDeployed(std::optional _deployName) deployedObject.assembly = deployedAssembly; deployedObject.sourceMappings = std::make_unique( evmasm::AssemblyItem::computeSourceMapping( - deployedAssembly->items(), + deployedAssembly->codeSections().front().items, {{m_charStream->name(), 0}} - ) + ) ); } } diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index 06c7fb6a97eb..fe3e1f9573dd 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -270,7 +270,7 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps) checkCompilation(assembly); - std::string const sourceMappings = AssemblyItem::computeSourceMapping(assembly.items(), indices); + std::string const sourceMappings = AssemblyItem::computeSourceMapping(assembly.codeSections().at(0).items, indices); auto const numberOfMappings = std::count(sourceMappings.begin(), sourceMappings.end(), ';'); LinkerObject const& obj = assembly.assemble(); diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 13d9d6f2bb6f..a33ecb3d11ec 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1382,7 +1382,7 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies) u256(8) }; BOOST_CHECK_EQUAL_COLLECTIONS( - main.items().begin(), main.items().end(), + main.codeSections().at(0).items.begin(),main.codeSections().at(0).items.end(), expectationMain.begin(), expectationMain.end() ); @@ -1390,7 +1390,7 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies) u256(1), t1.tag(), u256(2), Instruction::JUMP, t4.tag(), u256(7), t4.pushTag(), Instruction::JUMP }; BOOST_CHECK_EQUAL_COLLECTIONS( - sub->items().begin(), sub->items().end(), + sub->codeSections().at(0).items.begin(), sub->codeSections().at(0).items.end(), expectationSub.begin(), expectationSub.end() ); } diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index e25ac55c2762..5e6bc98e2167 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -92,7 +92,7 @@ evmasm::AssemblyItems compileContract(std::shared_ptr _sourceCode) ); compiler.compileContract(*contract, std::map>{}, bytes()); - return compiler.runtimeAssembly().items(); + return compiler.runtimeAssembly().codeSections().at(0).items; } BOOST_FAIL("No contract found in source."); return AssemblyItems(); From 84ab8855ec009218d35a2d60ca5ec8f03c811af5 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 11:06:48 +0200 Subject: [PATCH 05/15] eof: Support `DATALOADN` (EIP-7480) Add `DATALOAD` instruction --- libevmasm/Assembly.cpp | 50 +++++++++++++++++-- libevmasm/Assembly.h | 2 + libevmasm/AssemblyItem.cpp | 7 +++ libevmasm/AssemblyItem.h | 1 + libevmasm/Instruction.cpp | 4 ++ libevmasm/Instruction.h | 2 + libyul/backends/evm/AbstractAssembly.h | 3 ++ libyul/backends/evm/EVMDialect.cpp | 16 ++++++ libyul/backends/evm/EthAssemblyAdapter.cpp | 5 ++ libyul/backends/evm/EthAssemblyAdapter.h | 2 + libyul/backends/evm/NoOutputAssembly.cpp | 5 ++ libyul/backends/evm/NoOutputAssembly.h | 2 + .../EVMInstructionInterpreter.cpp | 3 ++ 13 files changed, 99 insertions(+), 3 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 0b50ed06fb21..094c9a159317 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -711,6 +711,11 @@ AssemblyItem Assembly::newImmutableAssignment(std::string const& _identifier) return AssemblyItem{AssignImmutable, h}; } +AssemblyItem Assembly::newDataLoadN(size_t offset) +{ + return AssemblyItem{DataLoadN, offset}; +} + Assembly& Assembly::optimise(OptimiserSettings const& _settings) { // TODO: implement and verify `optimiseInternal` implementation for EOF. @@ -950,6 +955,20 @@ LinkerObject const& Assembly::assemble() const bytesRequiredForSubs += static_cast(subAssemblyById(static_cast(i.data()))->assemble().bytecode.size()); unsigned bytesRequiredForDataUpperBound = static_cast(m_auxiliaryData.size()); + std::optional maxDataLoadNOffset = std::nullopt; + for (auto&& codeSection: m_codeSections) + for (AssemblyItem const& i: codeSection.items) + if (i.type() == DataLoadN) + { + assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, "Invalid dataloadn index value."); + auto const offset = static_cast(i.data()); + if (!maxDataLoadNOffset.has_value() || offset > maxDataLoadNOffset.value()) + maxDataLoadNOffset = offset; + + } + + bytesRequiredForDataUpperBound += maxDataLoadNOffset.has_value() ? (maxDataLoadNOffset.value() + 32u) : 0u; + // Some of these may be unreferenced and not actually end up in data. for (auto const& dataItem: m_data) bytesRequiredForDataUpperBound += static_cast(dataItem.second.size()); @@ -1027,6 +1046,7 @@ LinkerObject const& Assembly::assemble() const std::map> tagRef; std::multimap dataRef; std::multimap subRef; + std::map dataSectionRef; std::vector sizeRef; ///< Pointers to code locations where the size of the program is inserted unsigned bytesPerTag = numberEncodingSize(headerSize + bytesRequiredForCode + bytesRequiredForDataUpperBound); // Adjust bytesPerTag for references to sub assemblies. @@ -1175,6 +1195,14 @@ LinkerObject const& Assembly::assemble() const immutableReferencesBySub.erase(i.data()); break; } + case DataLoadN: + { + assertThrow(i.data() <= std::numeric_limits::max(), AssemblyException, "Invalid dataloadn position."); + ret.bytecode.push_back(uint8_t(Instruction::DATALOADN)); + dataSectionRef[ret.bytecode.size()] = static_cast(i.data()); + appendBigEndianUint16(ret.bytecode, i.data()); + break; + } case PushDeployTimeAddress: ret.bytecode.push_back(static_cast(Instruction::PUSH20)); ret.bytecode.resize(ret.bytecode.size() + 20); @@ -1221,7 +1249,7 @@ LinkerObject const& Assembly::assemble() const // In order for de-duplication to kick in, not only must the bytecode be identical, but // link and immutables references as well. - const auto it = subAssemblyOffsets.find(subObject); + auto const it = subAssemblyOffsets.find(subObject); if (it != subAssemblyOffsets.end()) { if (!eof) @@ -1283,7 +1311,7 @@ LinkerObject const& Assembly::assemble() const if (eof) { // Fill the num_container_sections - const auto numContainers = subAssemblyOffsets.size(); + auto const numContainers = subAssemblyOffsets.size(); if (numContainers > 0) { bytes containerSectionHeader = {}; @@ -1309,6 +1337,13 @@ LinkerObject const& Assembly::assemble() const newLinkRefs[ref.first + containerSectionHeader.size()] = ref.second; ret.linkReferences = newLinkRefs; + + // We inserted some bytecode before first code section so dataSectionRef have to be updated too. + decltype(dataSectionRef) newDataSectionRef; + for (auto const& ref: dataSectionRef) + newDataSectionRef[ref.first + containerSectionHeader.size()] = ref.second; + + dataSectionRef = newDataSectionRef; } else assertThrow(0 == containerSizes.size(), AssemblyException, "Invalid container size"); @@ -1331,7 +1366,16 @@ LinkerObject const& Assembly::assemble() const for (unsigned pos: sizeRef) setBigEndian(ret.bytecode, pos, bytesPerDataRef, ret.bytecode.size()); - auto dataLength = ret.bytecode.size() - dataStart; + auto appendedDataAndAuxDataSize = ret.bytecode.size() - dataStart; + + // If some data was already added to data section (EOF only) we need to remap data section refs accordigly + if (eof && appendedDataAndAuxDataSize > 0) + { + for (auto [pos, val] : dataSectionRef) + setBigEndian(ret.bytecode, pos, 2, val + appendedDataAndAuxDataSize); + } + + auto dataLength = appendedDataAndAuxDataSize + (maxDataLoadNOffset.has_value() ? (maxDataLoadNOffset.value() + 32u) : 0u); assertThrow( bytesRequiredForDataAndSubsUpperBound >= dataLength, AssemblyException, diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index a31e84a364d2..f213967bd67d 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -73,6 +73,7 @@ class Assembly AssemblyItem newPushLibraryAddress(std::string const& _identifier); AssemblyItem newPushImmutable(std::string const& _identifier); AssemblyItem newImmutableAssignment(std::string const& _identifier); + AssemblyItem newDataLoadN(size_t offset); AssemblyItem const& append(AssemblyItem _i); AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } @@ -85,6 +86,7 @@ class Assembly void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); } void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); } void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); } + void appendDataLoadN(size_t offset) { append(newDataLoadN(offset));} void appendVerbatim(bytes _data, size_t _arguments, size_t _returnVariables) { diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 837dad8afe1b..ba9503858520 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -161,6 +161,8 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ } case VerbatimBytecode: return std::get<2>(*m_verbatimBytecode).size(); + case DataLoadN: + return 2; default: break; } @@ -203,6 +205,8 @@ size_t AssemblyItem::returnValues() const return 0; case VerbatimBytecode: return std::get<1>(*m_verbatimBytecode); + case DataLoadN: + return 1; default: break; } @@ -327,6 +331,9 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case VerbatimBytecode: text = std::string("verbatimbytecode_") + util::toHex(std::get<2>(*m_verbatimBytecode)); break; + case DataLoadN: + text = "dataloadn(" + std::to_string(static_cast(data())) + ")"; + break; default: assertThrow(false, InvalidOpcode, ""); } diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index f06c72bac2ff..d9ef0eb156d2 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -51,6 +51,7 @@ enum AssemblyItemType PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer. PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor. AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code. + DataLoadN, /// Loads 32 bytes from EOF data section. TODO: Why cannot it be done with builtin function? VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification. }; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 943f34ee4c1f..2cb282c3b862 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -168,6 +168,8 @@ std::map const solidity::evmasm::c_instructions = { "LOG2", Instruction::LOG2 }, { "LOG3", Instruction::LOG3 }, { "LOG4", Instruction::LOG4 }, + { "DATALOAD", Instruction::DATALOAD }, + { "DATALOADN", Instruction::DATALOADN }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "CALLCODE", Instruction::CALLCODE }, @@ -252,6 +254,8 @@ static std::map const c_instructionInfo = {Instruction::MSIZE, {"MSIZE", 0, 0, 1, false, Tier::Base}}, {Instruction::GAS, {"GAS", 0, 0, 1, false, Tier::Base}}, {Instruction::JUMPDEST, {"JUMPDEST", 0, 0, 0, true, Tier::Special}}, + {Instruction::DATALOAD, {"DATALOAD", 0, 1, 1, true, Tier::Low}}, + {Instruction::DATALOADN, {"DATALOADN", 2, 0, 1, true, Tier::Low}}, {Instruction::PUSH0, {"PUSH0", 0, 0, 1, false, Tier::Base}}, {Instruction::PUSH1, {"PUSH1", 1, 0, 1, false, Tier::VeryLow}}, {Instruction::PUSH2, {"PUSH2", 2, 0, 1, false, Tier::VeryLow}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index a3629eec2db8..4b6438a11596 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -182,6 +182,8 @@ enum class Instruction: uint8_t LOG3, ///< Makes a log entry; 3 topics. LOG4, ///< Makes a log entry; 4 topics. + DATALOAD = 0xd0, ///< load data from EOF data section + DATALOADN = 0xd1, ///< load data from EOF data section CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 30b42045f44f..27957d3dad00 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -112,6 +112,9 @@ class AbstractAssembly /// Appends an assignment to an immutable variable. virtual void appendImmutableAssignment(std::string const& _identifier) = 0; + /// Appends 32 bytes data load from EOF data section in dataOffset pos + virtual void appendDataLoadN(size_t dataOffset) = 0; + /// Appends data to the very end of the bytecode. Repeated calls concatenate. virtual void appendToAuxiliaryData(bytes const& _data) = 0; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 50ede8c760f0..1163fa01783a 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -204,6 +204,7 @@ std::map createBuiltins(langutil::EVMVersion _ev opcode != evmasm::Instruction::JUMP && opcode != evmasm::Instruction::JUMPI && opcode != evmasm::Instruction::JUMPDEST && + opcode != evmasm::Instruction::DATALOADN && _evmVersion.hasOpcode(opcode) && !prevRandaoException(name) ) @@ -347,6 +348,21 @@ std::map createBuiltins(langutil::EVMVersion _ev _assembly.appendImmutable(formatLiteral(std::get(_call.arguments.front()))); } )); + builtins.emplace(createFunction( + "dataloadn", + 1, + 1, + SideEffects{}, + {LiteralKind::String}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& + ) { + yulAssert(_call.arguments.size() == 1, ""); + _assembly.appendDataLoadN(std::stoul(formatLiteral(std::get(_call.arguments.front())))); + } + )); } return builtins; } diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index 0bb680516f96..ae27457a0b26 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -175,6 +175,11 @@ void EthAssemblyAdapter::appendImmutableAssignment(std::string const& _identifie m_assembly.appendImmutableAssignment(_identifier); } +void EthAssemblyAdapter::appendDataLoadN(size_t dataOffset) +{ + m_assembly.appendDataLoadN(dataOffset); +} + void EthAssemblyAdapter::markAsInvalid() { m_assembly.markAsInvalid(); diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index 1349d120606c..91707df67156 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -65,6 +65,8 @@ class EthAssemblyAdapter: public AbstractAssembly void appendImmutable(std::string const& _identifier) override; void appendImmutableAssignment(std::string const& _identifier) override; + void appendDataLoadN(size_t dataOffset) override; + void markAsInvalid() override; langutil::EVMVersion evmVersion() const override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index eedadb3ef680..d8d7479a980b 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -129,6 +129,11 @@ void NoOutputAssembly::appendImmutableAssignment(std::string const&) yulAssert(false, "setimmutable not implemented."); } +void NoOutputAssembly::appendDataLoadN(size_t) +{ + yulAssert(false, "dataloadn not implemented."); +} + NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): EVMDialect(_copyFrom.evmVersion(), _copyFrom.providesObjectAccess()) { diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index ce720027e7e5..271cd297ad1a 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -75,6 +75,8 @@ class NoOutputAssembly: public AbstractAssembly void appendImmutable(std::string const& _identifier) override; void appendImmutableAssignment(std::string const& _identifier) override; + void appendDataLoadN(size_t) override; + void markAsInvalid() override {} langutil::EVMVersion evmVersion() const override { return m_evmVersion; } diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index d2cbdb11b360..c624b020c0bf 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -487,6 +487,9 @@ u256 EVMInstructionInterpreter::eval( case Instruction::SWAP14: case Instruction::SWAP15: case Instruction::SWAP16: + // TODO: Not sure about it. + case Instruction::DATALOAD: + case Instruction::DATALOADN: { yulAssert(false, ""); return 0; From 70b9e8b949701dea7bd9b88538c04aa1501a5647 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 10:54:13 +0200 Subject: [PATCH 06/15] eof: Support RJUMP, RJUMPI (EIP-4200) Co-authored-by: Daniel Kirchner --- libevmasm/Assembly.cpp | 52 +++++++-- libevmasm/Assembly.h | 1 + libevmasm/AssemblyItem.cpp | 34 +++++- libevmasm/AssemblyItem.h | 17 ++- libevmasm/BlockDeduplicator.cpp | 4 +- libevmasm/Instruction.cpp | 6 + libevmasm/Instruction.h | 4 + libevmasm/JumpdestRemover.cpp | 2 +- libevmasm/PeepholeOptimiser.cpp | 104 +++++++++++++++++- libevmasm/SemanticInformation.cpp | 6 + libyul/backends/evm/EthAssemblyAdapter.cpp | 25 ++++- .../EVMInstructionInterpreter.cpp | 3 + 12 files changed, 234 insertions(+), 24 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 094c9a159317..ef82509042df 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -1043,7 +1043,13 @@ LinkerObject const& Assembly::assemble() const unsigned headerSize = static_cast(ret.bytecode.size()); unsigned bytesRequiredForCode = codeSize(static_cast(subTagSize)); m_tagPositionsInBytecode = std::vector(m_usedTags, std::numeric_limits::max()); - std::map> tagRef; + struct TagRef + { + size_t subId = 0; + size_t tagId = 0; + bool isRelative = 0; + }; + std::map tagRef; std::multimap dataRef; std::multimap subRef; std::map dataSectionRef; @@ -1112,7 +1118,8 @@ LinkerObject const& Assembly::assemble() const { assertThrow(!eof, AssemblyException, "PushTag in EOF code"); ret.bytecode.push_back(tagPush); - tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); + auto [subId, tagId] = i.splitForeignPushTag(); + tagRef[ret.bytecode.size()] = TagRef{subId, tagId, false}; ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); break; } @@ -1219,6 +1226,16 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.push_back(static_cast(Instruction::JUMPDEST)); break; } + case RelativeJump: + case ConditionalRelativeJump: + { + assertThrow(eof, AssemblyException, "Relative jump in non-EOF code"); + ret.bytecode.push_back(static_cast(i.type() == RelativeJump ? Instruction::RJUMP : Instruction::RJUMPI)); + auto [subId, tagId] = i.splitForeignPushTag(); + tagRef[ret.bytecode.size()] = TagRef{subId, tagId, true}; + appendBigEndianUint16(ret.bytecode, 0u); + break; + } default: assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling."); } @@ -1271,22 +1288,37 @@ LinkerObject const& Assembly::assemble() const for (auto const& ref: subObject.linkReferences) ret.linkReferences[ref.first + subAssemblyOffsets[subObject].first] = ref.second; } - for (auto const& i: tagRef) + for (auto const& [bytecodeOffset, ref]: tagRef) { - size_t subId; - size_t tagId; - std::tie(subId, tagId) = i.second; + size_t subId = ref.subId; + size_t tagId = ref.tagId; + bool relative = ref.isRelative; assertThrow(subId == std::numeric_limits::max() || subId < m_subs.size(), AssemblyException, "Invalid sub id"); std::vector const& tagPositions = subId == std::numeric_limits::max() ? - m_tagPositionsInBytecode : - m_subs[subId]->m_tagPositionsInBytecode; + m_tagPositionsInBytecode : + m_subs[subId]->m_tagPositionsInBytecode; assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); size_t pos = tagPositions[tagId]; assertThrow(pos != std::numeric_limits::max(), AssemblyException, "Reference to tag without position."); assertThrow(numberEncodingSize(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space."); - bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); - toBigEndian(pos, r); + if (relative) + { + assertThrow(m_eofVersion.has_value(), AssemblyException, "Relative jump outside EOF"); + assertThrow(subId == std::numeric_limits::max(), AssemblyException, "Relative jump to sub"); + assertThrow( + static_cast(pos) - static_cast(bytecodeOffset + 2u) < 0x7FFF && + static_cast(pos) - static_cast(bytecodeOffset + 2u) >= -0x8000, + AssemblyException, + "Relative jump too far" + ); + toBigEndian(pos - (bytecodeOffset + 2u), bytesRef(ret.bytecode.data() + bytecodeOffset, 2)); + } + else + { + assertThrow(!m_eofVersion.has_value(), AssemblyException, "Dynamic tag reference within EOF"); + toBigEndian(pos, bytesRef(ret.bytecode.data() + bytecodeOffset, bytesPerTag)); + } } for (auto const& [name, tagInfo]: m_namedTags) { diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index f213967bd67d..dd80d5445668 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -59,6 +59,7 @@ class Assembly } std::optional eofVersion() const { return m_eofVersion; } + bool supportsRelativeJumps() const { return m_eofVersion.has_value(); } AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } /// Returns a tag identified by the given name. Creates it if it does not yet exist. diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index ba9503858520..402058570560 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -62,7 +62,7 @@ AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const std::pair AssemblyItem::splitForeignPushTag() const { - assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); + assertThrow(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump, util::Exception, ""); u256 combined = u256(data()); size_t subId = static_cast((combined >> 64) - 1); size_t tag = static_cast(combined & 0xffffffffffffffffULL); @@ -109,7 +109,7 @@ std::pair AssemblyItem::nameAndData(langutil::EVMVersi void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) { - assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); + assertThrow(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump , util::Exception, ""); u256 data = _tag; if (_subId != std::numeric_limits::max()) data |= (u256(_subId) + 1) << 64; @@ -161,6 +161,10 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ } case VerbatimBytecode: return std::get<2>(*m_verbatimBytecode).size(); + case RelativeJump: + return 3; + case ConditionalRelativeJump: + return 3; case DataLoadN: return 2; default: @@ -179,6 +183,8 @@ size_t AssemblyItem::arguments() const return std::get<0>(*m_verbatimBytecode); else if (type() == AssignImmutable) return 2; + else if (type() == ConditionalRelativeJump) + return 1; else return 0; } @@ -331,6 +337,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case VerbatimBytecode: text = std::string("verbatimbytecode_") + util::toHex(std::get<2>(*m_verbatimBytecode)); break; + case RelativeJump: + text = "rjump(" + std::string("tag_") + std::to_string(static_cast(data())) + ")"; + break; + case ConditionalRelativeJump: + text = "rjumpi(" + std::string("tag_") + std::to_string(static_cast(data())) + ")"; + break; case DataLoadN: text = "dataloadn(" + std::to_string(static_cast(data())) + ")"; break; @@ -370,6 +382,24 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons _out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second; break; } + case RelativeJump: + { + size_t subId = _item.splitForeignPushTag().first; + if (subId == std::numeric_limits::max()) + _out << " RelativeJump " << _item.splitForeignPushTag().second; + else + _out << " RelativeJump " << subId << ":" << _item.splitForeignPushTag().second; + break; + } + case ConditionalRelativeJump: + { + size_t subId = _item.splitForeignPushTag().first; + if (subId == std::numeric_limits::max()) + _out << " ConditionalRelativeJump " << _item.splitForeignPushTag().second; + else + _out << " ConditionalRelativeJump " << subId << ":" << _item.splitForeignPushTag().second; + break; + } case Tag: _out << " Tag " << _item.data(); break; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index d9ef0eb156d2..db3e1f5e80e3 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -52,7 +52,9 @@ enum AssemblyItemType PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor. AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code. DataLoadN, /// Loads 32 bytes from EOF data section. TODO: Why cannot it be done with builtin function? - VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification. + VerbatimBytecode, ///< Contains data that is inserted into the bytecode code section without modification. + RelativeJump, + ConditionalRelativeJump, }; enum class Precision { Precise , Approximate }; @@ -89,13 +91,22 @@ class AssemblyItem m_debugData{langutil::DebugData::create()} {} + static AssemblyItem jumpTo(AssemblyItem _tag, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(RelativeJump, _tag.data(), _debugData); + } + static AssemblyItem conditionalJumpTo(AssemblyItem _tag, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(ConditionalRelativeJump, _tag.data(), _debugData); + } + AssemblyItem(AssemblyItem const&) = default; AssemblyItem(AssemblyItem&&) = default; AssemblyItem& operator=(AssemblyItem const&) = default; AssemblyItem& operator=(AssemblyItem&&) = default; - AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); return AssemblyItem(Tag, data()); } - AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); return AssemblyItem(PushTag, data()); } + AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump, util::Exception, ""); return AssemblyItem(Tag, data()); } + AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag || m_type == RelativeJump || m_type == ConditionalRelativeJump, util::Exception, ""); return AssemblyItem(PushTag, data()); } /// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies. /// @param _subId the identifier of the subassembly the tag is taken from. AssemblyItem toSubAssemblyTag(size_t _subId) const; diff --git a/libevmasm/BlockDeduplicator.cpp b/libevmasm/BlockDeduplicator.cpp index c978bae2bb39..3ba8cfcd3523 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -106,7 +106,7 @@ bool BlockDeduplicator::applyTagReplacement( { bool changed = false; for (AssemblyItem& item: _items) - if (item.type() == PushTag) + if (item.type() == PushTag || item.type() == RelativeJump || item.type() == ConditionalRelativeJump) { size_t subId; size_t tagId; @@ -131,7 +131,7 @@ BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++() { if (it == end) return *this; - if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem{Instruction::JUMPI}) + if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem{Instruction::JUMPI} && it->type() != ConditionalRelativeJump) it = end; else { diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 2cb282c3b862..1acea4c7a9b2 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -170,6 +170,9 @@ std::map const solidity::evmasm::c_instructions = { "LOG4", Instruction::LOG4 }, { "DATALOAD", Instruction::DATALOAD }, { "DATALOADN", Instruction::DATALOADN }, + { "RJUMP", Instruction::RJUMP }, + { "RJUMPI", Instruction::RJUMPI }, + { "RJUMPV", Instruction::RJUMPV }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "CALLCODE", Instruction::CALLCODE }, @@ -256,6 +259,9 @@ static std::map const c_instructionInfo = {Instruction::JUMPDEST, {"JUMPDEST", 0, 0, 0, true, Tier::Special}}, {Instruction::DATALOAD, {"DATALOAD", 0, 1, 1, true, Tier::Low}}, {Instruction::DATALOADN, {"DATALOADN", 2, 0, 1, true, Tier::Low}}, + {Instruction::RJUMP, {"RJUMP", 2, 0, 0, true, Tier::Low }}, + {Instruction::RJUMPI, {"RJUMPI", 2, 1, 0, true, Tier::Low }}, + {Instruction::RJUMPV, {"RJUMPV", 2, 1, 0, true, Tier::Low }}, {Instruction::PUSH0, {"PUSH0", 0, 0, 1, false, Tier::Base}}, {Instruction::PUSH1, {"PUSH1", 1, 0, 1, false, Tier::VeryLow}}, {Instruction::PUSH2, {"PUSH2", 2, 0, 1, false, Tier::VeryLow}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 4b6438a11596..2b4fb87fd294 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -184,6 +184,10 @@ enum class Instruction: uint8_t DATALOAD = 0xd0, ///< load data from EOF data section DATALOADN = 0xd1, ///< load data from EOF data section + + RJUMP = 0xe0, ///< relative jump + RJUMPI = 0xe1, ///< conidtional relative jump + RJUMPV = 0xe2, ///< relative jump via jump table CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libevmasm/JumpdestRemover.cpp b/libevmasm/JumpdestRemover.cpp index 7afa715a88df..7ded5ae90889 100644 --- a/libevmasm/JumpdestRemover.cpp +++ b/libevmasm/JumpdestRemover.cpp @@ -58,7 +58,7 @@ std::set JumpdestRemover::referencedTags(AssemblyItems const& _items, si { std::set ret; for (auto const& item: _items) - if (item.type() == PushTag) + if (item.type() == PushTag || item.type() == RelativeJump || item.type() == ConditionalRelativeJump) { auto subAndTag = item.splitForeignPushTag(); if (subAndTag.first == _subId) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 40937e73f107..7cafbaa7c57a 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -328,6 +328,29 @@ struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod } }; +struct IsZeroIsZeroRJumpI: SimplePeepholeOptimizerMethod +{ + static size_t applySimple( + AssemblyItem const& _iszero1, + AssemblyItem const& _iszero2, + AssemblyItem const& _rjumpi, + std::back_insert_iterator _out + ) + { + if ( + _iszero1 == Instruction::ISZERO && + _iszero2 == Instruction::ISZERO && + _rjumpi.type() == ConditionalRelativeJump + ) + { + *_out = _rjumpi; + return true; + } + else + return false; + } +}; + struct EqIsZeroJumpI: SimplePeepholeOptimizerMethod { static size_t applySimple( @@ -355,6 +378,30 @@ struct EqIsZeroJumpI: SimplePeepholeOptimizerMethod } }; +struct EqIsZeroRJumpI: SimplePeepholeOptimizerMethod +{ + static size_t applySimple( + AssemblyItem const& _eq, + AssemblyItem const& _iszero, + AssemblyItem const& _rjumpi, + std::back_insert_iterator _out + ) + { + if ( + _eq == Instruction::EQ && + _iszero == Instruction::ISZERO && + _rjumpi.type() == ConditionalRelativeJump + ) + { + *_out = AssemblyItem(Instruction::SUB, _eq.debugData()); + *_out = _rjumpi; + return true; + } + else + return false; + } +}; + // push_tag_1 jumpi push_tag_2 jump tag_1: -> iszero push_tag_2 jumpi tag_1: struct DoubleJump: SimplePeepholeOptimizerMethod { @@ -387,6 +434,33 @@ struct DoubleJump: SimplePeepholeOptimizerMethod } }; +// rjumpi(tag_1) rjump(tag_2) tag_1: -> iszero rjumpi(tag_2) tag_1: +struct DoubleRJump: SimplePeepholeOptimizerMethod +{ + static size_t applySimple( + AssemblyItem const& _rjumpi, + AssemblyItem const& _rjump, + AssemblyItem const& _tag1, + std::back_insert_iterator _out + ) + { + if ( + _rjumpi.type() == ConditionalRelativeJump && + _rjump.type() == RelativeJump && + _tag1.type() == Tag && + _rjumpi.data() == _tag1.data() + ) + { + *_out = AssemblyItem(Instruction::ISZERO, _rjumpi.debugData()); + *_out = AssemblyItem::conditionalJumpTo(_rjump.tag(), _rjump.debugData()); + *_out = _tag1; + return true; + } + else + return false; + } +}; + struct JumpToNext: SimplePeepholeOptimizerMethod { static size_t applySimple( @@ -413,6 +487,30 @@ struct JumpToNext: SimplePeepholeOptimizerMethod } }; +struct RJumpToNext: SimplePeepholeOptimizerMethod +{ + static size_t applySimple( + AssemblyItem const& _rjump, + AssemblyItem const& _tag, + std::back_insert_iterator _out + ) + { + if ( + (_rjump.type() == ConditionalRelativeJump || _rjump.type() == RelativeJump) && + _tag.type() == Tag && + _rjump.data() == _tag.data() + ) + { + if (_rjump.type() == ConditionalRelativeJump) + *_out = AssemblyItem(Instruction::POP, _rjump.debugData()); + *_out = _tag; + return true; + } + else + return false; + } +}; + struct TagConjunctions: SimplePeepholeOptimizerMethod { static bool applySimple( @@ -476,6 +574,7 @@ struct UnreachableCode return false; if ( it[0] != Instruction::JUMP && + it[0] != Instruction::RJUMP && it[0] != Instruction::RETURN && it[0] != Instruction::STOP && it[0] != Instruction::INVALID && @@ -619,8 +718,9 @@ bool PeepholeOptimiser::optimise() applyMethods( state, PushPop(), OpPop(), OpStop(), OpReturnRevert(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), - DupSwap(), IsZeroIsZeroJumpI(), EqIsZeroJumpI(), DoubleJump(), JumpToNext(), UnreachableCode(), DeduplicateNextTagSize3(), - DeduplicateNextTagSize2(), DeduplicateNextTagSize1(), TagConjunctions(), TruthyAnd(), Identity() + DupSwap(), IsZeroIsZeroJumpI(), IsZeroIsZeroRJumpI(), EqIsZeroJumpI(), DoubleJump(), DoubleRJump(), JumpToNext(), + RJumpToNext(), UnreachableCode(), DeduplicateNextTagSize3(), DeduplicateNextTagSize2(), DeduplicateNextTagSize1(), + TagConjunctions(), TruthyAnd(), Identity() ); if (m_optimisedItems.size() < m_items.size() || ( m_optimisedItems.size() == m_items.size() && ( diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 87d41f6c13a0..564327ac9b8b 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -280,6 +280,8 @@ bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) { + if (_item.type() == evmasm::RelativeJump || _item.type() == evmasm::ConditionalRelativeJump) + return true; if (_item.type() != evmasm::Operation) return false; switch (_item.instruction()) @@ -288,6 +290,9 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) // continue on the next instruction case Instruction::JUMP: case Instruction::JUMPI: + case Instruction::RJUMP: + case Instruction::RJUMPI: + case Instruction::RJUMPV: case Instruction::RETURN: case Instruction::SELFDESTRUCT: case Instruction::STOP: @@ -297,6 +302,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) default: return false; } + } bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item) diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index ae27457a0b26..b34835c4e208 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -106,14 +106,31 @@ void EthAssemblyAdapter::appendJump(int _stackDiffAfter, JumpType _jumpType) void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) { - appendLabelReference(_labelId); - appendJump(_stackDiffAfter, _jumpType); + if (m_assembly.supportsRelativeJumps()) + { + m_assembly.append(evmasm::AssemblyItem::jumpTo(evmasm::AssemblyItem(evmasm::PushTag, _labelId))); + yulAssert(_jumpType == JumpType::Ordinary); + m_assembly.adjustDeposit(_stackDiffAfter); + } + else + { + appendLabelReference(_labelId); + appendJump(_stackDiffAfter, _jumpType); + } } void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId, JumpType _jumpType) { - appendLabelReference(_labelId); - appendJumpInstruction(evmasm::Instruction::JUMPI, _jumpType); + if (m_assembly.supportsRelativeJumps()) + { + m_assembly.append(evmasm::AssemblyItem::conditionalJumpTo(evmasm::AssemblyItem(evmasm::PushTag, _labelId))); + yulAssert(_jumpType == JumpType::Ordinary); + } + else + { + appendLabelReference(_labelId); + appendJumpInstruction(evmasm::Instruction::JUMPI, _jumpType); + } } void EthAssemblyAdapter::appendAssemblySize() diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index c624b020c0bf..d5738c069a4c 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -490,6 +490,9 @@ u256 EVMInstructionInterpreter::eval( // TODO: Not sure about it. case Instruction::DATALOAD: case Instruction::DATALOADN: + case Instruction::RJUMP: + case Instruction::RJUMPI: + case Instruction::RJUMPV: { yulAssert(false, ""); return 0; From 64cbc1d1eabf5d01d396f862862e474297195032 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 10:46:44 +0200 Subject: [PATCH 07/15] eof: Support CALLF, JUMPF, RETF. (EIP-4750, EIP-6206) Co-authored-by: Daniel Kirchner --- libevmasm/Assembly.cpp | 20 ++++++ libevmasm/Assembly.h | 50 ++++++++++++++ libevmasm/AssemblyItem.cpp | 37 ++++++++++ libevmasm/AssemblyItem.h | 21 ++++++ libevmasm/Instruction.cpp | 6 ++ libevmasm/Instruction.h | 5 ++ libevmasm/SemanticInformation.cpp | 5 +- libsolidity/codegen/CompilerContext.cpp | 1 + libyul/YulStack.cpp | 1 + libyul/backends/evm/AbstractAssembly.h | 9 +++ libyul/backends/evm/ControlFlowGraph.h | 3 +- .../backends/evm/ControlFlowGraphBuilder.cpp | 30 +++++++-- libyul/backends/evm/ControlFlowGraphBuilder.h | 8 ++- libyul/backends/evm/EVMDialect.h | 3 + libyul/backends/evm/EVMObjectCompiler.cpp | 1 + libyul/backends/evm/EthAssemblyAdapter.cpp | 26 +++++++ libyul/backends/evm/EthAssemblyAdapter.h | 5 ++ libyul/backends/evm/NoOutputAssembly.cpp | 27 ++++++++ libyul/backends/evm/NoOutputAssembly.h | 15 ++++- .../evm/OptimizedEVMCodeTransform.cpp | 67 ++++++++++++++----- .../backends/evm/OptimizedEVMCodeTransform.h | 1 + libyul/backends/evm/StackLayoutGenerator.cpp | 10 +-- libyul/backends/evm/StackLayoutGenerator.h | 1 + libyul/optimiser/OptimiserStep.h | 1 + libyul/optimiser/StackCompressor.cpp | 3 +- libyul/optimiser/StackCompressor.h | 1 + libyul/optimiser/StackLimitEvader.cpp | 2 +- libyul/optimiser/Suite.cpp | 5 +- libyul/optimiser/Suite.h | 1 + test/libyul/ControlFlowGraphTest.cpp | 2 +- test/libyul/KnowledgeBaseTest.cpp | 2 +- test/libyul/StackLayoutGeneratorTest.cpp | 2 +- test/libyul/YulOptimizerTestCommon.cpp | 4 +- .../EVMInstructionInterpreter.cpp | 3 + test/tools/yulopti.cpp | 3 +- tools/yulPhaser/Program.cpp | 1 + 36 files changed, 347 insertions(+), 35 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index ef82509042df..c17ef94ba241 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -1226,6 +1226,26 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.push_back(static_cast(Instruction::JUMPDEST)); break; } + case CallF: + { + assertThrow(eof, AssemblyException, "Function call (CALLF) in non-EOF code"); + ret.bytecode.push_back(static_cast(Instruction::CALLF)); + appendBigEndianUint16(ret.bytecode, i.data()); + break; + } + case JumpF: + { + assertThrow(eof, AssemblyException, "Function call (JUMPF) in non-EOF code"); + ret.bytecode.push_back(static_cast(Instruction::JUMPF)); + appendBigEndianUint16(ret.bytecode, i.data()); + break; + } + case RetF: + { + assertThrow(eof, AssemblyException, "Function return (RETF) in non-EOF code"); + ret.bytecode.push_back(static_cast(Instruction::RETF)); + break; + } case RelativeJump: case ConditionalRelativeJump: { diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index dd80d5445668..64f9ef076ee7 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -59,9 +59,49 @@ class Assembly } std::optional eofVersion() const { return m_eofVersion; } + bool supportsFunctions() const { return m_eofVersion.has_value(); } bool supportsRelativeJumps() const { return m_eofVersion.has_value(); } AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } + AssemblyItem newFunctionCall(uint16_t _functionID) + { + assertThrow(_functionID < m_codeSections.size(), AssemblyException, "Call to undeclared function."); + auto const& section = m_codeSections.at(_functionID); + if (section.outputs != 0x80) + return AssemblyItem::functionCall(_functionID, section.inputs, section.outputs); + else + return AssemblyItem::jumpF(_functionID, section.inputs); + } + + AssemblyItem newFunctionReturn() + { + return AssemblyItem::functionReturn(m_codeSections.at(m_currentCodeSection).outputs); + } + + uint16_t createFunction(uint8_t _args, uint8_t _rets) + { + size_t functionID = m_codeSections.size(); + assertThrow(functionID < 1024, AssemblyException, "Too many functions."); + assertThrow(m_currentCodeSection == 0, AssemblyException, "Functions need to be declared from the main block."); + assertThrow(_rets <= 0x80, AssemblyException, "Too many function returns."); + m_codeSections.emplace_back(CodeSection{_args, _rets, {}}); + return static_cast(functionID); + } + + void beginFunction(uint16_t _functionID) + { + assertThrow(m_currentCodeSection == 0, AssemblyException, "Atempted to begin a function before ending the last one."); + assertThrow(_functionID < m_codeSections.size(), AssemblyException, "Attempt to begin an undeclared function."); + auto& section = m_codeSections.at(_functionID); + assertThrow(section.items.empty(), AssemblyException, "Function already defined."); + m_currentCodeSection = _functionID; + } + void endFunction() + { + assertThrow(m_currentCodeSection != 0, AssemblyException, "End function without begin function."); + m_currentCodeSection = 0; + } + /// Returns a tag identified by the given name. Creates it if it does not yet exist. AssemblyItem namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional _sourceID); AssemblyItem newData(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } @@ -94,6 +134,16 @@ class Assembly append(AssemblyItem(std::move(_data), _arguments, _returnVariables)); } + AssemblyItem appendFunctionCall(uint16_t _functionID) + { + return append(newFunctionCall(_functionID)); + } + + AssemblyItem appendFunctionReturn() + { + return append(newFunctionReturn()); + } + AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 402058570560..c01661f93c26 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -165,6 +165,11 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ return 3; case ConditionalRelativeJump: return 3; + case JumpF: + case CallF: + return 3; + case RetF: + return 1; case DataLoadN: return 2; default: @@ -185,6 +190,13 @@ size_t AssemblyItem::arguments() const return 2; else if (type() == ConditionalRelativeJump) return 1; + else if (type() == CallF || type() == JumpF) + { + assertThrow(m_functionSignature.has_value(), AssemblyException, ""); + return std::get<0>(*m_functionSignature); + } + else if (type() == RetF) + return static_cast(data()); else return 0; } @@ -211,6 +223,13 @@ size_t AssemblyItem::returnValues() const return 0; case VerbatimBytecode: return std::get<1>(*m_verbatimBytecode); + case CallF: + assertThrow(m_functionSignature.has_value(), AssemblyException, ""); + return std::get<1>(*m_functionSignature); + case JumpF: + return 0; + case RetF: + return 0; case DataLoadN: return 1; default: @@ -343,6 +362,15 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case ConditionalRelativeJump: text = "rjumpi(" + std::string("tag_") + std::to_string(static_cast(data())) + ")"; break; + case CallF: + text = "callf(" + std::to_string(static_cast(data())) + ")"; + break; + case JumpF: + text = "jumpf(" + std::to_string(static_cast(data())) + ")"; + break; + case RetF: + text = "retf"; + break; case DataLoadN: text = "dataloadn(" + std::to_string(static_cast(data())) + ")"; break; @@ -433,6 +461,15 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons case VerbatimBytecode: _out << " Verbatim " << util::toHex(_item.verbatimData()); break; + case CallF: + _out << " CALLF " << std::dec << _item.data(); + break; + case JumpF: + _out << " JUMPF " << std::dec << _item.data(); + break; + case RetF: + _out << " RETF"; + break; case UndefinedItem: _out << " ???"; break; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index db3e1f5e80e3..681a2ecf8bfd 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -53,6 +53,9 @@ enum AssemblyItemType AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code. DataLoadN, /// Loads 32 bytes from EOF data section. TODO: Why cannot it be done with builtin function? VerbatimBytecode, ///< Contains data that is inserted into the bytecode code section without modification. + CallF, + JumpF, + RetF, RelativeJump, ConditionalRelativeJump, }; @@ -91,6 +94,23 @@ class AssemblyItem m_debugData{langutil::DebugData::create()} {} + static AssemblyItem functionCall(uint16_t _functionID, uint8_t _args, uint8_t _rets, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + AssemblyItem result(CallF, _functionID, _debugData); + result.m_functionSignature = std::make_tuple(_args, _rets); + return result; + } + static AssemblyItem jumpF(uint16_t _functionID, uint8_t _args, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + AssemblyItem result(JumpF, _functionID, _debugData); + result.m_functionSignature = std::make_tuple(_args, 0); + return result; + } + static AssemblyItem functionReturn(uint8_t _rets, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(RetF, _rets, _debugData); + } + static AssemblyItem jumpTo(AssemblyItem _tag, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) { return AssemblyItem(RelativeJump, _tag.data(), _debugData); @@ -229,6 +249,7 @@ class AssemblyItem AssemblyItemType m_type; Instruction m_instruction; ///< Only valid if m_type == Operation std::shared_ptr m_data; ///< Only valid if m_type != Operation + std::optional> m_functionSignature; /// If m_type == VerbatimBytecode, this holds number of arguments, number of /// return variables and verbatim bytecode. std::optional> m_verbatimBytecode; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 1acea4c7a9b2..1f5d705a3dd7 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -170,6 +170,9 @@ std::map const solidity::evmasm::c_instructions = { "LOG4", Instruction::LOG4 }, { "DATALOAD", Instruction::DATALOAD }, { "DATALOADN", Instruction::DATALOADN }, + { "CALLF", Instruction::CALLF }, + { "RETF", Instruction::RETF }, + { "JUMPF", Instruction::JUMPF }, { "RJUMP", Instruction::RJUMP }, { "RJUMPI", Instruction::RJUMPI }, { "RJUMPV", Instruction::RJUMPV }, @@ -332,6 +335,9 @@ static std::map const c_instructionInfo = {Instruction::LOG2, {"LOG2", 0, 4, 0, true, Tier::Special}}, {Instruction::LOG3, {"LOG3", 0, 5, 0, true, Tier::Special}}, {Instruction::LOG4, {"LOG4", 0, 6, 0, true, Tier::Special}}, + {Instruction::RETF, {"RETF", 0, 0, 0, true, Tier::Special}}, + {Instruction::CALLF, {"CALLF", 2, 0, 0, true, Tier::Special}}, + {Instruction::JUMPF, {"JUMPF", 2, 0, 0, true, Tier::Special}}, {Instruction::CREATE, {"CREATE", 0, 3, 1, true, Tier::Special}}, {Instruction::CALL, {"CALL", 0, 7, 1, true, Tier::Special}}, {Instruction::CALLCODE, {"CALLCODE", 0, 7, 1, true, Tier::Special}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 2b4fb87fd294..2565df2abe2d 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -188,6 +188,11 @@ enum class Instruction: uint8_t RJUMP = 0xe0, ///< relative jump RJUMPI = 0xe1, ///< conidtional relative jump RJUMPV = 0xe2, ///< relative jump via jump table + + CALLF = 0xe3, ///< call function in a EOF code section + RETF = 0xe4, ///< return to caller from the code section of EOF continer + JUMPF = 0xe5, ///< jump to a code section of EOF contaner. No stack cleaning. + CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 564327ac9b8b..cb6e73f98d56 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -203,6 +203,9 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool case PushDeployTimeAddress: case AssignImmutable: case VerbatimBytecode: + case CallF: + case JumpF: + case RetF: return true; case Push: case PushTag: @@ -280,7 +283,7 @@ bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) { - if (_item.type() == evmasm::RelativeJump || _item.type() == evmasm::ConditionalRelativeJump) + if (_item.type() == evmasm::RetF || _item.type() == evmasm::RelativeJump || _item.type() == evmasm::ConditionalRelativeJump) return true; if (_item.type() != evmasm::Operation) return false; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index b1b76a97b8d6..b22b105b2549 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -542,6 +542,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _ yul::GasMeter meter(_dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); yul::OptimiserSuite::run( _dialect, + assembly().eofVersion(), &meter, _object, _optimiserSettings.optimizeStackAllocation, diff --git a/libyul/YulStack.cpp b/libyul/YulStack.cpp index b4412ed31995..de862a2c2ad4 100644 --- a/libyul/YulStack.cpp +++ b/libyul/YulStack.cpp @@ -230,6 +230,7 @@ void YulStack::optimize(Object& _object, bool _isCreation) OptimiserSuite::run( dialect, + m_eofVersion, meter.get(), _object, // Defaults are the minimum necessary to avoid running into "Stack too deep" constantly. diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 27957d3dad00..2e6f3281730a 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -56,6 +56,7 @@ class AbstractAssembly public: using LabelID = size_t; using SubID = size_t; + using FunctionID = uint16_t; enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; virtual ~AbstractAssembly() = default; @@ -100,6 +101,14 @@ class AbstractAssembly virtual void appendAssemblySize() = 0; /// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset. virtual std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = "") = 0; + + virtual FunctionID createFunction(uint8_t _args, uint8_t _rets) = 0; + virtual void beginFunction(FunctionID _functionID) = 0; + virtual void endFunction() = 0; + + virtual void appendFunctionCall(FunctionID _functionID, int _stackDiffAfter = 0) = 0; + virtual void appendFunctionReturn() = 0; + /// Appends the offset of the given sub-assembly or data. virtual void appendDataOffset(std::vector const& _subPath) = 0; /// Appends the size of the given sub-assembly or data. diff --git a/libyul/backends/evm/ControlFlowGraph.h b/libyul/backends/evm/ControlFlowGraph.h index 980787c52297..6d91d3b80c43 100644 --- a/libyul/backends/evm/ControlFlowGraph.h +++ b/libyul/backends/evm/ControlFlowGraph.h @@ -123,7 +123,7 @@ inline bool canBeFreelyGenerated(StackSlot const& _slot) /// Control flow graph consisting of ``CFG::BasicBlock``s connected by control flow. struct CFG { - explicit CFG() {} + explicit CFG(bool _useFunctions): useFunctions(_useFunctions) {} CFG(CFG const&) = delete; CFG(CFG&&) = delete; CFG& operator=(CFG const&) = delete; @@ -220,6 +220,7 @@ struct CFG bool canContinue = true; }; + bool useFunctions = false; /// The main entry point, i.e. the start of the outermost Yul block. BasicBlock* entry = nullptr; /// Subgraphs for functions. diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.cpp b/libyul/backends/evm/ControlFlowGraphBuilder.cpp index b4d1cdf15c3a..545f3aa7fe66 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.cpp +++ b/libyul/backends/evm/ControlFlowGraphBuilder.cpp @@ -51,12 +51,23 @@ namespace /// Removes edges to blocks that are not reachable. void cleanUnreachable(CFG& _cfg) { + auto const addFunctionsEntries = [&_cfg](CFG::BasicBlock* _node, auto&& _addChild) + { + for (auto const& o: _node->operations) + { + if (auto const* p = std::get_if(&o.operation); p != nullptr) + { + auto const fInfo = _cfg.functionInfo.at(&(p->function.get())); + _addChild(fInfo.entry); + } + } + }; + // Determine which blocks are reachable from the entry. util::BreadthFirstSearch reachabilityCheck{{_cfg.entry}}; - for (auto const& functionInfo: _cfg.functionInfo | ranges::views::values) - reachabilityCheck.verticesToTraverse.emplace_back(functionInfo.entry); reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) { + addFunctionsEntries(_node, _addChild); visit(util::GenericVisitor{ [&](CFG::BasicBlock::Jump const& _jump) { _addChild(_jump.target); @@ -76,6 +87,16 @@ void cleanUnreachable(CFG& _cfg) cxx20::erase_if(node->entries, [&](CFG::BasicBlock* entry) -> bool { return !reachabilityCheck.visited.count(entry); }); + + // Remove functions which are never referenced. + _cfg.functions.erase(std::remove_if(_cfg.functions.begin(), _cfg.functions.end(), [&](auto const& item) { + return !reachabilityCheck.visited.count(_cfg.functionInfo.at(item).entry); + }), _cfg.functions.end()); + + // Remove functionInfos which are never referenced. + cxx20::erase_if(_cfg.functionInfo, [&](auto const& entry) -> bool { + return !reachabilityCheck.visited.count(entry.second.entry); + }); } /// Sets the ``recursive`` member to ``true`` for all recursive function calls. @@ -208,10 +229,11 @@ void markNeedsCleanStack(CFG& _cfg) std::unique_ptr ControlFlowGraphBuilder::build( AsmAnalysisInfo const& _analysisInfo, Dialect const& _dialect, + std::optional _eofVersion, Block const& _block ) { - auto result = std::make_unique(); + auto result = std::make_unique(_eofVersion.has_value()); result->entry = &result->makeBlock(debugDataOf(_block)); ControlFlowSideEffectsCollector sideEffects(_dialect, _block); @@ -540,7 +562,7 @@ Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _cal Scope::Function const& function = lookupFunction(_call.functionName.name); canContinue = m_graph.functionInfo.at(&function).canContinue; Stack inputs; - if (canContinue) + if (!m_graph.useFunctions && canContinue) inputs.emplace_back(FunctionCallReturnLabelSlot{_call}); for (auto const& arg: _call.arguments | ranges::views::reverse) inputs.emplace_back(std::visit(*this, arg)); diff --git a/libyul/backends/evm/ControlFlowGraphBuilder.h b/libyul/backends/evm/ControlFlowGraphBuilder.h index 77feaea7b469..83e27bcb267d 100644 --- a/libyul/backends/evm/ControlFlowGraphBuilder.h +++ b/libyul/backends/evm/ControlFlowGraphBuilder.h @@ -31,7 +31,12 @@ class ControlFlowGraphBuilder public: ControlFlowGraphBuilder(ControlFlowGraphBuilder const&) = delete; ControlFlowGraphBuilder& operator=(ControlFlowGraphBuilder const&) = delete; - static std::unique_ptr build(AsmAnalysisInfo const& _analysisInfo, Dialect const& _dialect, Block const& _block); + static std::unique_ptr build( + AsmAnalysisInfo const& _analysisInfo, + Dialect const& _dialect, + std::optional _eofVersion, + Block const& _block + ); StackSlot operator()(Expression const& _literal); StackSlot operator()(Literal const& _literal); @@ -81,6 +86,7 @@ class ControlFlowGraphBuilder AsmAnalysisInfo const& m_info; std::map const& m_functionSideEffects; Dialect const& m_dialect; + std::optional m_eofVersion; CFG::BasicBlock* m_currentBlock = nullptr; Scope* m_scope = nullptr; struct ForLoopInfo diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 7db1ec09d32b..82b8af4f2b73 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include @@ -45,6 +46,8 @@ struct BuiltinContext Object const* currentObject = nullptr; /// Mapping from named objects to abstract assembly sub IDs. std::map subIDs; + + std::map functionIDs; }; struct BuiltinFunctionForEVM: public BuiltinFunction diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp index c5b363293f39..0205becbe150 100644 --- a/libyul/backends/evm/EVMObjectCompiler.cpp +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -85,6 +85,7 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) *_object.analysisInfo, _object.code()->root(), m_dialect, + m_eofVersion, context, OptimizedEVMCodeTransform::UseNamedLabels::ForFirstFunctionOfEachName ); diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index b34835c4e208..554b03ef23e1 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -145,6 +145,32 @@ std::pair, AbstractAssembly::SubID> EthAssembl return {std::make_shared(*assembly), static_cast(sub.data())}; } +AbstractAssembly::FunctionID EthAssemblyAdapter::createFunction(uint8_t _args, uint8_t _rets) +{ + return m_assembly.createFunction(_args, _rets); +} + +void EthAssemblyAdapter::beginFunction(AbstractAssembly::FunctionID _functionID) +{ + m_assembly.beginFunction(_functionID); +} + +void EthAssemblyAdapter::endFunction() +{ + m_assembly.endFunction(); +} + +void EthAssemblyAdapter::appendFunctionReturn() +{ + m_assembly.appendFunctionReturn(); +} + +void EthAssemblyAdapter::appendFunctionCall(FunctionID _functionID, int _stackDiffAfter) +{ + m_assembly.appendFunctionCall(_functionID); + m_assembly.adjustDeposit(_stackDiffAfter); +} + void EthAssemblyAdapter::appendDataOffset(std::vector const& _subPath) { if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index 91707df67156..d1195cfa70a2 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -56,6 +56,11 @@ class EthAssemblyAdapter: public AbstractAssembly void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override; void appendAssemblySize() override; std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = {}) override; + AbstractAssembly::FunctionID createFunction(uint8_t _args, uint8_t _rets) override; + void beginFunction(AbstractAssembly::FunctionID _functionID) override; + void endFunction() override; + void appendFunctionCall(FunctionID _functionID, int _stackDiffAfter = 0) override; + void appendFunctionReturn() override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index d8d7479a980b..711e1242a679 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -103,6 +103,33 @@ std::pair, AbstractAssembly::SubID> NoOutputAs return {}; } +AbstractAssembly::FunctionID NoOutputAssembly::createFunction(uint8_t _args, uint8_t _rets) +{ + yulAssert(m_context->numFunctions <= std::numeric_limits::max()); + AbstractAssembly::FunctionID id = static_cast(m_context->numFunctions++); + m_context->functionSignatures[id] = std::make_pair(_args, _rets); + return id; +} + +void NoOutputAssembly::beginFunction(FunctionID) +{ +} + +void NoOutputAssembly::endFunction() +{ +} + +void NoOutputAssembly::appendFunctionCall(FunctionID _functionID, int) +{ + auto [args, rets] = m_context->functionSignatures.at(_functionID); + m_stackHeight += static_cast(rets) - static_cast(args); +} + +void NoOutputAssembly::appendFunctionReturn() +{ + m_stackHeight = 0; +} + void NoOutputAssembly::appendDataOffset(std::vector const&) { appendInstruction(evmasm::Instruction::PUSH1); diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 271cd297ad1a..66a33e725c06 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -37,6 +37,13 @@ struct SourceLocation; namespace solidity::yul { +class NoOutputAssembly; + +struct NoOutputAssemblyContext +{ + size_t numFunctions = 0; + std::map> functionSignatures; +}; /** * Assembly class that just ignores everything and only performs stack counting. @@ -45,7 +52,7 @@ namespace solidity::yul class NoOutputAssembly: public AbstractAssembly { public: - explicit NoOutputAssembly(langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) { } + explicit NoOutputAssembly(langutil::EVMVersion _evmVersion): m_context(std::make_shared()), m_evmVersion(_evmVersion) { } ~NoOutputAssembly() override = default; void setSourceLocation(langutil::SourceLocation const&) override {} @@ -66,6 +73,11 @@ class NoOutputAssembly: public AbstractAssembly void appendAssemblySize() override; std::pair, SubID> createSubAssembly(bool _creation, std::optional _eofVersion, std::string _name = "") override; + FunctionID createFunction(uint8_t _args, uint8_t _rets) override; + void beginFunction(FunctionID) override; + void endFunction() override; + void appendFunctionCall(FunctionID _functionID, int _stackDiffAfter = 0) override; + void appendFunctionReturn() override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; @@ -82,6 +94,7 @@ class NoOutputAssembly: public AbstractAssembly langutil::EVMVersion evmVersion() const override { return m_evmVersion; } private: + std::shared_ptr m_context; int m_stackHeight = 0; langutil::EVMVersion m_evmVersion; }; diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp index a6b867aba167..37d438e0297c 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.cpp +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.cpp @@ -44,12 +44,30 @@ std::vector OptimizedEVMCodeTransform::run( AsmAnalysisInfo& _analysisInfo, Block const& _block, EVMDialect const& _dialect, + std::optional _eofVersion, BuiltinContext& _builtinContext, UseNamedLabels _useNamedLabelsForFunctions ) { - std::unique_ptr dfg = ControlFlowGraphBuilder::build(_analysisInfo, _dialect, _block); + std::unique_ptr dfg = ControlFlowGraphBuilder::build(_analysisInfo, _dialect, _eofVersion, _block); + yulAssert(_eofVersion.has_value() == dfg->useFunctions); StackLayout stackLayout = StackLayoutGenerator::run(*dfg); + + if (dfg->useFunctions) + { + for (Scope::Function const* function: dfg->functions) + { + auto const& info = dfg->functionInfo.at(function); + yulAssert(info.parameters.size() <= 0xFF); + yulAssert(info.returnVariables.size() <= 0xFF); + auto functionID = _assembly.createFunction( + static_cast(info.parameters.size()), + static_cast(info.canContinue ? info.returnVariables.size() : 0x80) + ); + _builtinContext.functionIDs[function] = functionID; + } + } + OptimizedEVMCodeTransform optimizedCodeTransform( _assembly, _builtinContext, @@ -67,10 +85,11 @@ std::vector OptimizedEVMCodeTransform::run( void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) { + bool useReturnLabel = !m_dfg.useFunctions && _call.canContinue; // Validate stack. { yulAssert(m_assembly.stackHeight() == static_cast(m_stack.size()), ""); - yulAssert(m_stack.size() >= _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0), ""); + yulAssert(m_stack.size() >= _call.function.get().arguments.size() + (useReturnLabel ? 1 : 0), ""); // Assert that we got the correct arguments on stack for the call. for (auto&& [arg, slot]: ranges::zip_view( _call.functionCall.get().arguments | ranges::views::reverse, @@ -78,7 +97,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) )) validateSlot(slot, arg); // Assert that we got the correct return label on stack. - if (_call.canContinue) + if (useReturnLabel) { auto const* returnLabelSlot = std::get_if( &m_stack.at(m_stack.size() - _call.functionCall.get().arguments.size() - 1) @@ -90,23 +109,30 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionCall const& _call) // Emit code. { m_assembly.setSourceLocation(originLocationOf(_call)); - m_assembly.appendJumpTo( - getFunctionLabel(_call.function), - static_cast(_call.function.get().returns.size() - _call.function.get().arguments.size()) - (_call.canContinue ? 1 : 0), - AbstractAssembly::JumpType::IntoFunction - ); - if (_call.canContinue) + if (m_dfg.useFunctions) + m_assembly.appendFunctionCall(m_builtinContext.functionIDs.at(&_call.function.get()), + !_call.canContinue ? static_cast(_call.function.get().returns.size()) : 0); + else + m_assembly.appendJumpTo( + getFunctionLabel(_call.function), + static_cast(_call.function.get().returns.size() - _call.function.get().arguments.size()) - (_call.canContinue ? 1 : 0), + AbstractAssembly::JumpType::IntoFunction + ); + if (useReturnLabel) m_assembly.appendLabel(m_returnLabels.at(&_call.functionCall.get())); } // Update stack. { // Remove arguments and return label from m_stack. - for (size_t i = 0; i < _call.function.get().arguments.size() + (_call.canContinue ? 1 : 0); ++i) + for (size_t i = 0; i < _call.function.get().arguments.size() + (useReturnLabel ? 1 : 0); ++i) m_stack.pop_back(); // Push return values to m_stack. + yulAssert(_call.function.get().returns.size() < 0x80, "Num of function output >= 128"); + for (size_t index: ranges::views::iota(0u, _call.function.get().returns.size())) m_stack.emplace_back(TemporarySlot{_call.functionCall, index}); + yulAssert(m_assembly.stackHeight() == static_cast(m_stack.size()), ""); } } @@ -183,7 +209,7 @@ OptimizedEVMCodeTransform::OptimizedEVMCodeTransform( m_builtinContext(_builtinContext), m_dfg(_dfg), m_stackLayout(_stackLayout), - m_functionLabels([&](){ + m_functionLabels(_dfg.useFunctions ? decltype(m_functionLabels)() : [&](){ std::map functionLabels; std::set assignedFunctionNames; for (Scope::Function const* function: m_dfg.functions) @@ -216,6 +242,7 @@ void OptimizedEVMCodeTransform::assertLayoutCompatibility(Stack const& _currentS AbstractAssembly::LabelID OptimizedEVMCodeTransform::getFunctionLabel(Scope::Function const& _function) { + yulAssert(!m_dfg.useFunctions); return m_functionLabels.at(&m_dfg.functionInfo.at(&_function)); } @@ -493,11 +520,15 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) Stack exitStack = m_currentFunctionInfo->returnVariables | ranges::views::transform([](auto const& _varSlot){ return StackSlot{_varSlot}; }) | ranges::to; - exitStack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); + if (!m_dfg.useFunctions) + exitStack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); // Create the function return layout and jump. createStackLayout(debugDataOf(_functionReturn), exitStack); - m_assembly.appendJump(0, AbstractAssembly::JumpType::OutOfFunction); + if (m_dfg.useFunctions) + m_assembly.appendFunctionReturn(); + else + m_assembly.appendJump(0, AbstractAssembly::JumpType::OutOfFunction); }, [&](CFG::BasicBlock::Terminated const&) { @@ -518,25 +549,31 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block) void OptimizedEVMCodeTransform::operator()(CFG::FunctionInfo const& _functionInfo) { + bool useReturnLabel = !m_dfg.useFunctions && _functionInfo.canContinue; yulAssert(!m_currentFunctionInfo, ""); ScopedSaveAndRestore currentFunctionInfoRestore(m_currentFunctionInfo, &_functionInfo); yulAssert(m_stack.empty() && m_assembly.stackHeight() == 0, ""); // Create function entry layout in m_stack. - if (_functionInfo.canContinue) + if (useReturnLabel) m_stack.emplace_back(FunctionReturnLabelSlot{_functionInfo.function}); for (auto const& param: _functionInfo.parameters | ranges::views::reverse) m_stack.emplace_back(param); + if (m_dfg.useFunctions) + m_assembly.beginFunction(m_builtinContext.functionIDs[&_functionInfo.function]); m_assembly.setStackHeight(static_cast(m_stack.size())); m_assembly.setSourceLocation(originLocationOf(_functionInfo)); - m_assembly.appendLabel(getFunctionLabel(_functionInfo.function)); + if (!m_dfg.useFunctions) + m_assembly.appendLabel(getFunctionLabel(_functionInfo.function)); // Create the entry layout of the function body block and visit. createStackLayout(debugDataOf(_functionInfo), m_stackLayout.blockInfos.at(_functionInfo.entry).entryLayout); (*this)(*_functionInfo.entry); m_stack.clear(); + if (m_dfg.useFunctions) + m_assembly.endFunction(); m_assembly.setStackHeight(0); } diff --git a/libyul/backends/evm/OptimizedEVMCodeTransform.h b/libyul/backends/evm/OptimizedEVMCodeTransform.h index 7e648ca964bb..ec7e6f699787 100644 --- a/libyul/backends/evm/OptimizedEVMCodeTransform.h +++ b/libyul/backends/evm/OptimizedEVMCodeTransform.h @@ -52,6 +52,7 @@ class OptimizedEVMCodeTransform AsmAnalysisInfo& _analysisInfo, Block const& _block, EVMDialect const& _dialect, + std::optional _eofVersion, BuiltinContext& _builtinContext, UseNamedLabels _useNamedLabelsForFunctions ); diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index 1a30842aecc8..a503c034e0b8 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -49,7 +49,7 @@ using namespace solidity::yul; StackLayout StackLayoutGenerator::run(CFG const& _cfg) { - StackLayout stackLayout; + StackLayout stackLayout{_cfg.useFunctions, {}, {}}; StackLayoutGenerator{stackLayout, nullptr}.processEntryPoint(*_cfg.entry); for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) @@ -70,7 +70,7 @@ std::map> StackLayoutGe std::vector StackLayoutGenerator::reportStackTooDeep(CFG const& _cfg, YulName _functionName) { - StackLayout stackLayout; + StackLayout stackLayout{_cfg.useFunctions, {}, {}}; CFG::FunctionInfo const* functionInfo = nullptr; if (!_functionName.empty()) { @@ -464,7 +464,9 @@ std::optional StackLayoutGenerator::getExitLayoutOrStageDependencies( Stack stack = _functionReturn.info->returnVariables | ranges::views::transform([](auto const& _varSlot){ return StackSlot{_varSlot}; }) | ranges::to; - stack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); + + if (!m_layout.useFunctions) + stack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function}); return stack; }, [&](CFG::BasicBlock::Terminated const&) -> std::optional @@ -735,7 +737,7 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const& _block, CFG::Functi _addChild(_conditionalJump.zero); _addChild(_conditionalJump.nonZero); }, - [&](CFG::BasicBlock::FunctionReturn const&) { yulAssert(false); }, + [&](CFG::BasicBlock::FunctionReturn const&) { yulAssert(m_layout.useFunctions); }, [&](CFG::BasicBlock::Terminated const&) {}, }, _block->exit); }); diff --git a/libyul/backends/evm/StackLayoutGenerator.h b/libyul/backends/evm/StackLayoutGenerator.h index fc846a9deced..cbd1d2b2527a 100644 --- a/libyul/backends/evm/StackLayoutGenerator.h +++ b/libyul/backends/evm/StackLayoutGenerator.h @@ -37,6 +37,7 @@ struct StackLayout /// The resulting stack layout after executing the block. Stack exitLayout; }; + bool useFunctions = false; std::map blockInfos; /// For each operation the complete stack layout that: /// - has the slots required for the operation at the stack top. diff --git a/libyul/optimiser/OptimiserStep.h b/libyul/optimiser/OptimiserStep.h index 89bfcef814d0..fa5a6593396a 100644 --- a/libyul/optimiser/OptimiserStep.h +++ b/libyul/optimiser/OptimiserStep.h @@ -34,6 +34,7 @@ class NameDispenser; struct OptimiserStepContext { Dialect const& dialect; + std::optional eofVersion; NameDispenser& dispenser; std::set const& reservedIdentifiers; /// The value nullopt represents creation code diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 2c2ef4272904..e2414f75411e 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -238,6 +238,7 @@ void eliminateVariablesOptimizedCodegen( std::tuple StackCompressor::run( Dialect const& _dialect, + std::optional _eofVersion, Object const& _object, bool _optimizeStackAllocation, size_t _maxIterations) @@ -258,7 +259,7 @@ std::tuple StackCompressor::run( if (usesOptimizedCodeGenerator) { yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, astRoot, _object.qualifiedDataNames()); - std::unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, astRoot); + std::unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, _dialect, _eofVersion, astRoot); eliminateVariablesOptimizedCodegen( _dialect, astRoot, diff --git a/libyul/optimiser/StackCompressor.h b/libyul/optimiser/StackCompressor.h index 15d057455ac8..3c2f19be6049 100644 --- a/libyul/optimiser/StackCompressor.h +++ b/libyul/optimiser/StackCompressor.h @@ -48,6 +48,7 @@ class StackCompressor /// @returns tuple with true if it was successful as first element, second element is the modified AST. static std::tuple run( Dialect const& _dialect, + std::optional _eofVersion, Object const& _object, bool _optimizeStackAllocation, size_t _maxIterations diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index 6e6da63e603b..17c72b559c18 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -133,7 +133,7 @@ Block StackLimitEvader::run( if (evmDialect && evmDialect->evmVersion().canOverchargeGasForCall()) { yul::AsmAnalysisInfo analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(*evmDialect, astRoot, _object.qualifiedDataNames()); - std::unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, *evmDialect, astRoot); + std::unique_ptr cfg = ControlFlowGraphBuilder::build(analysisInfo, *evmDialect, _context.eofVersion, astRoot); run(_context, astRoot, StackLayoutGenerator::reportStackTooDeep(*cfg)); } else diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index a06f48a7f236..71c80b339480 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -135,6 +135,7 @@ void outputPerformanceMetrics(map const& _metrics) void OptimiserSuite::run( Dialect const& _dialect, + std::optional _eofVersion, GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, @@ -160,7 +161,7 @@ void OptimiserSuite::run( )(_object.code()->root())); NameDispenser dispenser{_dialect, astRoot, reservedIdentifiers}; - OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment}; + OptimiserStepContext context{_dialect, _eofVersion, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment}; OptimiserSuite suite(context, Debug::None); @@ -183,6 +184,7 @@ void OptimiserSuite::run( _object.setCode(std::make_shared(std::move(astRoot))); astRoot = std::get<1>(StackCompressor::run( _dialect, + _eofVersion, _object, _optimizeStackAllocation, stackCompressorMaxIterations @@ -205,6 +207,7 @@ void OptimiserSuite::run( _object.setCode(std::make_shared(std::move(astRoot))); astRoot = std::get<1>(StackCompressor::run( _dialect, + _eofVersion, _object, _optimizeStackAllocation, stackCompressorMaxIterations diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 15a41cb9f3dc..17ee5072a917 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -64,6 +64,7 @@ class OptimiserSuite /// The value nullopt for `_expectedExecutionsPerDeployment` represents creation code. static void run( Dialect const& _dialect, + std::optional _eofVersion, GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, diff --git a/test/libyul/ControlFlowGraphTest.cpp b/test/libyul/ControlFlowGraphTest.cpp index 5c9bba8d41e5..6241e35d4ddc 100644 --- a/test/libyul/ControlFlowGraphTest.cpp +++ b/test/libyul/ControlFlowGraphTest.cpp @@ -206,7 +206,7 @@ TestCase::TestResult ControlFlowGraphTest::run(std::ostream& _stream, std::strin std::ostringstream output; - std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, object->code()->root()); + std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, std::nullopt /* TODO */, object->code()->root()); output << "digraph CFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; ControlFlowGraphPrinter printer{output}; diff --git a/test/libyul/KnowledgeBaseTest.cpp b/test/libyul/KnowledgeBaseTest.cpp index 0293f8ee4472..cc28412d64cc 100644 --- a/test/libyul/KnowledgeBaseTest.cpp +++ b/test/libyul/KnowledgeBaseTest.cpp @@ -52,7 +52,7 @@ class KnowledgeBaseTest auto astRoot = std::get(yul::ASTCopier{}(m_object->code()->root())); NameDispenser dispenser(m_dialect, astRoot); std::set reserved; - OptimiserStepContext context{m_dialect, dispenser, reserved, 0}; + OptimiserStepContext context{m_dialect, std::nullopt /* TODO */, dispenser, reserved, 0}; CommonSubexpressionEliminator::run(context, astRoot); m_ssaValues(astRoot); diff --git a/test/libyul/StackLayoutGeneratorTest.cpp b/test/libyul/StackLayoutGeneratorTest.cpp index 9c16eaefc110..1db19243fd75 100644 --- a/test/libyul/StackLayoutGeneratorTest.cpp +++ b/test/libyul/StackLayoutGeneratorTest.cpp @@ -224,7 +224,7 @@ TestCase::TestResult StackLayoutGeneratorTest::run(std::ostream& _stream, std::s std::ostringstream output; - std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, object->code()->root()); + std::unique_ptr cfg = ControlFlowGraphBuilder::build(*analysisInfo, *m_dialect, std::nullopt /* TODO */, object->code()->root()); StackLayout stackLayout = StackLayoutGenerator::run(*cfg); output << "digraph CFG {\nnodesep=0.7;\nnode[shape=box];\n\n"; diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index bf374c21fcee..d67959145a56 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -411,7 +411,7 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( { Object object(*m_optimizedObject); object.setCode(std::make_shared(std::get(ASTCopier{}(block)))); - block = std::get<1>(StackCompressor::run(*m_dialect, object, true, maxIterations)); + block = std::get<1>(StackCompressor::run(*m_dialect, std::nullopt /* TODO */, object, true, maxIterations)); } BlockFlattener::run(*m_context, block); return block; @@ -420,6 +420,7 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( GasMeter meter(dynamic_cast(*m_dialect), false, 200); OptimiserSuite::run( *m_dialect, + std::nullopt, // TODO &meter, *m_optimizedObject, true, @@ -553,6 +554,7 @@ void YulOptimizerTestCommon::updateContext(Block const& _block) m_nameDispenser = std::make_unique(*m_dialect, _block, m_reservedIdentifiers); m_context = std::make_unique(OptimiserStepContext{ *m_dialect, + std::nullopt, // TODO *m_nameDispenser, m_reservedIdentifiers, frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index d5738c069a4c..c2908090ee71 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -490,6 +490,9 @@ u256 EVMInstructionInterpreter::eval( // TODO: Not sure about it. case Instruction::DATALOAD: case Instruction::DATALOADN: + case Instruction::CALLF: + case Instruction::RETF: + case Instruction::JUMPF: case Instruction::RJUMP: case Instruction::RJUMPI: case Instruction::RJUMPV: diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index faab89e7923e..6f2d4f959280 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -219,7 +219,7 @@ class YulOpti { Object obj; obj.setCode(std::make_shared(std::get(ASTCopier{}(*m_astRoot)))); - *m_astRoot = std::get<1>(StackCompressor::run(m_dialect, obj, true, 16)); + *m_astRoot = std::get<1>(StackCompressor::run(m_dialect, std::nullopt /* TODO */, obj, true, 16)); break; } default: @@ -248,6 +248,7 @@ class YulOpti NameDispenser m_nameDispenser{m_dialect, m_reservedIdentifiers}; OptimiserStepContext m_context{ m_dialect, + std::nullopt, // TODO m_nameDispenser, m_reservedIdentifiers, solidity::frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment diff --git a/tools/yulPhaser/Program.cpp b/tools/yulPhaser/Program.cpp index 14b6406493d7..863787f139d5 100644 --- a/tools/yulPhaser/Program.cpp +++ b/tools/yulPhaser/Program.cpp @@ -193,6 +193,7 @@ std::unique_ptr Program::applyOptimisationSteps( std::set const externallyUsedIdentifiers = {}; OptimiserStepContext context{ _dialect, + std::nullopt, // TODO _nameDispenser, externallyUsedIdentifiers, frontend::OptimiserSettings::standard().expectedExecutionsPerDeployment From 412edcdb09d5027a72895c8ee0204552d3d532d6 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Thu, 25 Jul 2024 16:43:40 +0200 Subject: [PATCH 08/15] eof: Pass flag to `copyConstructorArgumentsToMemoryFunction` (EIP-7620) Preparation to implement new contract creation Co-authored-by: Daniel Kirchner --- libsolidity/codegen/ABIFunctions.h | 3 +- libsolidity/codegen/CompilerContext.h | 4 +-- libsolidity/codegen/YulUtilFunctions.cpp | 31 ++++++++++++------- libsolidity/codegen/YulUtilFunctions.h | 3 ++ .../codegen/ir/IRGenerationContext.cpp | 4 +-- libsolidity/codegen/ir/IRGenerationContext.h | 4 +++ libsolidity/codegen/ir/IRGenerator.cpp | 3 +- libsolidity/codegen/ir/IRGenerator.h | 3 +- .../codegen/ir/IRGeneratorForStatements.cpp | 4 +-- 9 files changed, 38 insertions(+), 21 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index b7f7f8ae5735..d357447ac3e8 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -56,13 +56,14 @@ class ABIFunctions public: explicit ABIFunctions( langutil::EVMVersion _evmVersion, + std::optional _eofVersion, RevertStrings _revertStrings, MultiUseYulFunctionCollector& _functionCollector ): m_evmVersion(_evmVersion), m_revertStrings(_revertStrings), m_functionCollector(_functionCollector), - m_utils(_evmVersion, m_revertStrings, m_functionCollector) + m_utils(_evmVersion, _eofVersion, m_revertStrings, m_functionCollector) {} /// @returns name of an assembly function to ABI-encode values of @a _givenTypes diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 80fee14575df..8de3671515c0 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -71,8 +71,8 @@ class CompilerContext m_revertStrings(_revertStrings), m_reservedMemory{0}, m_runtimeContext(_runtimeContext), - m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector), - m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector) + m_abiFunctions(m_evmVersion, _eofVersion, m_revertStrings, m_yulFunctionCollector), + m_yulUtilFunctions(m_evmVersion, _eofVersion, m_revertStrings, m_yulFunctionCollector) { if (m_runtimeContext) m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index ebf1d384aa57..4d555843861e 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -285,7 +285,7 @@ std::string YulUtilFunctions::revertWithError( errorArgumentTypes.push_back(arg->annotation().type); } templ("argumentVars", joinHumanReadablePrefixed(errorArgumentVars)); - templ("encode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleEncoder(errorArgumentTypes, _parameterTypes)); + templ("encode", ABIFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector).tupleEncoder(errorArgumentTypes, _parameterTypes)); return templ.render(); } @@ -2592,7 +2592,7 @@ std::string YulUtilFunctions::copyArrayFromStorageToMemoryFunction(ArrayType con if (_from.baseType()->isValueType()) { solAssert(*_from.baseType() == *_to.baseType(), ""); - ABIFunctions abi(m_evmVersion, m_revertStrings, m_functionCollector); + ABIFunctions abi(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector); return Whiskers(R"( function (slot) -> memPtr { memPtr := () @@ -2697,7 +2697,7 @@ std::string YulUtilFunctions::bytesOrStringConcatFunction( templ("finalizeAllocation", finalizeAllocationFunction()); templ( "encodePacked", - ABIFunctions{m_evmVersion, m_revertStrings, m_functionCollector}.tupleEncoderPacked( + ABIFunctions{m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector}.tupleEncoderPacked( _argumentTypes, targetTypes ) @@ -3541,7 +3541,7 @@ std::string YulUtilFunctions::conversionFunction(Type const& _from, Type const& )") ( "abiDecode", - ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).abiDecodingFunctionStruct( + ABIFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector).abiDecodingFunctionStruct( toStructType, false ) @@ -3869,6 +3869,7 @@ std::string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, Ar _from.dataStoredIn(DataLocation::CallData) ? ABIFunctions( m_evmVersion, + m_eofVersion, m_revertStrings, m_functionCollector ).abiDecodingFunctionArrayAvailableLength(_to, false) : @@ -4067,7 +4068,7 @@ std::string YulUtilFunctions::packedHashFunction( templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); templ("comma", sizeOnStack > 0 ? "," : ""); templ("allocateUnbounded", allocateUnboundedFunction()); - templ("packedEncode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); + templ("packedEncode", ABIFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); return templ.render(); }); } @@ -4688,16 +4689,21 @@ std::string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction( return m_functionCollector.createFunction(functionName, [&]() { std::string returnParams = suffixedVariableNameList("ret_param_",0, CompilerUtils::sizeOnStack(_contract.constructor()->parameters())); - ABIFunctions abiFunctions(m_evmVersion, m_revertStrings, m_functionCollector); + ABIFunctions abiFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functionCollector); return util::Whiskers(R"( function () -> { - let programSize := datasize("") - let argSize := sub(codesize(), programSize) - - let memoryDataOffset := (argSize) - codecopy(memoryDataOffset, programSize, argSize) - + + let argSize := calldatasize() + let memoryDataOffset := (argSize) + calldatacopy(memoryDataOffset, 0, argSize) + + let programSize := datasize("") + let argSize := sub(codesize(), programSize) + + let memoryDataOffset := (argSize) + codecopy(memoryDataOffset, programSize, argSize) + := (memoryDataOffset, add(memoryDataOffset, argSize)) } )") @@ -4706,6 +4712,7 @@ std::string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction( ("object", _creationObjectName) ("allocate", allocationFunction()) ("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true)) + ("eof", m_eofVersion.has_value()) .render(); }); } diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index a1b45b656f13..20fd6af2192d 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -51,10 +51,12 @@ class YulUtilFunctions public: explicit YulUtilFunctions( langutil::EVMVersion _evmVersion, + std::optional _eofVersion, RevertStrings _revertStrings, MultiUseYulFunctionCollector& _functionCollector ): m_evmVersion(_evmVersion), + m_eofVersion(_eofVersion), m_revertStrings(_revertStrings), m_functionCollector(_functionCollector) {} @@ -624,6 +626,7 @@ class YulUtilFunctions std::string longByteArrayStorageIndexAccessNoCheckFunction(); langutil::EVMVersion m_evmVersion; + std::optional m_eofVersion; RevertStrings m_revertStrings; MultiUseYulFunctionCollector& m_functionCollector; }; diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index e087dc69a575..067eda550dd6 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -169,10 +169,10 @@ void IRGenerationContext::internalFunctionCalledThroughDispatch(YulArity const& YulUtilFunctions IRGenerationContext::utils() { - return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions); + return YulUtilFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functions); } ABIFunctions IRGenerationContext::abiFunctions() { - return ABIFunctions(m_evmVersion, m_revertStrings, m_functions); + return ABIFunctions(m_evmVersion, m_eofVersion, m_revertStrings, m_functions); } diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 2bf837697b47..69608f36b449 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -58,6 +58,7 @@ class IRGenerationContext IRGenerationContext( langutil::EVMVersion _evmVersion, + std::optional _eofVersion, ExecutionContext _executionContext, RevertStrings _revertStrings, std::map _sourceIndices, @@ -65,6 +66,7 @@ class IRGenerationContext langutil::CharStreamProvider const* _soliditySourceProvider ): m_evmVersion(_evmVersion), + m_eofVersion(_eofVersion), m_executionContext(_executionContext), m_revertStrings(_revertStrings), m_sourceIndices(std::move(_sourceIndices)), @@ -134,6 +136,7 @@ class IRGenerationContext YulUtilFunctions utils(); langutil::EVMVersion evmVersion() const { return m_evmVersion; } + std::optional eofVersion() const { return m_eofVersion; } ExecutionContext executionContext() const { return m_executionContext; } void setArithmetic(Arithmetic _value) { m_arithmetic = _value; } @@ -160,6 +163,7 @@ class IRGenerationContext private: langutil::EVMVersion m_evmVersion; + std::optional m_eofVersion; ExecutionContext m_executionContext; RevertStrings m_revertStrings; std::map m_sourceIndices; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 00c9fa196bd8..d4f791f2aa02 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -738,7 +738,7 @@ std::string IRGenerator::generateExternalFunction(ContractDefinition const& _con unsigned paramVars = std::make_shared(_functionType.parameterTypes())->sizeOnStack(); unsigned retVars = std::make_shared(_functionType.returnParameterTypes())->sizeOnStack(); - ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector()); + ABIFunctions abiFunctions(m_evmVersion, m_eofVersion, m_context.revertStrings(), m_context.functionCollector()); t("abiDecode", abiFunctions.tupleDecoder(_functionType.parameterTypes())); t("params", suffixedVariableNameList("param_", 0, paramVars)); t("retParams", suffixedVariableNameList("ret_", 0, retVars)); @@ -1106,6 +1106,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionCon ); IRGenerationContext newContext( m_evmVersion, + m_eofVersion, _context, m_context.revertStrings(), m_context.sourceIndices(), diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index 9f01b74fc246..60d5d25cd026 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -59,13 +59,14 @@ class IRGenerator m_eofVersion(_eofVersion), m_context( _evmVersion, + _eofVersion, ExecutionContext::Creation, _revertStrings, std::move(_sourceIndices), _debugInfoSelection, _soliditySourceProvider ), - m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector()), + m_utils(_evmVersion, _eofVersion, m_context.revertStrings(), m_context.functionCollector()), m_optimiserSettings(_optimiserSettings) {} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 459959604625..2efcb0fbff21 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1070,7 +1070,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) { auto const& event = dynamic_cast(functionType->declaration()); TypePointers paramTypes = functionType->parameterTypes(); - ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); + ABIFunctions abi(m_context.evmVersion(), m_context.eofVersion(), m_context.revertStrings(), m_context.functionCollector()); std::vector indexedArgs; std::vector nonIndexedArgs; @@ -2783,7 +2783,7 @@ void IRGeneratorForStatements::appendBareCall( else { templ("needsEncoding", true); - ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); + ABIFunctions abi(m_context.evmVersion(), m_context.eofVersion(), m_context.revertStrings(), m_context.functionCollector()); templ("encode", abi.tupleEncoderPacked({&argType}, {TypeProvider::bytesMemory()})); } From 76a25cc2fab6745951d08c15ed6c015a310b62dd Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 10:46:44 +0200 Subject: [PATCH 09/15] eof: Implement EOF Contract Creation (EIP-7620) --- libevmasm/Assembly.cpp | 18 ++- libevmasm/Assembly.h | 14 ++ libevmasm/AssemblyItem.cpp | 28 +++- libevmasm/AssemblyItem.h | 10 ++ libevmasm/Instruction.cpp | 4 + libevmasm/Instruction.h | 3 + libevmasm/SemanticInformation.cpp | 6 + .../codegen/ir/IRGenerationContext.cpp | 12 ++ libsolidity/codegen/ir/IRGenerationContext.h | 17 ++- libsolidity/codegen/ir/IRGenerator.cpp | 139 ++++++++++++------ libsolidity/codegen/ir/IRGenerator.h | 3 +- .../codegen/ir/IRGeneratorForStatements.cpp | 44 ++++-- .../experimental/codegen/IRGenerator.cpp | 9 +- libyul/backends/evm/AbstractAssembly.h | 4 + libyul/backends/evm/EVMDialect.cpp | 62 ++++++++ libyul/backends/evm/EthAssemblyAdapter.cpp | 10 ++ libyul/backends/evm/EthAssemblyAdapter.h | 2 + libyul/backends/evm/NoOutputAssembly.cpp | 10 ++ libyul/backends/evm/NoOutputAssembly.h | 2 + .../EVMInstructionInterpreter.cpp | 2 + 20 files changed, 335 insertions(+), 64 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index c17ef94ba241..143e6c3a3392 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -951,7 +951,7 @@ LinkerObject const& Assembly::assemble() const // TODO: consider fully producing all sub and data refs in this pass already. for (auto&& codeSection: m_codeSections) for (AssemblyItem const& i: codeSection.items) - if (i.type() == PushSub) + if (i.type() == PushSub || i.type() == EofCreate || i.type() == ReturnContract) bytesRequiredForSubs += static_cast(subAssemblyById(static_cast(i.data()))->assemble().bytecode.size()); unsigned bytesRequiredForDataUpperBound = static_cast(m_auxiliaryData.size()); @@ -1246,6 +1246,22 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.push_back(static_cast(Instruction::RETF)); break; } + case EofCreate: + { + assertThrow(eof, AssemblyException, "Eof create (EOFCREATE) in non-EOF code"); + ret.bytecode.push_back(static_cast(Instruction::EOFCREATE)); + subRef.insert(std::make_pair(static_cast(i.data()), ret.bytecode.size())); + ret.bytecode.push_back(static_cast(i.data())); + break; + } + case ReturnContract: + { + assertThrow(eof, AssemblyException, "Return contract (RETURNCONTRACT) in non-EOF code"); + ret.bytecode.push_back(static_cast(Instruction::RETURNCONTRACT)); + subRef.insert(std::make_pair(static_cast(i.data()), ret.bytecode.size())); + ret.bytecode.push_back(static_cast(i.data())); + break; + } case RelativeJump: case ConditionalRelativeJump: { diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 64f9ef076ee7..268cff32f783 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -144,6 +144,20 @@ class Assembly return append(newFunctionReturn()); } + AssemblyItem appendEOFCreate(uint16_t _containerId) + { + assertThrow(_containerId < m_subs.size(), AssemblyException, "EOF Create of undefined container"); + + return append(AssemblyItem::eofCreate(_containerId)); + } + + AssemblyItem appendReturnContract(uint16_t _containerId) + { + assertThrow(_containerId < m_subs.size(), AssemblyException, "Return undefined container id"); + + return append(AssemblyItem::returnContract(_containerId)); + } + AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index c01661f93c26..3898033dec63 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -161,15 +161,19 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ } case VerbatimBytecode: return std::get<2>(*m_verbatimBytecode).size(); - case RelativeJump: - return 3; - case ConditionalRelativeJump: - return 3; case JumpF: case CallF: return 3; case RetF: return 1; + case RelativeJump: + return 3; + case ConditionalRelativeJump: + return 3; + case EofCreate: + return 2; + case ReturnContract: + return 2; case DataLoadN: return 2; default: @@ -188,8 +192,6 @@ size_t AssemblyItem::arguments() const return std::get<0>(*m_verbatimBytecode); else if (type() == AssignImmutable) return 2; - else if (type() == ConditionalRelativeJump) - return 1; else if (type() == CallF || type() == JumpF) { assertThrow(m_functionSignature.has_value(), AssemblyException, ""); @@ -197,6 +199,12 @@ size_t AssemblyItem::arguments() const } else if (type() == RetF) return static_cast(data()); + else if (type() == ConditionalRelativeJump) + return 1; + else if (type() == EofCreate) + return 4; + else if (type() == ReturnContract) + return 2; else return 0; } @@ -229,8 +237,10 @@ size_t AssemblyItem::returnValues() const case JumpF: return 0; case RetF: + case ReturnContract: return 0; case DataLoadN: + case EofCreate: return 1; default: break; @@ -371,6 +381,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case RetF: text = "retf"; break; + case EofCreate: + text = "eofcreate(" + std::to_string(static_cast(data())) + ")"; + break; + case ReturnContract: + text = "returcontract(" + std::to_string(static_cast(data())) + ")"; + break; case DataLoadN: text = "dataloadn(" + std::to_string(static_cast(data())) + ")"; break; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 681a2ecf8bfd..f8be897f69a7 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -58,6 +58,8 @@ enum AssemblyItemType RetF, RelativeJump, ConditionalRelativeJump, + EofCreate, + ReturnContract }; enum class Precision { Precise , Approximate }; @@ -119,6 +121,14 @@ class AssemblyItem { return AssemblyItem(ConditionalRelativeJump, _tag.data(), _debugData); } + static AssemblyItem eofCreate(uint16_t _containerID, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(EofCreate, _containerID, _debugData); + } + static AssemblyItem returnContract(uint16_t _containerID, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(ReturnContract, _containerID, _debugData); + } AssemblyItem(AssemblyItem const&) = default; AssemblyItem(AssemblyItem&&) = default; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 1f5d705a3dd7..03e1956fbe48 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -176,6 +176,8 @@ std::map const solidity::evmasm::c_instructions = { "RJUMP", Instruction::RJUMP }, { "RJUMPI", Instruction::RJUMPI }, { "RJUMPV", Instruction::RJUMPV }, + { "EOFCREATE", Instruction::EOFCREATE }, + { "RETURNCONTRACT", Instruction::RETURNCONTRACT }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "CALLCODE", Instruction::CALLCODE }, @@ -338,6 +340,8 @@ static std::map const c_instructionInfo = {Instruction::RETF, {"RETF", 0, 0, 0, true, Tier::Special}}, {Instruction::CALLF, {"CALLF", 2, 0, 0, true, Tier::Special}}, {Instruction::JUMPF, {"JUMPF", 2, 0, 0, true, Tier::Special}}, + {Instruction::EOFCREATE, {"EOFCREATE", 1, 4, 1, true, Tier::Special}}, + {Instruction::RETURNCONTRACT, {"RETURNCONTRACT", 1, 2, 0, true, Tier::Special}}, {Instruction::CREATE, {"CREATE", 0, 3, 1, true, Tier::Special}}, {Instruction::CALL, {"CALL", 0, 7, 1, true, Tier::Special}}, {Instruction::CALLCODE, {"CALLCODE", 0, 7, 1, true, Tier::Special}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 2565df2abe2d..9ae9f9ecf68d 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -193,6 +193,9 @@ enum class Instruction: uint8_t RETF = 0xe4, ///< return to caller from the code section of EOF continer JUMPF = 0xe5, ///< jump to a code section of EOF contaner. No stack cleaning. + EOFCREATE = 0xec, ///< create a new account with associated container code. + RETURNCONTRACT = 0xee, ///< create a new account with associated container code. + CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index cb6e73f98d56..bc3f04b45b3e 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -301,6 +301,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) case Instruction::STOP: case Instruction::INVALID: case Instruction::REVERT: + case Instruction::RETURNCONTRACT: return true; default: return false; @@ -324,6 +325,7 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction) case Instruction::STOP: case Instruction::INVALID: case Instruction::REVERT: + case Instruction::RETURNCONTRACT: return true; default: return false; @@ -482,6 +484,7 @@ SemanticInformation::Effect SemanticInformation::storage(Instruction _instructio case Instruction::CREATE: case Instruction::CREATE2: case Instruction::SSTORE: + case Instruction::EOFCREATE: return SemanticInformation::Write; case Instruction::SLOAD: @@ -503,6 +506,7 @@ SemanticInformation::Effect SemanticInformation::transientStorage(Instruction _i case Instruction::CREATE: case Instruction::CREATE2: case Instruction::TSTORE: + case Instruction::EOFCREATE: return SemanticInformation::Write; case Instruction::TLOAD: @@ -523,6 +527,7 @@ SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruc case Instruction::DELEGATECALL: case Instruction::CREATE: case Instruction::CREATE2: + case Instruction::EOFCREATE: case Instruction::SELFDESTRUCT: case Instruction::STATICCALL: // because it can affect returndatasize // Strictly speaking, log0, .., log4 writes to the state, but the EVM cannot read it, so they @@ -597,6 +602,7 @@ bool SemanticInformation::invalidInViewFunctions(Instruction _instruction) case Instruction::CALL: case Instruction::CALLCODE: case Instruction::DELEGATECALL: + case Instruction::EOFCREATE: case Instruction::CREATE2: case Instruction::SELFDESTRUCT: return true; diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 067eda550dd6..3cadd2fb7f84 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -95,6 +95,7 @@ void IRGenerationContext::registerImmutableVariable(VariableDeclaration const& _ solAssert(m_reservedMemory.has_value(), "Reserved memory has already been reset."); m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap."); + m_immutableVariablesOffsetsInDataSection[&_variable] = *m_reservedMemory; *m_reservedMemory += _variable.annotation().type->memoryHeadSize(); } @@ -115,6 +116,17 @@ size_t IRGenerationContext::reservedMemory() return reservedMemory; } +size_t IRGenerationContext::immutablesMemorySize() const +{ + solAssert(m_reservedMemory.has_value(), "Reserved memory was used before."); + return *m_reservedMemory; +} + +size_t IRGenerationContext::immutablesMemoryOffset() const +{ + return CompilerUtils::generalPurposeMemoryStart; +} + void IRGenerationContext::addStateVariable( VariableDeclaration const& _declaration, u256 _storageOffset, diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 69608f36b449..7cec38ac2539 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -63,13 +63,15 @@ class IRGenerationContext RevertStrings _revertStrings, std::map _sourceIndices, langutil::DebugInfoSelection const& _debugInfoSelection, - langutil::CharStreamProvider const* _soliditySourceProvider + langutil::CharStreamProvider const* _soliditySourceProvider, + std::map _immutableVariablesOffsetsInDataSection ): m_evmVersion(_evmVersion), m_eofVersion(_eofVersion), m_executionContext(_executionContext), m_revertStrings(_revertStrings), m_sourceIndices(std::move(_sourceIndices)), + m_immutableVariablesOffsetsInDataSection(_immutableVariablesOffsetsInDataSection), m_debugInfoSelection(_debugInfoSelection), m_soliditySourceProvider(_soliditySourceProvider) {} @@ -108,6 +110,8 @@ class IRGenerationContext /// Intended to be used only once for initializing the free memory pointer /// to after the area used for immutables. size_t reservedMemory(); + size_t immutablesMemorySize() const; + size_t immutablesMemoryOffset() const; void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } @@ -138,6 +142,14 @@ class IRGenerationContext langutil::EVMVersion evmVersion() const { return m_evmVersion; } std::optional eofVersion() const { return m_eofVersion; } ExecutionContext executionContext() const { return m_executionContext; } + size_t immutableVariableOffsetInDataSection(VariableDeclaration const& _variable) const + { + solAssert( + m_immutableVariablesOffsetsInDataSection.count(&_variable), + "Unknown immutable variable: " + _variable.name() + ); + return m_immutableVariablesOffsetsInDataSection.at(&_variable); + } void setArithmetic(Arithmetic _value) { m_arithmetic = _value; } Arithmetic arithmetic() const { return m_arithmetic; } @@ -160,6 +172,7 @@ class IRGenerationContext langutil::DebugInfoSelection debugInfoSelection() const { return m_debugInfoSelection; } langutil::CharStreamProvider const* soliditySourceProvider() const { return m_soliditySourceProvider; } + std::map const& immutableVariablesOffsetsInDataSection() const { return m_immutableVariablesOffsetsInDataSection; } private: langutil::EVMVersion m_evmVersion; @@ -173,6 +186,8 @@ class IRGenerationContext /// Memory offsets reserved for the values of immutable variables during contract creation. /// This map is empty in the runtime context. std::map m_immutableVariables; + /// EOF Data section offsets of values of immutable variables + std::map m_immutableVariablesOffsetsInDataSection; /// Total amount of reserved memory. Reserved memory is used to store /// immutable variables during contract creation. std::optional m_reservedMemory = {0}; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index d4f791f2aa02..27b1aa8c2480 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -151,7 +151,11 @@ std::string IRGenerator::generate( - let called_via_delegatecall := iszero(eq(loadimmutable(""), address())) + + let called_via_delegatecall := iszero(eq(dataloadn(""), address())) + + let called_via_delegatecall := iszero(eq(loadimmutable(""), address())) + @@ -210,7 +214,13 @@ std::string IRGenerator::generate( // Do not register immutables to avoid assignment. t("DeployedObject", IRNames::deployedObject(_contract)); t("sourceLocationCommentDeployed", dispenseLocationComment(_contract)); - t("library_address", IRNames::libraryAddressImmutable()); + auto const eof = m_context.eofVersion().has_value(); + t("eof", eof); + if (!eof) + t("library_address", IRNames::libraryAddressImmutable()); + else + t("library_address_data_offset", "0"); + t("dispatch", dispatchRoutine(_contract)); std::set deployedFunctionList = generateQueuedFunctions(); generateInternalDispatchFunctions(_contract); @@ -542,27 +552,31 @@ std::string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) { solAssert(paramTypes.empty(), ""); solUnimplementedAssert(type->sizeOnStack() == 1); - return Whiskers(R"( + + auto t = Whiskers(R"( function () -> rval { - rval := loadimmutable("") + + rval := dataloadn("") + + rval := loadimmutable("") + } - )") - ( - "astIDComment", - m_context.debugInfoSelection().astID ? - "/// @ast-id " + std::to_string(_varDecl.id()) + "\n" : - "" - ) - ("sourceLocationComment", dispenseLocationComment(_varDecl)) - ( - "contractSourceLocationComment", - dispenseLocationComment(m_context.mostDerivedContract()) - ) - ("functionName", functionName) - ("id", std::to_string(_varDecl.id())) - .render(); + )"); + t("astIDComment", m_context.debugInfoSelection().astID ? "/// @ast-id " + std::to_string(_varDecl.id()) + "\n" : ""); + t("sourceLocationComment", dispenseLocationComment(_varDecl)); + t("contractSourceLocationComment", dispenseLocationComment(m_context.mostDerivedContract())); + auto const eof = m_context.eofVersion().has_value(); + t("eof", eof); + t("functionName", functionName); + + if (!eof) + t("id", std::to_string(_varDecl.id())); + else + t("id", std::to_string(m_context.immutableVariableOffsetInDataSection(_varDecl))); + + return t.render(); } else if (_varDecl.isConstant()) { @@ -951,38 +965,78 @@ void IRGenerator::generateConstructors(ContractDefinition const& _contract) std::string IRGenerator::deployCode(ContractDefinition const& _contract) { Whiskers t(R"X( - let := () - codecopy(, dataoffset(""), datasize("")) - <#immutables> - setimmutable(, "", ) - - return(, datasize("")) + + + let immutablesOffset := () + mstore(immutablesOffset, address()) + let immutablesSize := 32 + returncontract("", immutablesOffset, immutablesSize) + + returncontract("", , ) + + + let := () + codecopy(, dataoffset(""), datasize("")) + <#immutables> + setimmutable(, "", ) + + return(, datasize("")) + )X"); + auto const eof = m_context.eofVersion().has_value(); + auto const isLibrary = _contract.isLibrary(); + t("eof", eof); t("allocateUnbounded", m_utils.allocateUnboundedFunction()); - t("codeOffset", m_context.newYulVariable()); + if (!eof) + t("codeOffset", m_context.newYulVariable()); + else + t("library", isLibrary); + t("object", IRNames::deployedObject(_contract)); std::vector> immutables; - if (_contract.isLibrary()) + if (isLibrary) { - solAssert(ContractType(_contract).immutableVariables().empty(), ""); - immutables.emplace_back(std::map{ - {"immutableName"s, IRNames::libraryAddressImmutable()}, - {"value"s, "address()"} - }); - - } - else - for (VariableDeclaration const* immutable: ContractType(_contract).immutableVariables()) + if (!eof) { - solUnimplementedAssert(immutable->type()->isValueType()); - solUnimplementedAssert(immutable->type()->sizeOnStack() == 1); + solAssert(ContractType(_contract).immutableVariables().empty(), ""); immutables.emplace_back(std::map{ - {"immutableName"s, std::to_string(immutable->id())}, - {"value"s, "mload(" + std::to_string(m_context.immutableMemoryOffset(*immutable)) + ")"} + {"immutableName"s, IRNames::libraryAddressImmutable()}, + {"value"s, "address()"} }); + t("immutables", std::move(immutables)); + } + } + else + { + if (!eof) + { + for (VariableDeclaration const* immutable: ContractType(_contract).immutableVariables()) + { + solUnimplementedAssert(immutable->type()->isValueType()); + solUnimplementedAssert(immutable->type()->sizeOnStack() == 1); + immutables.emplace_back(std::map{ + {"immutableName"s, std::to_string(immutable->id())}, + {"value"s, "mload(" + std::to_string(m_context.immutableMemoryOffset(*immutable)) + ")"} + }); + } + + t("immutables", std::move(immutables)); } - t("immutables", std::move(immutables)); + else + { + auto const& immutableVariables = ContractType(_contract).immutableVariables(); + for (VariableDeclaration const* immutable: immutableVariables) + { + solUnimplementedAssert(immutable->type()->isValueType()); + solUnimplementedAssert(immutable->type()->sizeOnStack() == 1); + } + + t("immutablesOffset", std::to_string(m_context.immutablesMemoryOffset())); + t("immutablesSize", std::to_string(m_context.immutablesMemorySize())); + } + } + return t.render(); } @@ -1111,7 +1165,8 @@ void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionCon m_context.revertStrings(), m_context.sourceIndices(), m_context.debugInfoSelection(), - m_context.soliditySourceProvider() + m_context.soliditySourceProvider(), + m_context.immutableVariablesOffsetsInDataSection() ); m_context = std::move(newContext); diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index 60d5d25cd026..2a9d53b437a1 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -64,7 +64,8 @@ class IRGenerator _revertStrings, std::move(_sourceIndices), _debugInfoSelection, - _soliditySourceProvider + _soliditySourceProvider, + {} ), m_utils(_evmVersion, _eofVersion, m_context.revertStrings(), m_context.functionCollector()), m_optimiserSettings(_optimiserSettings) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 2efcb0fbff21..7164b1c328c6 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1561,23 +1561,38 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) &dynamic_cast(*functionType->returnParameterTypes().front()).contractDefinition(); m_context.addSubObject(contract); - Whiskers t(R"(let := () - let := add(, datasize("")) - if or(gt(, 0xffffffffffffffff), lt(, )) { () } - datacopy(, dataoffset(""), datasize("")) - := () - - let
:= create2(, , sub(, ), ) - - let
:= create(, , sub(, )) - + Whiskers t(R"( + + let := () + let := () + let := sub(, ) + let
:= eofcreate("", , , , ) + + let := () + let := add(, datasize("")) + if or(gt(, 0xffffffffffffffff), lt(, )) { () } + datacopy(, dataoffset(""), datasize("")) + let := () + + + let
:= create2(, , sub(, ), ) + + let
:= create(, , sub(, )) + + let := iszero(iszero(
)) if iszero(
) { () } )"); + t("eof", m_context.eofVersion().has_value()); + if (m_context.eofVersion().has_value()) + { + t("argSize", m_context.newYulVariable()); + } t("memPos", m_context.newYulVariable()); + t("argPos", m_context.newYulVariable()); t("memEnd", m_context.newYulVariable()); t("allocateUnbounded", m_utils.allocateUnboundedFunction()); t("object", IRNames::creationObject(*contract)); @@ -1590,6 +1605,8 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) t("saltSet", functionType->saltSet()); if (functionType->saltSet()) t("salt", IRVariable(_functionCall.expression()).part("salt").name()); + else if (m_context.eofVersion().has_value()) // Set salt to 0 if not defined. + t("salt", "0"); solAssert(IRVariable(_functionCall).stackSlots().size() == 1); t("address", IRVariable(_functionCall).commaSeparatedList()); t("isTryCall", _functionCall.annotation().tryCall); @@ -3198,7 +3215,12 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue) ")\n"; } else - define(result) << "loadimmutable(\"" << std::to_string(_immutable.variable->id()) << "\")\n"; + { + if (!m_context.eofVersion().has_value()) + define(result) << "loadimmutable(\"" << std::to_string(_immutable.variable->id()) << "\")\n"; + else + define(result) << "dataloadn(\"" << std::to_string(m_context.immutableVariableOffsetInDataSection(*_immutable.variable)) << "\")\n"; + } }, [&](IRLValue::Tuple const&) { solAssert(false, "Attempted to read from tuple lvalue."); diff --git a/libsolidity/experimental/codegen/IRGenerator.cpp b/libsolidity/experimental/codegen/IRGenerator.cpp index 40deb38c397e..0744282d4bdd 100644 --- a/libsolidity/experimental/codegen/IRGenerator.cpp +++ b/libsolidity/experimental/codegen/IRGenerator.cpp @@ -72,8 +72,12 @@ std::string IRGenerator::run( Whiskers t(R"( object "" { code { - codecopy(0, dataoffset(""), datasize("")) - return(0, datasize("")) + + returncontract("", 0, 0) + + codecopy(0, dataoffset(""), datasize("")) + return(0, datasize("")) + } object "" { code { @@ -82,6 +86,7 @@ std::string IRGenerator::run( } } )"); + t("eof", m_eofVersion.has_value()); t("CreationObject", IRNames::creationObject(_contract)); t("DeployedObject", IRNames::deployedObject(_contract)); t("code", generate(_contract)); diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 2e6f3281730a..04fa2ec120c6 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -57,6 +57,7 @@ class AbstractAssembly using LabelID = size_t; using SubID = size_t; using FunctionID = uint16_t; + using ContainerID = uint8_t; enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; virtual ~AbstractAssembly() = default; @@ -109,6 +110,9 @@ class AbstractAssembly virtual void appendFunctionCall(FunctionID _functionID, int _stackDiffAfter = 0) = 0; virtual void appendFunctionReturn() = 0; + virtual void appendEofCreateCall(ContainerID _containerID) = 0; + virtual void appendReturnContractCall(ContainerID _containerID) = 0; + /// Appends the offset of the given sub-assembly or data. virtual void appendDataOffset(std::vector const& _subPath) = 0; /// Appends the size of the given sub-assembly or data. diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 1163fa01783a..89dd464d5c44 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -204,6 +204,8 @@ std::map createBuiltins(langutil::EVMVersion _ev opcode != evmasm::Instruction::JUMP && opcode != evmasm::Instruction::JUMPI && opcode != evmasm::Instruction::JUMPDEST && + opcode != evmasm::Instruction::EOFCREATE && + opcode != evmasm::Instruction::RETURNCONTRACT && opcode != evmasm::Instruction::DATALOADN && _evmVersion.hasOpcode(opcode) && !prevRandaoException(name) @@ -307,6 +309,66 @@ std::map createBuiltins(langutil::EVMVersion _ev _assembly.appendInstruction(evmasm::Instruction::CODECOPY); } )); + builtins.emplace(createFunction( + "eofcreate", + 5, + 1, + SideEffects{ + false, // movable + false, // movableApartFromEffects + false, // canBeRemoved + false, // canBeRemovedIfNotMSize + true, // cannotLoop + SideEffects::Write, // otherState + SideEffects::Write, // storage + SideEffects::Write, // memory + SideEffects::Write // transientStorage + }, + {LiteralKind::String, std::nullopt, std::nullopt, std::nullopt, std::nullopt}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& context + ) { + yulAssert(_call.arguments.size() == 5, ""); + auto const it = context.subIDs.find(formatLiteral(std::get(_call.arguments.front()))); + if (it != context.subIDs.end()) + _assembly.appendEofCreateCall(static_cast((*it).second)); + } + )); + + { + auto [it, _] = builtins.emplace(createFunction( + "returncontract", + 3, + 0, + SideEffects{ + false, // movable + false, // movableApartFromEffects + false, // canBeRemoved + false, // canBeRemovedIfNotMSize + true, // cannotLoop + SideEffects::None, // otherState + SideEffects::None, // storage + SideEffects::Write, // memory + SideEffects::None // transientStorage + }, + {LiteralKind::String, std::nullopt, std::nullopt}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& context + ) { + yulAssert(_call.arguments.size() == 3, ""); + auto const it = context.subIDs.find(formatLiteral(std::get(_call.arguments.front()))); + if (it != context.subIDs.end()) + _assembly.appendReturnContractCall(static_cast((*it).second)); + } + )); + (*it).second.controlFlowSideEffects.canContinue = false; + (*it).second.controlFlowSideEffects.canTerminate = true; + (*it).second.controlFlowSideEffects.canRevert = false; + } builtins.emplace(createFunction( "setimmutable", 3, diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index 554b03ef23e1..dc7c3267a000 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -171,6 +171,16 @@ void EthAssemblyAdapter::appendFunctionCall(FunctionID _functionID, int _stackDi m_assembly.adjustDeposit(_stackDiffAfter); } +void EthAssemblyAdapter::appendEofCreateCall(ContainerID _containerID) +{ + m_assembly.appendEOFCreate(_containerID); +} + +void EthAssemblyAdapter::appendReturnContractCall(ContainerID _containerID) +{ + m_assembly.appendReturnContract(_containerID); +} + void EthAssemblyAdapter::appendDataOffset(std::vector const& _subPath) { if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index d1195cfa70a2..98fcc2a126f9 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -61,6 +61,8 @@ class EthAssemblyAdapter: public AbstractAssembly void endFunction() override; void appendFunctionCall(FunctionID _functionID, int _stackDiffAfter = 0) override; void appendFunctionReturn() override; + void appendEofCreateCall(ContainerID _containerID) override; + void appendReturnContractCall(ContainerID _containerID) override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 711e1242a679..18bf2c708c32 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -130,6 +130,16 @@ void NoOutputAssembly::appendFunctionReturn() m_stackHeight = 0; } +void NoOutputAssembly::appendEofCreateCall(ContainerID) +{ + // TODO: Implement + +} +void NoOutputAssembly::appendReturnContractCall(ContainerID) +{ + // TODO: Implement +} + void NoOutputAssembly::appendDataOffset(std::vector const&) { appendInstruction(evmasm::Instruction::PUSH1); diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 66a33e725c06..be4f9a2c57ea 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -78,6 +78,8 @@ class NoOutputAssembly: public AbstractAssembly void endFunction() override; void appendFunctionCall(FunctionID _functionID, int _stackDiffAfter = 0) override; void appendFunctionReturn() override; + void appendEofCreateCall(ContainerID) override; + void appendReturnContractCall(ContainerID) override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index c2908090ee71..d5451c4e6687 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -490,6 +490,8 @@ u256 EVMInstructionInterpreter::eval( // TODO: Not sure about it. case Instruction::DATALOAD: case Instruction::DATALOADN: + case Instruction::EOFCREATE: + case Instruction::RETURNCONTRACT: case Instruction::CALLF: case Instruction::RETF: case Instruction::JUMPF: From a4199082f0e89be9fc4fb4319186cc016705eb06 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 10:40:02 +0200 Subject: [PATCH 10/15] eof: Implement revamped CALL instructions (EIP-7069) --- libevmasm/Instruction.cpp | 6 + libevmasm/Instruction.h | 6 + libevmasm/SemanticInformation.cpp | 49 ++++++++ .../codegen/ir/IRGeneratorForStatements.cpp | 112 ++++++++++++++---- .../EVMInstructionInterpreter.cpp | 8 ++ 5 files changed, 158 insertions(+), 23 deletions(-) diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 03e1956fbe48..598c44221441 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -184,6 +184,9 @@ std::map const solidity::evmasm::c_instructions = { "STATICCALL", Instruction::STATICCALL }, { "RETURN", Instruction::RETURN }, { "DELEGATECALL", Instruction::DELEGATECALL }, + {"EXTCALL", Instruction::EXTCALL}, + { "EXTSTATICCALL", Instruction::EXTSTATICCALL }, + {"EXTDELEGATECALL", Instruction::EXTDELEGATECALL}, { "CREATE2", Instruction::CREATE2 }, { "REVERT", Instruction::REVERT }, { "INVALID", Instruction::INVALID }, @@ -348,6 +351,9 @@ static std::map const c_instructionInfo = {Instruction::RETURN, {"RETURN", 0, 2, 0, true, Tier::Zero}}, {Instruction::DELEGATECALL, {"DELEGATECALL", 0, 6, 1, true, Tier::Special}}, {Instruction::STATICCALL, {"STATICCALL", 0, 6, 1, true, Tier::Special}}, + {Instruction::EXTCALL, {"EXTCALL", 0, 4, 1, true, Tier::Special}}, + {Instruction::EXTDELEGATECALL,{"EXTDELEGATECALL", 0, 3, 1, true, Tier::Special}}, + {Instruction::EXTSTATICCALL, {"EXTSTATICCALL", 0, 3, 1, true, Tier::Special}}, {Instruction::CREATE2, {"CREATE2", 0, 4, 1, true, Tier::Special}}, {Instruction::REVERT, {"REVERT", 0, 2, 0, true, Tier::Zero}}, {Instruction::INVALID, {"INVALID", 0, 0, 0, true, Tier::Zero}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 9ae9f9ecf68d..d6e96ea1689e 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -202,7 +202,10 @@ enum class Instruction: uint8_t RETURN, ///< halt execution returning output data DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender CREATE2 = 0xf5, ///< create new account with associated code at address `sha3(0xff + sender + salt + init code) % 2**160` + EXTCALL = 0xf8, ///< EOF message-call into an account + EXTDELEGATECALL = 0xf9, ///< EOF delegate call STATICCALL = 0xfa, ///< like CALL but disallow state modifications + EXTSTATICCALL = 0xfb, ///< like EXTCALL but disallow state modifications REVERT = 0xfd, ///< halt execution, revert state and return output data INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) @@ -218,6 +221,9 @@ constexpr bool isCallInstruction(Instruction _inst) noexcept case Instruction::CALLCODE: case Instruction::DELEGATECALL: case Instruction::STATICCALL: + case Instruction::EXTCALL: + case Instruction::EXTSTATICCALL: + case Instruction::EXTDELEGATECALL: return true; default: return false; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index bc3f04b45b3e..d7921cf46ac1 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -169,6 +169,39 @@ std::vector SemanticInformation::readWriteOperat }); return operations; } + case Instruction::EXTSTATICCALL: + case Instruction::EXTDELEGATECALL: + { + size_t paramCount = static_cast(instructionInfo(_instruction, langutil::EVMVersion()).args); + std::vector operations{ + Operation{Location::Memory, Effect::Read, paramCount - 2, paramCount - 1, {}}, + Operation{Location::Storage, Effect::Read, {}, {}, {}}, + Operation{Location::TransientStorage, Effect::Read, {}, {}, {}} + }; + if (_instruction != Instruction::EXTSTATICCALL) + { + operations.emplace_back(Operation{Location::Storage, Effect::Write, {}, {}, {}}); + operations.emplace_back(Operation{Location::TransientStorage, Effect::Write, {}, {}, {}}); + } + + return operations; + } + case Instruction::EXTCALL: + { + size_t paramCount = static_cast(instructionInfo(_instruction, langutil::EVMVersion()).args); + std::vector operations{ + Operation{Location::Memory, Effect::Read, paramCount - 3, paramCount - 2, {}}, + Operation{Location::Storage, Effect::Read, {}, {}, {}}, + Operation{Location::TransientStorage, Effect::Read, {}, {}, {}} + }; + if (_instruction != Instruction::EXTSTATICCALL) + { + operations.emplace_back(Operation{Location::Storage, Effect::Write, {}, {}, {}}); + operations.emplace_back(Operation{Location::TransientStorage, Effect::Write, {}, {}, {}}); + } + + return operations; + } case Instruction::CREATE: case Instruction::CREATE2: return std::vector{ @@ -357,6 +390,9 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) case Instruction::CALLCODE: case Instruction::DELEGATECALL: case Instruction::STATICCALL: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: + case Instruction::EXTSTATICCALL: case Instruction::CREATE: case Instruction::CREATE2: case Instruction::GAS: @@ -433,6 +469,9 @@ SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction case Instruction::CALLCODE: case Instruction::DELEGATECALL: case Instruction::STATICCALL: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: + case Instruction::EXTSTATICCALL: return SemanticInformation::Write; case Instruction::CREATE: @@ -485,10 +524,13 @@ SemanticInformation::Effect SemanticInformation::storage(Instruction _instructio case Instruction::CREATE2: case Instruction::SSTORE: case Instruction::EOFCREATE: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: return SemanticInformation::Write; case Instruction::SLOAD: case Instruction::STATICCALL: + case Instruction::EXTSTATICCALL: return SemanticInformation::Read; default: @@ -507,10 +549,13 @@ SemanticInformation::Effect SemanticInformation::transientStorage(Instruction _i case Instruction::CREATE2: case Instruction::TSTORE: case Instruction::EOFCREATE: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: return SemanticInformation::Write; case Instruction::TLOAD: case Instruction::STATICCALL: + case Instruction::EXTSTATICCALL: return SemanticInformation::Read; default: @@ -528,6 +573,8 @@ SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruc case Instruction::CREATE: case Instruction::CREATE2: case Instruction::EOFCREATE: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: case Instruction::SELFDESTRUCT: case Instruction::STATICCALL: // because it can affect returndatasize // Strictly speaking, log0, .., log4 writes to the state, but the EVM cannot read it, so they @@ -602,6 +649,8 @@ bool SemanticInformation::invalidInViewFunctions(Instruction _instruction) case Instruction::CALL: case Instruction::CALLCODE: case Instruction::DELEGATECALL: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: case Instruction::EOFCREATE: case Instruction::CREATE2: case Instruction::SELFDESTRUCT: diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 7164b1c328c6..1e2dd29cb716 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1627,11 +1627,17 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) Whiskers templ(R"( let := 0 if iszero() { := } - let := call(,
, , 0, 0, 0, 0) + + let := extcall(
, 0, 0, ) + := iszero() + + let := call(,
, , 0, 0, 0, 0) + if iszero() { () } )"); + templ("eof", m_context.eofVersion().has_value()); templ("gas", m_context.newYulVariable()); templ("callStipend", toString(evmasm::GasCosts::callStipend)); templ("address", address); @@ -1674,17 +1680,31 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) mstore(0, 0) - let := (,
, 0, , sub(, ), 0, 32) + + let := (
, , sub(, ) , 0) + := iszero() + + let := (,
, 0, , sub(, ), 0, 32) + if iszero() { () } + + if eq(returndatasize(), 32) { returndatacopy(0, 0, 32) } + let := (mload(0)) )"); - templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call"); + auto const eof = m_context.eofVersion().has_value(); + + if (!eof) + templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call"); + else + templ("call", m_context.evmVersion().hasStaticCall() ? "extstaticcall" : "extcall"); templ("isCall", !m_context.evmVersion().hasStaticCall()); templ("shl", m_utils.shiftLeftFunction(offset * 8)); templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); templ("pos", m_context.newYulVariable()); templ("end", m_context.newYulVariable()); templ("isECRecover", FunctionType::Kind::ECRecover == functionType->kind()); + templ("eof", eof); if (FunctionType::Kind::ECRecover == functionType->kind()) templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, parameterTypes)); else @@ -1849,7 +1869,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) solAssert(dynamic_cast(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable); define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression()); } - else if (std::set{"call", "callcode", "delegatecall", "staticcall"}.count(member)) + else if (std::set{"call", "extcall", "callcode", "delegatecall", "staticcall"}.count(member)) define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression()); else solAssert(false, "Invalid member access to address"); @@ -2606,6 +2626,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall( bool const isDelegateCall = funKind == FunctionType::Kind::DelegateCall; bool const useStaticCall = funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall(); + auto const eof = m_context.eofVersion().has_value(); + ReturnInfo const returnInfo{m_context.evmVersion(), funType}; TypePointers parameterTypes = funType.parameterTypes(); @@ -2636,7 +2658,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( } // NOTE: When the expected size of returndata is static, we pass that in to the call opcode and it gets copied automatically. - // When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy(). + // When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy(). Whiskers templ(R"( if iszero(extcodesize(
)) { () } @@ -2646,7 +2668,12 @@ void IRGeneratorForStatements::appendExternalFunctionCall( mstore(, ()) let := (add(, 4) ) - let := (,
, , , sub(, ), , ) + + let := (
, , sub(, ) , ) + := iszero() + + let := (,
, , , sub(, ), , ) + if iszero() { () } @@ -2661,6 +2688,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall( if gt(, returndatasize()) { := returndatasize() } + + returndatacopy(, 0, ) + @@ -2672,16 +2702,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall( } )"); templ("revertNoCode", m_utils.revertReasonIfDebugFunction("Target contract does not contain code")); + templ("eof", eof); // We do not need to check extcodesize if we expect return data: If there is no // code, the call will return empty data and the ABI decoder will revert. size_t encodedHeadSize = 0; for (auto const& t: returnInfo.returnTypes) encodedHeadSize += t->decodingType()->calldataHeadSize(); - bool const checkExtcodesize = - encodedHeadSize == 0 || + bool const checkExtcodesize = (!m_context.eofVersion().has_value()) && + (encodedHeadSize == 0 || !m_context.evmVersion().supportsReturndata() || - m_context.revertStrings() >= RevertStrings::Debug; + m_context.revertStrings() >= RevertStrings::Debug); + templ("checkExtcodesize", checkExtcodesize); templ("pos", m_context.newYulVariable()); @@ -2741,12 +2773,25 @@ void IRGeneratorForStatements::appendExternalFunctionCall( templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); } // Order is important here, STATICCALL might overlap with DELEGATECALL. - if (isDelegateCall) - templ("call", "delegatecall"); - else if (useStaticCall) - templ("call", "staticcall"); + if (!eof) + { + if (isDelegateCall) + templ("call", "delegatecall"); + else if (useStaticCall) + templ("call", "staticcall"); + else + templ("call", "call"); + } else - templ("call", "call"); + { + if (isDelegateCall) + templ("call", "extdelegatecall"); + else if (useStaticCall) + templ("call", "extstaticcall"); + else + templ("call", "extcall"); + } + templ("forwardingRevert", m_utils.forwardingRevertFunction()); @@ -2761,9 +2806,9 @@ void IRGeneratorForStatements::appendBareCall( FunctionType const& funType = dynamic_cast(type(_functionCall.expression())); solAssert( !funType.hasBoundFirstArgument() && - !funType.takesArbitraryParameters() && - _arguments.size() == 1 && - funType.parameterTypes().size() == 1, "" + !funType.takesArbitraryParameters() && + _arguments.size() == 1 && + funType.parameterTypes().size() == 1, "" ); FunctionType::Kind const funKind = funType.kind(); @@ -2771,8 +2816,8 @@ void IRGeneratorForStatements::appendBareCall( solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); solAssert( funKind == FunctionType::Kind::BareCall || - funKind == FunctionType::Kind::BareDelegateCall || - funKind == FunctionType::Kind::BareStaticCall, "" + funKind == FunctionType::Kind::BareDelegateCall || + funKind == FunctionType::Kind::BareStaticCall, "" ); solAssert(!_functionCall.annotation().tryCall); @@ -2785,13 +2830,21 @@ void IRGeneratorForStatements::appendBareCall( let := mload() - let := (,
, , , , 0, 0) + + let := (
, , , ) + := iszero() + + let := (,
, , , , 0, 0) + + let := () )"); templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); templ("pos", m_context.newYulVariable()); templ("length", m_context.newYulVariable()); + auto const eof = m_context.eofVersion().has_value(); + templ("eof", eof); templ("arg", IRVariable(*_arguments.front()).commaSeparatedList()); Type const& argType = type(*_arguments.front()); @@ -2813,16 +2866,29 @@ void IRGeneratorForStatements::appendBareCall( if (funKind == FunctionType::Kind::BareCall) { templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0"); - templ("call", "call"); + if (eof) + templ("call", "extcall"); + else + templ("call", "call"); } else { solAssert(!funType.valueSet(), "Value set for delegatecall or staticcall."); templ("value", ""); if (funKind == FunctionType::Kind::BareStaticCall) - templ("call", "staticcall"); + { + if (eof) + templ("call", "extstaticcall"); + else + templ("call", "staticcall"); + } else - templ("call", "delegatecall"); + { + if (eof) + templ("call", "extdelegatecall"); + else + templ("call", "delegatecall"); + } } if (funType.gasSet()) diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index d5451c4e6687..f460cc264296 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -390,6 +390,14 @@ u256 EVMInstructionInterpreter::eval( (arg[0] > 0) && (arg[1] == util::h160::Arith(m_state.address) || (arg[1] & 1)) ) ? 1 : 0; + case Instruction::EXTCALL: + case Instruction::EXTSTATICCALL: + case Instruction::EXTDELEGATECALL: + accessMemory(arg[1], arg[2]); + logTrace(_instruction, arg); + return ( + (arg[0] == util::h160::Arith(m_state.address) || (arg[0] & 1)) + ) ? 1 : 0; case Instruction::RETURN: { m_state.returndata = {}; From 79b4d65e61e4db2918509a42cfbaa48507954da2 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 23 Jul 2024 12:47:09 +0200 Subject: [PATCH 11/15] eof: Implement max stack heigh calculation (EIP-5450) Co-authored-by: Daniel Kirchner --- libevmasm/Assembly.cpp | 64 +++++++++++++++++++++++++++++++++++++- libevmasm/AssemblyItem.cpp | 14 +++++++++ libevmasm/AssemblyItem.h | 1 + 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 143e6c3a3392..3c7b8772325f 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -46,6 +46,7 @@ #include #include #include +#include using namespace solidity; using namespace solidity::evmasm; @@ -886,6 +887,67 @@ std::map const& Assembly::optimiseInternal( return *m_tagReplacements; } +namespace +{ +uint16_t calculateMaxStackHeight(std::vector const& _items, uint16_t _args) +{ + static auto constexpr LOC_UNVISITED = std::numeric_limits::max(); + + uint16_t maxStackHeight = _args; + std::stack worklist; + std::vector stack_heights(_items.size(), LOC_UNVISITED); + stack_heights[0] = _args; + worklist.push(0u); + while (!worklist.empty()) + { + size_t i = worklist.top(); + worklist.pop(); + AssemblyItem const& item = _items.at(i); + size_t stack_height_change = item.deposit(); + size_t stackHeight = stack_heights.at(i); + assertThrow(stackHeight != LOC_UNVISITED, AssemblyException, ""); + + std::vector successors; + + if ( + item.type() != RelativeJump && + !(item.type() == Operation && SemanticInformation::terminatesControlFlow(item.instruction())) && + item.type() != RetF && + item.type() != ReturnContract && + item.type() != JumpF + ) + { + assertThrow(i < _items.size() - 1, AssemblyException, "No terminating instruction."); + successors.emplace_back(i + 1); + } + + if (item.type() == RelativeJump || item.type() == ConditionalRelativeJump) + { + auto it = std::find(_items.begin(), _items.end(), item.tag()); + assertThrow(it != _items.end(), AssemblyException, "Tag not found."); + successors.emplace_back(static_cast(std::distance(_items.begin(), it))); + } + + assertThrow(stackHeight + item.maxStackHeightDelta() <= std::numeric_limits::max(), AssemblyException, "Invalid stack height"); + maxStackHeight = std::max(maxStackHeight, static_cast(stackHeight + item.maxStackHeightDelta())); + stackHeight += stack_height_change; + + for (size_t s: successors) + { + solAssert(s < stack_heights.size()); + if (stack_heights.at(s) == LOC_UNVISITED) + { + stack_heights[s] = stackHeight; + worklist.push(s); + } + else + assertThrow(stack_heights.at(s) == stackHeight, AssemblyException, "Stack height mismatch."); + } + } + return maxStackHeight; +} +} + LinkerObject const& Assembly::assemble() const { assertThrow(!m_invalid, AssemblyException, "Attempted to assemble invalid Assembly object."); @@ -1036,7 +1098,7 @@ LinkerObject const& Assembly::assemble() const { ret.bytecode.push_back(codeSection.inputs); ret.bytecode.push_back(codeSection.outputs); - appendBigEndianUint16(ret.bytecode, 0xFFFFu); // TODO: Add stack heigh calculation + appendBigEndianUint16(ret.bytecode, calculateMaxStackHeight(codeSection.items, codeSection.inputs)); } } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 3898033dec63..49b009ead3f7 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -116,6 +116,20 @@ void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) setData(data); } +size_t AssemblyItem::maxStackHeightDelta() const +{ + if (m_type == AssignImmutable) + { + assertThrow(m_immutableOccurrences.has_value(), util::Exception, ""); + if (*m_immutableOccurrences == 0) + return 0; + else + return (*m_immutableOccurrences - 1) * 2 + 1; + } + else + return deposit(); +} + size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _evmVersion, Precision _precision) const { switch (m_type) diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index f8be897f69a7..f437dcf453f1 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -211,6 +211,7 @@ class AssemblyItem size_t arguments() const; size_t returnValues() const; size_t deposit() const { return returnValues() - arguments(); } + size_t maxStackHeightDelta() const; /// @returns true if the assembly item can be used in a functional context. bool canBeFunctional() const; From bdc126f3cfe847d5e20bfb7835c28de47acdb234 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 23 Jul 2024 16:03:18 +0200 Subject: [PATCH 12/15] prep: Update to emvc 12 --- test/evmc/evmc.h | 53 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/test/evmc/evmc.h b/test/evmc/evmc.h index 571c97f474e0..c8eebac6283c 100644 --- a/test/evmc/evmc.h +++ b/test/evmc/evmc.h @@ -44,7 +44,7 @@ enum * * @see @ref versioning */ - EVMC_ABI_VERSION = 11 + EVMC_ABI_VERSION = 12 }; @@ -79,7 +79,8 @@ enum evmc_call_kind The value param ignored. */ EVMC_CALLCODE = 2, /**< Request CALLCODE. */ EVMC_CREATE = 3, /**< Request CREATE. */ - EVMC_CREATE2 = 4 /**< Request CREATE2. Valid since Constantinople.*/ + EVMC_CREATE2 = 4, /**< Request CREATE2. Valid since Constantinople.*/ + EVMC_EOFCREATE = 5 /**< Request EOFCREATE. Valid since Prague.*/ }; /** The flags for ::evmc_message. */ @@ -168,7 +169,8 @@ struct evmc_message /** * The optional value used in new contract address construction. * - * Needed only for a Host to calculate created address when kind is ::EVMC_CREATE2. + * Needed only for a Host to calculate created address when kind is ::EVMC_CREATE2 + * or ::EVMC_EOFCREATE. * Ignored in evmc_execute_fn(). */ evmc_bytes32 create2_salt; @@ -179,7 +181,7 @@ struct evmc_message * For ::EVMC_CALLCODE or ::EVMC_DELEGATECALL this may be different from * the evmc_message::recipient. * Not required when invoking evmc_execute_fn(), only when invoking evmc_call_fn(). - * Ignored if kind is ::EVMC_CREATE or ::EVMC_CREATE2. + * Ignored if kind is ::EVMC_CREATE or ::EVMC_CREATE2 or ::EVMC_EOFCREATE. * * In case of ::EVMC_CAPABILITY_PRECOMPILES implementation, this fields should be inspected * to identify the requested precompile. @@ -187,24 +189,43 @@ struct evmc_message * Defined as `c` in the Yellow Paper. */ evmc_address code_address; + + /** + * The code to be executed. + */ + const uint8_t* code; + + /** + * The length of the code to be executed. + */ + size_t code_size; }; +/** The hashed initcode used for TXCREATE instruction. */ +typedef struct evmc_tx_initcode +{ + evmc_bytes32 hash; /**< The initcode hash. */ + const uint8_t* code; /**< The code. */ + size_t code_size; /**< The length of the code. */ +} evmc_tx_initcode; /** The transaction and block data for execution. */ struct evmc_tx_context { - evmc_uint256be tx_gas_price; /**< The transaction gas price. */ - evmc_address tx_origin; /**< The transaction origin account. */ - evmc_address block_coinbase; /**< The miner of the block. */ - int64_t block_number; /**< The block number. */ - int64_t block_timestamp; /**< The block timestamp. */ - int64_t block_gas_limit; /**< The block gas limit. */ - evmc_uint256be block_prev_randao; /**< The block previous RANDAO (EIP-4399). */ - evmc_uint256be chain_id; /**< The blockchain's ChainID. */ - evmc_uint256be block_base_fee; /**< The block base fee per gas (EIP-1559, EIP-3198). */ - evmc_uint256be blob_base_fee; /**< The blob base fee (EIP-7516). */ - const evmc_bytes32* blob_hashes; /**< The array of blob hashes (EIP-4844). */ - size_t blob_hashes_count; /**< The number of blob hashes (EIP-4844). */ + evmc_uint256be tx_gas_price; /**< The transaction gas price. */ + evmc_address tx_origin; /**< The transaction origin account. */ + evmc_address block_coinbase; /**< The miner of the block. */ + int64_t block_number; /**< The block number. */ + int64_t block_timestamp; /**< The block timestamp. */ + int64_t block_gas_limit; /**< The block gas limit. */ + evmc_uint256be block_prev_randao; /**< The block previous RANDAO (EIP-4399). */ + evmc_uint256be chain_id; /**< The blockchain's ChainID. */ + evmc_uint256be block_base_fee; /**< The block base fee per gas (EIP-1559, EIP-3198). */ + evmc_uint256be blob_base_fee; /**< The blob base fee (EIP-7516). */ + const evmc_bytes32* blob_hashes; /**< The array of blob hashes (EIP-4844). */ + size_t blob_hashes_count; /**< The number of blob hashes (EIP-4844). */ + const evmc_tx_initcode* initcodes; /**< The array of transaction initcodes (TXCREATE). */ + size_t initcodes_count; /**< The number of transaction initcodes (TXCREATE). */ }; /** From 00b0b146a920b2550f1ad8d77fabe68919081701 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 10:28:37 +0200 Subject: [PATCH 13/15] tests: Add new (EOF) message kind to EVMHost. Separate arguments in sendMessage Co-authored-by: Rodrigo Q. Saramago --- test/EVMHost.cpp | 17 ++++++++++++----- test/ExecutionFramework.cpp | 18 +++++++++++++----- test/ExecutionFramework.h | 8 ++++---- test/contracts/AuctionRegistrar.cpp | 2 +- test/contracts/Wallet.cpp | 2 +- test/libsolidity/GasMeter.cpp | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 4 ++-- test/libsolidity/SolidityExecutionFramework.h | 2 +- 8 files changed, 35 insertions(+), 20 deletions(-) diff --git a/test/EVMHost.cpp b/test/EVMHost.cpp index 5626f8d436e7..913e71661907 100644 --- a/test/EVMHost.cpp +++ b/test/EVMHost.cpp @@ -58,6 +58,7 @@ evmc::VM& EVMHost::getVM(std::string const& _path) std::cerr << ":" << std::endl << errorMsg; std::cerr << std::endl; } + vms[_path]->set_option("validate_eof", "1"); } if (vms.count(_path) > 0) @@ -332,13 +333,15 @@ evmc::Result EVMHost::call(evmc_message const& _message) noexcept code = evmc::bytes(message.input_data, message.input_data + message.input_size); } - else if (message.kind == EVMC_CREATE2) + else if (message.kind == EVMC_CREATE2 || message.kind == EVMC_EOFCREATE) { h160 createAddress(keccak256( bytes{0xff} + bytes(std::begin(message.sender.bytes), std::end(message.sender.bytes)) + bytes(std::begin(message.create2_salt.bytes), std::end(message.create2_salt.bytes)) + - keccak256(bytes(message.input_data, message.input_data + message.input_size)).asBytes() + keccak256( + message.kind == EVMC_CREATE2 ? bytes(message.input_data, message.input_data + message.input_size) : + bytes(message.code, message.code + message.code_size)).asBytes() ), h160::AlignRight); message.recipient = convertToEVMC(createAddress); @@ -353,13 +356,16 @@ evmc::Result EVMHost::call(evmc_message const& _message) noexcept return result; } - code = evmc::bytes(message.input_data, message.input_data + message.input_size); + if (message.kind == EVMC_CREATE2) + code = evmc::bytes(message.input_data, message.input_data + message.input_size); + else // EOFCREATE + code = evmc::bytes(message.code, message.code + message.code_size); } else code = accounts[message.code_address].code; auto& destination = accounts[message.recipient]; - if (message.kind == EVMC_CREATE || message.kind == EVMC_CREATE2) + if (message.kind == EVMC_CREATE || message.kind == EVMC_CREATE2 || message.kind == EVMC_EOFCREATE) // Mark account as created if it is a CREATE or CREATE2 call // TODO: Should we roll changes back on failure like we do for `accounts`? m_newlyCreatedAccounts.emplace(message.recipient); @@ -396,7 +402,7 @@ evmc::Result EVMHost::call(evmc_message const& _message) noexcept } evmc::Result result = m_vm.execute(*this, m_evmRevision, message, code.data(), code.size()); - if (message.kind == EVMC_CREATE || message.kind == EVMC_CREATE2) + if (message.kind == EVMC_CREATE || message.kind == EVMC_CREATE2 || message.kind == EVMC_EOFCREATE) { int64_t codeDepositGas = static_cast(evmasm::GasCosts::createDataGas * result.output_size); result.gas_left -= codeDepositGas; @@ -409,6 +415,7 @@ evmc::Result EVMHost::call(evmc_message const& _message) noexcept } else { + // TODO: Add proper codehash calculation for EOF. m_totalCodeDepositGas += codeDepositGas; result.create_address = message.recipient; destination.code = evmc::bytes(result.output_data, result.output_data + result.output_size); diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index 0081ba4a9d27..327f17d9c385 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -145,10 +145,13 @@ u256 ExecutionFramework::blockNumber() const return m_evmcHost->tx_context.block_number; } -void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256 const& _value) +void ExecutionFramework::sendMessage(bytes const& _bytecode, bytes const& _arguments, bool _isCreation, u256 const& _value) { + auto const eof = _bytecode.size() > 1 && _bytecode[0] == 0xef && _bytecode[1] == 0x00; m_evmcHost->newBlock(); + auto const _data = _bytecode + _arguments; + if (m_showMessages) { if (_isCreation) @@ -157,17 +160,22 @@ void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256 std::cout << "CALL " << m_sender.hex() << " -> " << m_contractAddress.hex() << ":" << std::endl; if (_value > 0) std::cout << " value: " << _value << std::endl; - std::cout << " in: " << util::toHex(_data) << std::endl; + std::cout << " in: " << util::toHex(_bytecode + _arguments) << std::endl; } evmc_message message{}; - message.input_data = _data.data(); - message.input_size = _data.size(); + message.input_data = eof ? _arguments.data() : _data.data(); + message.input_size = eof ? _arguments.size() : _data.size(); + if (eof) + { + message.code = _bytecode.data(); + message.code_size = _bytecode.size(); + } message.sender = EVMHost::convertToEVMC(m_sender); message.value = EVMHost::convertToEVMC(_value); if (_isCreation) { - message.kind = EVMC_CREATE; + message.kind = eof ? EVMC_EOFCREATE : EVMC_CREATE; message.recipient = {}; message.code_address = {}; } diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 5d66ea10d042..44812e230ab0 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -90,7 +90,7 @@ class ExecutionFramework bytes const& callFallbackWithValue(u256 const& _value) { - sendMessage(bytes(), false, _value); + sendMessage(bytes(), bytes(), false, _value); return m_output; } @@ -101,13 +101,13 @@ class ExecutionFramework bytes const& callLowLevel(bytes const& _data, u256 const& _value) { - sendMessage(_data, false, _value); + sendMessage(_data, bytes(), false, _value); return m_output; } bytes const& callContractFunctionWithValueNoEncoding(std::string _sig, u256 const& _value, bytes const& _arguments) { - sendMessage(util::selectorFromSignatureH32(_sig).asBytes() + _arguments, false, _value); + sendMessage(util::selectorFromSignatureH32(_sig).asBytes(), _arguments, false, _value); return m_output; } @@ -277,7 +277,7 @@ class ExecutionFramework void selectVM(evmc_capabilities _cap = evmc_capabilities::EVMC_CAPABILITY_EVM1); void reset(); - void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0); + void sendMessage(bytes const& _bytecode, bytes const& _argument, bool _isCreation, u256 const& _value = 0); void sendEther(util::h160 const& _to, u256 const& _value); size_t currentTimestamp(); size_t blockTimestamp(u256 _number); diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index 8d0b1ccb640d..c686963df317 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -225,7 +225,7 @@ class AuctionRegistrarTestFramework: public SolidityExecutionFramework return compileContract(registrarCode, "GlobalRegistrar"); }); - sendMessage(compiled, true); + sendMessage(compiled, bytes(), true); BOOST_REQUIRE(m_transactionSuccessful); BOOST_REQUIRE(!m_output.empty()); } diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index db9119deebeb..6e8670e14e0a 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -454,7 +454,7 @@ class WalletTestFramework: public SolidityExecutionFramework }); bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); - sendMessage(compiled + args, true, _value); + sendMessage(compiled, args, true, _value); BOOST_REQUIRE(m_transactionSuccessful); BOOST_REQUIRE(!m_output.empty()); } diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 1afebac5c5c8..9012ad083c02 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -76,7 +76,7 @@ class GasMeterTestFramework: public SolidityExecutionFramework util::FixedHash<4> hash = util::selectorFromSignatureH32(_sig); for (bytes const& arguments: _argumentVariants) { - sendMessage(hash.asBytes() + arguments, false, 0); + sendMessage(hash.asBytes(), arguments, false, 0); BOOST_CHECK(m_transactionSuccessful); gasUsed = std::max(gasUsed, m_gasUsed); gas = std::max(gas, gasForTransaction(hash.asBytes() + arguments, false)); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index e321a5298603..76699c2e3b6c 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1374,7 +1374,7 @@ BOOST_AUTO_TEST_CASE(bytes_from_calldata_to_memory) ALSO_VIA_YUL( compileAndRun(sourceCode); bytes calldata1 = util::selectorFromSignatureH32("f()").asBytes() + bytes(61, 0x22) + bytes(12, 0x12); - sendMessage(calldata1, false); + sendMessage(calldata1, bytes(), false); BOOST_CHECK(m_transactionSuccessful); BOOST_CHECK(m_output == encodeArgs(util::keccak256(bytes{'a', 'b', 'c'} + calldata1))); ); @@ -1474,7 +1474,7 @@ BOOST_AUTO_TEST_CASE(copy_from_calldata_removes_bytes_data) compileAndRun(sourceCode); ABI_CHECK(callContractFunction("set()", 1, 2, 3, 4, 5), encodeArgs(true)); BOOST_CHECK(!storageEmpty(m_contractAddress)); - sendMessage(bytes(), false); + sendMessage(bytes(), bytes(), false); BOOST_CHECK(m_transactionSuccessful); BOOST_CHECK(m_output.empty()); BOOST_CHECK(storageEmpty(m_contractAddress)); diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index f91ba758f08b..4caa4aa1e1a4 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -62,7 +62,7 @@ class SolidityExecutionFramework: public solidity::test::ExecutionFramework ) override { bytes bytecode = multiSourceCompileContract(_sourceCode, _sourceName, _contractName, _libraryAddresses); - sendMessage(bytecode + _arguments, true, _value); + sendMessage(bytecode, _arguments, true, _value); return m_output; } From a902291216d8adf6c4d47dd39263946ad953d068 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 23 Jul 2024 16:01:00 +0200 Subject: [PATCH 14/15] tests: Add `compileToEOF` flag to semantic tests settings --- test/libsolidity/SemanticTest.cpp | 138 ++++++++++++++++++++++++++---- test/libsolidity/SemanticTest.h | 7 +- 2 files changed, 125 insertions(+), 20 deletions(-) diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index b0bdb95cb9c4..6d3b838ac45a 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -77,6 +77,9 @@ SemanticTest::SemanticTest( static std::set const yulRunTriggers{"also", "true"}; static std::set const legacyRunTriggers{"also", "false", "default"}; + static std::set const EOFRunAllowedValues{"also", "true", "false"}; + static std::set const EOFRunTriggers{"also", "true"}; + m_requiresYulOptimizer = m_reader.enumSetting( "requiresYulOptimizer", { @@ -99,8 +102,24 @@ SemanticTest::SemanticTest( )); if (!util::contains(compileViaYulAllowedValues, compileViaYul)) BOOST_THROW_EXCEPTION(std::runtime_error("Invalid compileViaYul value: " + compileViaYul + ".")); - m_testCaseWantsYulRun = util::contains(yulRunTriggers, compileViaYul); - m_testCaseWantsLegacyRun = util::contains(legacyRunTriggers, compileViaYul); + + // If viaYul is explicitly set to false default value for compileToEOF is also false. + std::string compileToEOF = m_reader.stringSetting("compileToEOF", compileViaYul == "false" ? "false" : "also"); + if (compileToEOF == "true" && compileViaYul == "false") + BOOST_THROW_EXCEPTION(std::runtime_error( + "EOF tests can only be run via yul, " + "so they cannot specify ``compileViaYul: false``" + )); + if (!util::contains(EOFRunAllowedValues, compileToEOF)) + BOOST_THROW_EXCEPTION(std::runtime_error("Invalid compileToEOF value: " + compileToEOF + ".")); + + if (compileToEOF == "true" && _evmVersion < langutil::EVMVersion::prague() && shouldRun()) + BOOST_THROW_EXCEPTION(std::runtime_error("Compilation to EOF is only possible since prague")); + + // If compileToEOF flag is explicitly set to true we run EOF via yul only. + m_testCaseWantsYulRun = util::contains(yulRunTriggers, compileViaYul) && compileToEOF != "true"; + m_testCaseWantsLegacyRun = util::contains(legacyRunTriggers, compileViaYul) && compileToEOF != "true"; + m_testCaseWantsEOFRun = util::contains(EOFRunTriggers, compileToEOF); auto revertStrings = revertStringsFromString(m_reader.stringSetting("revertStrings", "default")); soltestAssert(revertStrings, "Invalid revertStrings setting."); @@ -316,15 +335,25 @@ TestCase::TestResult SemanticTest::run(std::ostream& _stream, std::string const& { TestResult result = TestResult::Success; - if (m_testCaseWantsLegacyRun && !m_eofVersion.has_value()) - result = runTest(_stream, _linePrefix, _formatted, false /* _isYulRun */); + if (m_testCaseWantsLegacyRun) + result = runTest(_stream, _linePrefix, _formatted, false /* _isYulRun */, false /* _isEOFRun */); + if (m_testCaseWantsYulRun && result == TestResult::Success) { if (solidity::test::CommonOptions::get().optimize) - result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */); + result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */, false /* _isEOFRun */); else - result = tryRunTestWithYulOptimizer(_stream, _linePrefix, _formatted); + result = tryRunTestWithYulOptimizer(_stream, _linePrefix, _formatted, false); + } + + // Run EOF test only for prage and later forks. + if (m_evmVersion >= langutil::EVMVersion::prague() && m_testCaseWantsEOFRun && result == TestResult::Success) + { + if (solidity::test::CommonOptions::get().optimize) + result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */, true /* _isEOFRun */); + else + result = tryRunTestWithYulOptimizer(_stream, _linePrefix, _formatted, true); } if (result != TestResult::Success) @@ -341,7 +370,8 @@ TestCase::TestResult SemanticTest::runTest( std::ostream& _stream, std::string const& _linePrefix, bool _formatted, - bool _isYulRun + bool _isYulRun, + bool _isEOFRun ) { bool success = true; @@ -353,8 +383,15 @@ TestCase::TestResult SemanticTest::runTest( m_compileViaYul = _isYulRun; + // TODO: Add support for more EOF versions. + if (_isEOFRun) + m_eofVersion = 1; + if (_isYulRun) - AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running via Yul: " << std::endl; + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running via Yul" + << (_isEOFRun ? "(EOF)" : "") << ": "; + else + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running legacy: "; for (TestFunctionCall& test: m_tests) test.reset(); @@ -470,6 +507,7 @@ TestCase::TestResult SemanticTest::runTest( if (!success) { + _stream << std::endl; AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << std::endl; for (TestFunctionCall const& test: m_tests) { @@ -500,26 +538,90 @@ TestCase::TestResult SemanticTest::runTest( AnsiColorized(_stream, _formatted, {BOLD, RED}) << _linePrefix << std::endl << _linePrefix << "Attention: Updates on the test will apply the detected format displayed." << std::endl; - if (_isYulRun && m_testCaseWantsLegacyRun) + + if (m_testCaseWantsLegacyRun && m_testCaseWantsYulRun && !m_testCaseWantsEOFRun) { - _stream << _linePrefix << std::endl << _linePrefix; - AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) << "Note that the test passed without Yul."; - _stream << std::endl; + if (_isYulRun) + { + _stream << _linePrefix << std::endl << _linePrefix; + AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) << "Note that the test passed without Yul."; + _stream << std::endl; + } + else + { + AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) + << _linePrefix << std::endl + << _linePrefix << "Note that the test also has to pass via Yul(non-EOF)." << std::endl; + } } - else if (!_isYulRun && m_testCaseWantsYulRun) - AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) - << _linePrefix << std::endl - << _linePrefix << "Note that the test also has to pass via Yul." << std::endl; + else if (!m_testCaseWantsLegacyRun && m_testCaseWantsYulRun && m_testCaseWantsEOFRun) + { + if (_isEOFRun) + { + _stream << _linePrefix << std::endl << _linePrefix; + AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) + << "Note that the test passed with Yul without EOF."; + _stream << std::endl; + } + else + { + AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) + << _linePrefix << std::endl + << _linePrefix << "Note that the test also has to pass via Yul(EOF)." << std::endl; + } + } + else if (m_testCaseWantsLegacyRun && !m_testCaseWantsYulRun && m_testCaseWantsEOFRun) + { + if (_isEOFRun) + { + _stream << _linePrefix << std::endl << _linePrefix; + AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) + << "Note that the test passed without Yul."; + _stream << std::endl; + } + else + { + AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) + << _linePrefix << std::endl + << _linePrefix << "Note that the test also has to pass via Yul(EOF)." << std::endl; + } + } + else if (m_testCaseWantsLegacyRun && m_testCaseWantsYulRun && m_testCaseWantsEOFRun) + { + if (!_isEOFRun && !_isYulRun) + { + AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) + << _linePrefix << std::endl + << _linePrefix << "Note that the test also has to pass via Yul and Yul(EOF)." << std::endl; + } + else if (!_isEOFRun && _isYulRun) + { + _stream << _linePrefix << std::endl << _linePrefix; + AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) + << "Note that the test passed without Yul and also has to pass with Yul(EOF)."; + _stream << std::endl; + } + else if (_isEOFRun && _isYulRun) + { + _stream << _linePrefix << std::endl << _linePrefix; + AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) + << "Note that the test passed without Yul and with Yul without EOF."; + _stream << std::endl; + } + } + return TestResult::Failure; } + AnsiColorized(_stream, _formatted, {BOLD, GREEN}) << _linePrefix << "OK" << std::endl; return TestResult::Success; } TestCase::TestResult SemanticTest::tryRunTestWithYulOptimizer( std::ostream& _stream, std::string const& _linePrefix, - bool _formatted + bool _formatted, + bool _compileToEOF ) { TestResult result{}; @@ -536,7 +638,7 @@ TestCase::TestResult SemanticTest::tryRunTestWithYulOptimizer( try { - result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */); + result = runTest(_stream, _linePrefix, _formatted, true /* _isYulRun */, _compileToEOF /* _isEOFRun */); } catch (yul::StackTooDeepError const&) { diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index efc2b59030e6..f9556f60b094 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -96,12 +96,14 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict std::ostream& _stream, std::string const& _linePrefix, bool _formatted, - bool _isYulRun + bool _isYulRun, + bool _isEOFRun ); TestResult tryRunTestWithYulOptimizer( std::ostream& _stream, std::string const& _linePrefix, - bool _formatted + bool _formatted, + bool _compileToEOF ); bool checkGasCostExpectation(TestFunctionCall& io_test, bool _compileViaYul) const; std::map makeBuiltins(); @@ -119,6 +121,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict std::vector const m_sideEffectHooks; bool m_testCaseWantsYulRun = true; bool m_testCaseWantsLegacyRun = true; + bool m_testCaseWantsEOFRun = true; bool m_runWithABIEncoderV1Only = false; bool m_allowNonExistingFunctions = false; bool m_gasCostFailure = false; From bbfb85d6d34e12d144349f8c5017e89bfa381316 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 26 Jul 2024 14:42:55 +0200 Subject: [PATCH 15/15] tests: Update semantic tests for EOF --- test/libsolidity/Metadata.cpp | 2 +- .../semanticTests/UniswapV3Flattened.sol | 5191 +++++++++++++++++ .../semanticTests/UniswapV3Flattened_eof.sol | 5191 +++++++++++++++++ .../keccak256_packed_complex_types.sol | 2 + .../keccak256_packed_complex_types_eof.sol | 17 + .../constructor/callvalue_check.sol | 35 +- .../constructor/no_callvalue_check.sol | 2 + .../constructor/no_callvalue_check_eof.sol | 28 + .../deployedCodeExclusion/bound_function.sol | 2 + .../library_function.sol | 2 + .../deployedCodeExclusion/module_function.sol | 2 + .../static_base_function.sol | 2 + .../subassembly_deduplication.sol | 2 + .../deployedCodeExclusion/super_function.sol | 2 + .../virtual_function.sol | 2 + .../errors/errors_by_parameter_type.sol | 1 + .../errors/errors_by_parameter_type_eof.sol | 46 + ...quire_error_function_pointer_parameter.sol | 2 + ...e_error_function_pointer_parameter_eof.sol | 21 + .../events/event_emit_from_other_contract.sol | 2 + .../event_emit_from_other_contract_eof.sol | 28 + .../events/event_indexed_function.sol | 2 + .../events/event_indexed_function2.sol | 2 + .../events/event_indexed_function2_eof.sol | 18 + .../events/event_indexed_function_eof.sol | 12 + .../_stringutils/stringutils.sol | 2 +- .../externalContracts/deposit_contract.sol | 2 + .../deposit_contract_eof.sol | 216 + .../semanticTests/externalContracts/snark.sol | 13 +- .../externalContracts/snark_eof.sol | 312 + .../freeFunctions/free_runtimecode.sol | 2 + .../calling_nonexisting_contract_throws.sol | 2 + .../external_call_at_construction_time.sol | 1 + .../external_call_to_nonexisting.sol | 2 + ...ernal_call_to_nonexisting_debugstrings.sol | 1 + .../functionCall/failed_create.sol | 1 + .../functionCall/failed_create_eof.sol | 38 + .../functionCall/gas_and_value_basic.sol | 2 + .../functionCall/gas_and_value_basic_eof.sol | 52 + .../gas_and_value_brace_syntax.sol | 2 + .../gas_and_value_brace_syntax_eof.sol | 51 + .../functionTypes/address_member.sol | 2 + .../functionTypes/address_member_eof.sol | 13 + .../function_external_delete_storage.sol | 2 + .../function_external_delete_storage_eof.sol | 40 + .../immutable/multi_creation.sol | 2 + .../immutable/multi_creation_eof.sol | 40 + .../address_overload_resolution.sol | 2 + .../address_overload_resolution_eof.sol | 32 + .../inheritance/member_notation_ctor.sol | 2 + .../inheritance/member_notation_ctor_eof.sol | 29 + .../external_function_pointer_address.sol | 2 + .../external_function_pointer_address_eof.sol | 20 + .../transient_storage_low_level_calls.sol | 1 + .../transient_storage_low_level_calls_eof.sol | 78 + .../transient_storage_selfdestruct.sol | 1 + .../interface_inheritance_conversions.sol | 2 + .../interface_inheritance_conversions_eof.sol | 47 + .../balance_other_contract.sol | 2 + .../balance_other_contract_eof.sol | 33 + .../operator_making_pure_external_call.sol | 2 + ...operator_making_pure_external_call_eof.sol | 68 + .../operator_making_view_external_call.sol | 2 + ...operator_making_view_external_call_eof.sol | 74 + .../called_contract_has_code.sol | 1 + .../reverts/revert_return_area.sol | 1 + .../reverts/revert_return_area_eof.sol | 21 + .../salted_create/prediction_example.sol | 22 +- .../salted_create_with_value.sol | 1 + .../salted_create_with_value_eof.sol | 31 + .../semanticTests/shanghai/evmone_support.sol | 1 + .../semanticTests/state/gasleft.sol | 2 + .../semanticTests/tryCatch/create.sol | 1 + .../semanticTests/tryCatch/create_eof.sol | 34 + .../tryCatch/return_function.sol | 2 + .../tryCatch/return_function_eof.sol | 20 + .../semanticTests/various/address_code.sol | 2 + .../various/address_code_complex.sol | 2 + .../various/code_access_content.sol | 2 + .../various/code_access_create.sol | 2 + .../various/code_access_padding.sol | 2 + .../various/code_access_runtime.sol | 1 + .../semanticTests/various/code_length.sol | 2 + .../various/code_length_contract_member.sol | 2 + .../semanticTests/various/codehash.sol | 1 + .../various/codehash_assembly.sol | 1 + .../semanticTests/various/create_calldata.sol | 2 + .../semanticTests/various/create_random.sol | 7 +- .../various/gasleft_decrease.sol | 2 + .../various/many_subassemblies.sol | 2 + .../various/many_subassemblies_eof.sol | 41 + .../various/selfdestruct_post_cancun.sol | 1 + ...uct_post_cancun_multiple_beneficiaries.sol | 1 + .../selfdestruct_post_cancun_redeploy.sol | 1 + .../viaYul/conversion/function_cast.sol | 3 + .../viaYul/conversion/function_cast_eof.sol | 25 + .../semanticTests/viaYul/function_address.sol | 2 + .../viaYul/function_address_eof.sol | 18 + 98 files changed, 12041 insertions(+), 29 deletions(-) create mode 100644 test/libsolidity/semanticTests/UniswapV3Flattened.sol create mode 100644 test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol create mode 100644 test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol create mode 100644 test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol create mode 100644 test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol create mode 100644 test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol create mode 100644 test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol create mode 100644 test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol create mode 100644 test/libsolidity/semanticTests/events/event_indexed_function_eof.sol create mode 100644 test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol create mode 100644 test/libsolidity/semanticTests/externalContracts/snark_eof.sol create mode 100644 test/libsolidity/semanticTests/functionCall/failed_create_eof.sol create mode 100644 test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol create mode 100644 test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol create mode 100644 test/libsolidity/semanticTests/functionTypes/address_member_eof.sol create mode 100644 test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol create mode 100644 test/libsolidity/semanticTests/immutable/multi_creation_eof.sol create mode 100644 test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol create mode 100644 test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol create mode 100644 test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol create mode 100644 test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol create mode 100644 test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol create mode 100644 test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol create mode 100644 test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol create mode 100644 test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol create mode 100644 test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol create mode 100644 test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol create mode 100644 test/libsolidity/semanticTests/tryCatch/create_eof.sol create mode 100644 test/libsolidity/semanticTests/tryCatch/return_function_eof.sol create mode 100644 test/libsolidity/semanticTests/various/many_subassemblies_eof.sol create mode 100644 test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol create mode 100644 test/libsolidity/semanticTests/viaYul/function_address_eof.sol diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index 94687f99006d..9c0fba3e1447 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -243,7 +243,7 @@ BOOST_AUTO_TEST_CASE(metadata_eof_experimental) CompilerStack compilerStack; compilerStack.setMetadataFormat(metadataFormat); compilerStack.setSources({{"", sourceCode}}); - compilerStack.setEVMVersion({}); + compilerStack.setEVMVersion(langutil::EVMVersion::prague()); compilerStack.setViaIR(true); compilerStack.setEOFVersion(1); compilerStack.setOptimiserSettings(true); diff --git a/test/libsolidity/semanticTests/UniswapV3Flattened.sol b/test/libsolidity/semanticTests/UniswapV3Flattened.sol new file mode 100644 index 000000000000..319cf73da2a1 --- /dev/null +++ b/test/libsolidity/semanticTests/UniswapV3Flattened.sol @@ -0,0 +1,5191 @@ +// Sources flattened with hardhat v2.2.0 https://hardhat.org + +// File contracts/interfaces/pool/IUniswapV3PoolImmutables.sol + + +pragma solidity >=0.0; + +/// @title Pool state that never changes +/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values +interface IUniswapV3PoolImmutables { + /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface + /// @return The contract address + function factory() external view returns (address); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); + + /// @notice The pool tick spacing + /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive + /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... + /// This value is an int24 to avoid casting even though it is always positive. + /// @return The tick spacing + function tickSpacing() external view returns (int24); + + /// @notice The maximum amount of position liquidity that can use any tick in the range + /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and + /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool + /// @return The max amount of liquidity per tick + function maxLiquidityPerTick() external view returns (uint128); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() external view returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks(int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions(bytes32 key) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations(uint256 index) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); + + /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range + /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. + /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first + /// snapshot is taken and the second snapshot is taken. + /// @param tickLower The lower tick of the range + /// @param tickUpper The upper tick of the range + /// @return tickCumulativeInside The snapshot of the tick accumulator for the range + /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range + /// @return secondsInside The snapshot of seconds per liquidity for the range + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolActions.sol + + +pragma solidity >=0.0; + +/// @title Permissionless pool actions +/// @notice Contains pool methods that can be called by anyone +interface IUniswapV3PoolActions { + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 + function initialize(uint160 sqrtPriceX96) external; + + /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position + /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback + /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + /// on tickLower, tickUpper, the amount of liquidity, and the current price. + /// @param recipient The address for which the liquidity will be created + /// @param tickLower The lower tick of the position in which to add liquidity + /// @param tickUpper The upper tick of the position in which to add liquidity + /// @param amount The amount of liquidity to mint + /// @param data Any data that should be passed through to the callback + /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback + /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Collects tokens owed to a position + /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. + /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or + /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the + /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. + /// @param recipient The address which should receive the fees collected + /// @param tickLower The lower tick of the position for which to collect fees + /// @param tickUpper The upper tick of the position for which to collect fees + /// @param amount0Requested How much token0 should be withdrawn from the fees owed + /// @param amount1Requested How much token1 should be withdrawn from the fees owed + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); + + /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position + /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 + /// @dev Fees must be collected separately via a call to #collect + /// @param tickLower The lower tick of the position for which to burn liquidity + /// @param tickUpper The upper tick of the position for which to burn liquidity + /// @param amount How much liquidity to burn + /// @return amount0 The amount of token0 sent to the recipient + /// @return amount1 The amount of token1 sent to the recipient + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback + /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback + /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling + /// with 0 amount{0,1} and sending the donation amount(s) from the callback + /// @param recipient The address which will receive the token0 and token1 amounts + /// @param amount0 The amount of token0 to send + /// @param amount1 The amount of token1 to send + /// @param data Any data to be passed through to the callback + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; + + /// @notice Increase the maximum number of price and liquidity observations that this pool will store + /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to + /// the input observationCardinalityNext. + /// @param observationCardinalityNext The desired minimum number of observations for the pool to store + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; +} + + +// File contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol + + +pragma solidity >=0.0; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolEvents.sol + + +pragma solidity >=0.0; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/IUniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolEvents +{ + +} + + +// File contracts/libraries/FullMath.sol + + +pragma solidity >=0.0; + +/// @title Contains 512-bit math functions +/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision +/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = uint256(-int256(denominator)) & denominator; + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + unchecked { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } + } +} + + +// File contracts/libraries/FixedPoint128.sol + + +pragma solidity >=0.0; + +/// @title FixedPoint128 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +library FixedPoint128 { + uint256 internal constant Q128 = 0x100000000000000000000000000000000; +} + + +// File contracts/libraries/LiquidityMath.sol + + +pragma solidity >=0.0; + +/// @title Math library for liquidity +library LiquidityMath { + /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows + /// @param x The liquidity before change + /// @param y The delta by which liquidity should be changed + /// @return z The liquidity delta + function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { + if (y < 0) { + unchecked { + require((z = x - uint128(-y)) < x, 'LS'); + } + } else { + unchecked { + require((z = x + uint128(y)) >= x, 'LA'); + } + } + } +} + + +// File contracts/libraries/Position.sol + + +pragma solidity >=0.0; + + + +/// @title Position +/// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary +/// @dev Positions store additional state for tracking fees owed to the position +library Position { + // info stored for each user's position + struct Info { + // the amount of liquidity owned by this position + uint128 liquidity; + // fee growth per unit of liquidity as of the last update to liquidity or fees owed + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // the fees owed to the position owner in token0/token1 + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + /// @notice Returns the Info struct of a position, given an owner and position boundaries + /// @param self The mapping containing all user positions + /// @param owner The address of the position owner + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @return position The position info struct of the given owners' position + function get( + mapping(bytes32 => Info) storage self, + address owner, + int24 tickLower, + int24 tickUpper + ) internal view returns (Position.Info storage position) { + position = self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))]; + } + + /// @notice Credits accumulated fees to a user's position + /// @param self The individual position to update + /// @param liquidityDelta The change in pool liquidity as a result of the position update + /// @param feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries + /// @param feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries + function update( + Info storage self, + int128 liquidityDelta, + uint256 feeGrowthInside0X128, + uint256 feeGrowthInside1X128 + ) internal { + unchecked { + Info memory _self = self; + + uint128 liquidityNext; + if (liquidityDelta == 0) { + require(_self.liquidity > 0, 'NP'); // disallow pokes for 0 liquidity positions + liquidityNext = _self.liquidity; + } else { + liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta); + } + + // calculate accumulated fees + uint128 tokensOwed0 = + uint128( + FullMath.mulDiv( + feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + uint128 tokensOwed1 = + uint128( + FullMath.mulDiv( + feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + + // update the position + if (liquidityDelta != 0) self.liquidity = liquidityNext; + self.feeGrowthInside0LastX128 = feeGrowthInside0X128; + self.feeGrowthInside1LastX128 = feeGrowthInside1X128; + if (tokensOwed0 > 0 || tokensOwed1 > 0) { + // overflow is acceptable, have to withdraw before you hit type(uint128).max fees + self.tokensOwed0 += tokensOwed0; + self.tokensOwed1 += tokensOwed1; + } + } + } +} + + +// File contracts/libraries/LowGasSafeMath.sol + + +pragma solidity >=0.0; + +/// @title Optimized overflow and underflow safe math operations +/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost +library LowGasSafeMath { + /// @notice Returns x + y, reverts if sum overflows uint256 + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x); + } + + /// @notice Returns x - y, reverts if underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x); + } + + /// @notice Returns x * y, reverts if overflows + /// @param x The multiplicand + /// @param y The multiplier + /// @return z The product of x and y + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + require(x == 0 || (z = x * y) / x == y); + } + + /// @notice Returns x + y, reverts if overflows or underflows + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x + y) >= x == (y >= 0)); + } + + /// @notice Returns x - y, reverts if overflows or underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x - y) <= x == (y >= 0)); + } +} + + +// File contracts/libraries/SafeCast.sol + + +pragma solidity >=0.0; + +/// @title Safe casting methods +/// @notice Contains methods for safely casting between types +library SafeCast { + /// @notice Cast a uint256 to a uint160, revert on overflow + /// @param y The uint256 to be downcasted + /// @return z The downcasted integer, now type uint160 + function toUint160(uint256 y) internal pure returns (uint160 z) { + require((z = uint160(y)) == y); + } + + /// @notice Cast a int256 to a int128, revert on overflow or underflow + /// @param y The int256 to be downcasted + /// @return z The downcasted integer, now type int128 + function toInt128(int256 y) internal pure returns (int128 z) { + require((z = int128(y)) == y); + } + + /// @notice Cast a uint256 to a int256, revert on overflow + /// @param y The uint256 to be casted + /// @return z The casted integer, now type int256 + function toInt256(uint256 y) internal pure returns (int256 z) { + require(y < 2**255); + z = int256(y); + } +} + + +// File contracts/libraries/UnsafeMath.sol + + +pragma solidity >=0.0; + +/// @title Math functions that do not check inputs or outputs +/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks +library UnsafeMath { + /// @notice Returns ceil(x / y) + /// @dev division by 0 has unspecified behavior, and must be checked externally + /// @param x The dividend + /// @param y The divisor + /// @return z The quotient, ceil(x / y) + function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := add(div(x, y), gt(mod(x, y), 0)) + } + } +} + + +// File contracts/libraries/FixedPoint96.sol + + +pragma solidity >=0.0; + +/// @title FixedPoint96 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +/// @dev Used in SqrtPriceMath.sol +library FixedPoint96 { + uint8 internal constant RESOLUTION = 96; + uint256 internal constant Q96 = 0x1000000000000000000000000; +} + + +// File contracts/libraries/SqrtPriceMath.sol + + +pragma solidity >=0.0; + + + + +/// @title Functions based on Q64.96 sqrt price and liquidity +/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas +library SqrtPriceMath { + using LowGasSafeMath for uint256; + using SafeCast for uint256; + + /// @notice Gets the next sqrt price given a delta of token0 + /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the + /// price less in order to not send too much output. + /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), + /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). + /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token0 to add or remove from virtual reserves + /// @param add Whether to add or remove the amount of token0 + /// @return The price after adding or removing amount, depending on add + function getNextSqrtPriceFromAmount0RoundingUp( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + + if (add) { + uint256 product; + if ((product = amount * sqrtPX96) / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) + // always fits in 160 bits + return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + } + + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))); + } else { + uint256 product; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + } + + /// @notice Gets the next sqrt price given a delta of token1 + /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the + /// price less in order to not send too much output. + /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity + /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token1 to add, or remove, from virtual reserves + /// @param add Whether to add, or remove, the amount of token1 + /// @return The price after adding or removing `amount` + function getNextSqrtPriceFromAmount1RoundingDown( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if (add) { + uint256 quotient = + ( + amount <= type(uint160).max + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ); + + return uint256(sqrtPX96).add(quotient).toUint160(); + } else { + uint256 quotient = + ( + amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ); + + require(sqrtPX96 > quotient); + // always fits 160 bits + return uint160(sqrtPX96 - quotient); + } + } + } + + /// @notice Gets the next sqrt price given an input amount of token0 or token1 + /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds + /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount + /// @param liquidity The amount of usable liquidity + /// @param amountIn How much of token0, or token1, is being swapped in + /// @param zeroForOne Whether the amount in is token0 or token1 + /// @return sqrtQX96 The price after adding the input amount to token0 or token1 + function getNextSqrtPriceFromInput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we don't pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + /// @notice Gets the next sqrt price given an output amount of token0 or token1 + /// @dev Throws if price or liquidity are 0 or the next price is out of bounds + /// @param sqrtPX96 The starting price before accounting for the output amount + /// @param liquidity The amount of usable liquidity + /// @param amountOut How much of token0, or token1, is being swapped out + /// @param zeroForOne Whether the amount out is token0 or token1 + /// @return sqrtQX96 The price after removing the output amount of token0 or token1 + function getNextSqrtPriceFromOutput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + /// @notice Gets the amount0 delta between two prices + /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), + /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up or down + /// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0); + + return + roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + sqrtRatioAX96 + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Helper that gets signed token0 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount0) { + return + liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + + /// @notice Helper that gets signed token1 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount1) { + return + liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } +} + + +// File contracts/libraries/SwapMath.sol + + +pragma solidity >=0.0; + + +/// @title Computes the result of a swap within ticks +/// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. +library SwapMath { + /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap + /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive + /// @param sqrtRatioCurrentX96 The current sqrt price of the pool + /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred + /// @param liquidity The usable liquidity + /// @param amountRemaining How much input or output amount is remaining to be swapped in/out + /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip + /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target + /// @return amountIn The amount to be swapped in, of either token0 or token1, based on the direction of the swap + /// @return amountOut The amount to be received, of either token0 or token1, based on the direction of the swap + /// @return feeAmount The amount of input that will be taken as a fee + function computeSwapStep( + uint160 sqrtRatioCurrentX96, + uint160 sqrtRatioTargetX96, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + internal + pure + returns ( + uint160 sqrtRatioNextX96, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; + bool exactIn = amountRemaining >= 0; + + if (exactIn) { + uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6); + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true); + if (amountRemainingLessFee >= amountIn) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtRatioCurrentX96, + liquidity, + amountRemainingLessFee, + zeroForOne + ); + } else { + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false); + if (uint256(-amountRemaining) >= amountOut) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtRatioCurrentX96, + liquidity, + uint256(-amountRemaining), + zeroForOne + ); + } + + bool max = sqrtRatioTargetX96 == sqrtRatioNextX96; + + // get the input/output amounts + if (zeroForOne) { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false); + } else { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false); + } + + // cap the output amount to not exceed the remaining output amount + if (!exactIn && amountOut > uint256(-amountRemaining)) { + amountOut = uint256(-amountRemaining); + } + + if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { + // we didn't reach the target, so take the remainder of the maximum input as fee + feeAmount = uint256(amountRemaining) - amountIn; + } else { + feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); + } + } +} + + +// File contracts/libraries/TickMath.sol + + +pragma solidity >=0.0; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(int256(MAX_TICK)), 'T'); + + uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint256).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + } + + /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio + function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R'); + uint256 ratio = uint256(sqrtPriceX96) << 32; + + uint256 r = ratio; + uint256 msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; + } +} + + +// File contracts/libraries/Tick.sol + + +pragma solidity >=0.0; + + + +/// @title Tick +/// @notice Contains functions for managing tick processes and relevant calculations +library Tick { + using LowGasSafeMath for int256; + using SafeCast for int256; + + // info stored for each initialized individual tick + struct Info { + // the total position liquidity that references this tick + uint128 liquidityGross; + // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), + int128 liquidityNet; + // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint256 feeGrowthOutside0X128; + uint256 feeGrowthOutside1X128; + // the cumulative tick value on the other side of the tick + int56 tickCumulativeOutside; + // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint160 secondsPerLiquidityOutsideX128; + // the seconds spent on the other side of the tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint32 secondsOutside; + // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 + // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks + bool initialized; + } + + /// @notice Derives max liquidity per tick from given tick spacing + /// @dev Executed within the pool constructor + /// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing` + /// e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ... + /// @return The max liquidity per tick + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128) { + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1; + return type(uint128).max / numTicks; + } + + /// @notice Retrieves fee growth data + /// @param self The mapping containing all tick information for initialized ticks + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @param tickCurrent The current tick + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @return feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries + /// @return feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries + function getFeeGrowthInside( + mapping(int24 => Tick.Info) storage self, + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + Info storage lower = self[tickLower]; + Info storage upper = self[tickUpper]; + + // calculate fee growth below + uint256 feeGrowthBelow0X128; + uint256 feeGrowthBelow1X128; + unchecked { + if (tickCurrent >= tickLower) { + feeGrowthBelow0X128 = lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = lower.feeGrowthOutside1X128; + } else { + feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128; + } + + // calculate fee growth above + uint256 feeGrowthAbove0X128; + uint256 feeGrowthAbove1X128; + if (tickCurrent < tickUpper) { + feeGrowthAbove0X128 = upper.feeGrowthOutside0X128; + feeGrowthAbove1X128 = upper.feeGrowthOutside1X128; + } else { + feeGrowthAbove0X128 = feeGrowthGlobal0X128 - upper.feeGrowthOutside0X128; + feeGrowthAbove1X128 = feeGrowthGlobal1X128 - upper.feeGrowthOutside1X128; + } + + feeGrowthInside0X128 = feeGrowthGlobal0X128 - feeGrowthBelow0X128 - feeGrowthAbove0X128; + feeGrowthInside1X128 = feeGrowthGlobal1X128 - feeGrowthBelow1X128 - feeGrowthAbove1X128; + } + } + + /// @notice Updates a tick and returns true if the tick was flipped from initialized to uninitialized, or vice versa + /// @param self The mapping containing all tick information for initialized ticks + /// @param tick The tick that will be updated + /// @param tickCurrent The current tick + /// @param liquidityDelta A new amount of liquidity to be added (subtracted) when tick is crossed from left to right (right to left) + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @param secondsPerLiquidityCumulativeX128 The all-time seconds per max(1, liquidity) of the pool + /// @param tickCumulative The tick * time elapsed since the pool was first initialized + /// @param time The current block timestamp cast to a uint32 + /// @param upper true for updating a position's upper tick, or false for updating a position's lower tick + /// @param maxLiquidity The maximum liquidity allocation for a single tick + /// @return flipped Whether the tick was flipped from initialized to uninitialized, or vice versa + function update( + mapping(int24 => Tick.Info) storage self, + int24 tick, + int24 tickCurrent, + int128 liquidityDelta, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time, + bool upper, + uint128 maxLiquidity + ) internal returns (bool flipped) { + Tick.Info storage info = self[tick]; + + uint128 liquidityGrossBefore = info.liquidityGross; + uint128 liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta); + + require(liquidityGrossAfter <= maxLiquidity, 'LO'); + + flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0); + + if (liquidityGrossBefore == 0) { + // by convention, we assume that all growth before a tick was initialized happened _below_ the tick + if (tick <= tickCurrent) { + info.feeGrowthOutside0X128 = feeGrowthGlobal0X128; + info.feeGrowthOutside1X128 = feeGrowthGlobal1X128; + info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128; + info.tickCumulativeOutside = tickCumulative; + info.secondsOutside = time; + } + info.initialized = true; + } + + info.liquidityGross = liquidityGrossAfter; + + // when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed) + info.liquidityNet = upper + ? int256(info.liquidityNet).sub(liquidityDelta).toInt128() + : int256(info.liquidityNet).add(liquidityDelta).toInt128(); + } + + /// @notice Clears tick data + /// @param self The mapping containing all initialized tick information for initialized ticks + /// @param tick The tick that will be cleared + function clear(mapping(int24 => Tick.Info) storage self, int24 tick) internal { + delete self[tick]; + } + + /// @notice Transitions to next tick as needed by price movement + /// @param self The mapping containing all tick information for initialized ticks + /// @param tick The destination tick of the transition + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @param secondsPerLiquidityCumulativeX128 The current seconds per liquidity + /// @param tickCumulative The tick * time elapsed since the pool was first initialized + /// @param time The current block.timestamp + /// @return liquidityNet The amount of liquidity added (subtracted) when tick is crossed from left to right (right to left) + function cross( + mapping(int24 => Tick.Info) storage self, + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) internal returns (int128 liquidityNet) { + Tick.Info storage info = self[tick]; + info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128; + info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128; + info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128 - info.secondsPerLiquidityOutsideX128; + info.tickCumulativeOutside = tickCumulative - info.tickCumulativeOutside; + info.secondsOutside = time - info.secondsOutside; + liquidityNet = info.liquidityNet; + } +} + + +// File contracts/libraries/BitMath.sol + + +pragma solidity >=0.0; + +/// @title BitMath +/// @dev This library provides functionality for computing bit properties of an unsigned integer +library BitMath { + /// @notice Returns the index of the most significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @dev The function satisfies the property: + /// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1) + /// @param x the value for which to compute the most significant bit, must be greater than 0 + /// @return r the index of the most significant bit + function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + if (x >= 0x100000000000000000000000000000000) { + x >>= 128; + r += 128; + } + if (x >= 0x10000000000000000) { + x >>= 64; + r += 64; + } + if (x >= 0x100000000) { + x >>= 32; + r += 32; + } + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 0x4) { + x >>= 2; + r += 2; + } + if (x >= 0x2) r += 1; + } + + /// @notice Returns the index of the least significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @dev The function satisfies the property: + /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) + /// @param x the value for which to compute the least significant bit, must be greater than 0 + /// @return r the index of the least significant bit + function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + r = 255; + if (x & type(uint128).max > 0) { + r -= 128; + } else { + x >>= 128; + } + if (x & type(uint64).max > 0) { + r -= 64; + } else { + x >>= 64; + } + if (x & type(uint32).max > 0) { + r -= 32; + } else { + x >>= 32; + } + if (x & type(uint16).max > 0) { + r -= 16; + } else { + x >>= 16; + } + if (x & type(uint8).max > 0) { + r -= 8; + } else { + x >>= 8; + } + if (x & 0xf > 0) { + r -= 4; + } else { + x >>= 4; + } + if (x & 0x3 > 0) { + r -= 2; + } else { + x >>= 2; + } + if (x & 0x1 > 0) r -= 1; + } +} + + +// File contracts/libraries/TickBitmap.sol + + +pragma solidity >=0.0; + +/// @title Packed tick initialized state library +/// @notice Stores a packed mapping of tick index to its initialized state +/// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word. +library TickBitmap { + /// @notice Computes the position in the mapping where the initialized bit for a tick lives + /// @param tick The tick for which to compute the position + /// @return wordPos The key in the mapping containing the word in which the bit is stored + /// @return bitPos The bit position in the word where the flag is stored + function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { + wordPos = int16(tick >> 8); + bitPos = uint8(uint24(tick % 256)); + } + + /// @notice Flips the initialized state for a given tick from false to true, or vice versa + /// @param self The mapping in which to flip the tick + /// @param tick The tick to flip + /// @param tickSpacing The spacing between usable ticks + function flipTick( + mapping(int16 => uint256) storage self, + int24 tick, + int24 tickSpacing + ) internal { + require(tick % tickSpacing == 0); // ensure that the tick is spaced + (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); + uint256 mask = 1 << bitPos; + self[wordPos] ^= mask; + } + + /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either + /// to the left (less than or equal to) or right (greater than) of the given tick + /// @param self The mapping in which to compute the next initialized tick + /// @param tick The starting tick + /// @param tickSpacing The spacing between usable ticks + /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) + /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick + /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks + function nextInitializedTickWithinOneWord( + mapping(int16 => uint256) storage self, + int24 tick, + int24 tickSpacing, + bool lte + ) internal view returns (int24 next, bool initialized) { + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(compressed + 1); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the left of the current tick, return leftmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; + } + } +} + + +// File contracts/interfaces/IERC20Minimal.sol + + +pragma solidity >=0.0; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IERC20Minimal { + /// @notice Returns the balance of a token + /// @param account The account for which to look up the number of tokens it has, i.e. its balance + /// @return The number of tokens held by the account + function balanceOf(address account) external view returns (uint256); + + /// @notice Transfers the amount of token from the `msg.sender` to the recipient + /// @param recipient The account that will receive the amount transferred + /// @param amount The number of tokens to send from the sender to the recipient + /// @return Returns true for a successful transfer, false for an unsuccessful transfer + function transfer(address recipient, uint256 amount) external returns (bool); + + /// @notice Returns the current allowance given to a spender by an owner + /// @param owner The account of the token owner + /// @param spender The account of the token spender + /// @return The current allowance granted by `owner` to `spender` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` + /// @param spender The account which will be allowed to spend a given amount of the owners tokens + /// @param amount The amount of tokens allowed to be used by `spender` + /// @return Returns true for a successful approval, false for unsuccessful + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` + /// @param sender The account from which the transfer will be initiated + /// @param recipient The recipient of the transfer + /// @param amount The amount of the transfer + /// @return Returns true for a successful transfer, false for unsuccessful + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. + /// @param from The account from which the tokens were sent, i.e. the balance decreased + /// @param to The account to which the tokens were sent, i.e. the balance increased + /// @param value The amount of tokens that were transferred + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. + /// @param owner The account that approved spending of its tokens + /// @param spender The account for which the spending allowance was modified + /// @param value The new allowance from the owner to the spender + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +// File contracts/libraries/TransferHelper.sol + + +pragma solidity >=0.0; + +/// @title TransferHelper +/// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false +library TransferHelper { + /// @notice Transfers tokens from msg.sender to a recipient + /// @dev Calls transfer on token contract, errors with TF if transfer fails + /// @param token The contract address of the token which will be transferred + /// @param to The recipient of the transfer + /// @param value The value of the transfer + function safeTransfer( + address token, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = + token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'TF'); + } +} + + +// File contracts/test/BitMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract BitMathEchidnaTest { + function mostSignificantBitInvariant(uint256 input) external pure { + uint8 msb = BitMath.mostSignificantBit(input); + assert(input >= (uint256(2)**msb)); + assert(msb == 255 || input < uint256(2)**(msb + 1)); + } + + function leastSignificantBitInvariant(uint256 input) external pure { + uint8 lsb = BitMath.leastSignificantBit(input); + assert(input & (uint256(2)**lsb) != 0); + assert(input & (uint256(2)**lsb - 1) == 0); + } +} + + +// File contracts/test/BitMathTest.sol + + +pragma solidity >=0.0; + +contract BitMathTest { + function mostSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.mostSignificantBit(x); + } + + function getGasCostOfMostSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.mostSignificantBit(x); + return gasBefore - gasleft(); + } + + function leastSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.leastSignificantBit(x); + } + + function getGasCostOfLeastSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.leastSignificantBit(x); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/FullMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract FullMathEchidnaTest { + function checkMulDivRounding( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + + uint256 ceiled = FullMath.mulDivRoundingUp(x, y, d); + uint256 floored = FullMath.mulDiv(x, y, d); + + if (mulmod(x, y, d) > 0) { + assert(ceiled - floored == 1); + } else { + assert(ceiled == floored); + } + } + + function checkMulDiv( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDiv(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 <= x); + assert(y2 <= y); + + assert(x - x2 < d); + assert(y - y2 < d); + } + + function checkMulDivRoundingUp( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDivRoundingUp(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 >= x); + assert(y2 >= y); + + assert(x2 - x < d); + assert(y2 - y < d); + } +} + + +// File contracts/test/FullMathTest.sol + + +pragma solidity >=0.0; + +contract FullMathTest { + function mulDiv( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDiv(x, y, z); + } + + function mulDivRoundingUp( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDivRoundingUp(x, y, z); + } +} + + +// File contracts/test/LiquidityMathTest.sol + + +pragma solidity >=0.0; + +contract LiquidityMathTest { + function addDelta(uint128 x, int128 y) external pure returns (uint128 z) { + return LiquidityMath.addDelta(x, y); + } + + function getGasCostOfAddDelta(uint128 x, int128 y) external view returns (uint256) { + uint256 gasBefore = gasleft(); + LiquidityMath.addDelta(x, y); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/LowGasSafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract LowGasSafeMathEchidnaTest { + function checkAdd(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(z >= x && z >= y); + } + + function checkSub(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(z <= x); + } + + function checkMul(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.mul(x, y); + assert(z == x * y); + assert(x == 0 || y == 0 || (z >= x && z >= y)); + } + + function checkAddi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(y < 0 ? z < x : z >= x); + } + + function checkSubi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(y < 0 ? z > x : z <= x); + } +} + + +// File contracts/NoDelegateCall.sol + + +pragma solidity >=0.0; + +/// @title Prevents delegatecall to a contract +/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract +abstract contract NoDelegateCall { + /// @dev The original address of this contract + address private immutable original; + + constructor() { + // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. + // In other words, this variable won't change when it's checked at runtime. + original = address(this); + } + + /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, + /// and the use of immutable means the address bytes are copied in every place the modifier is used. + function checkNotDelegateCall() private view { + require(address(this) == original); + } + + /// @notice Prevents delegatecall into the modified method + modifier noDelegateCall() { + checkNotDelegateCall(); + _; + } +} + + +// File contracts/libraries/Oracle.sol + + +pragma solidity >=0.0; + +/// @title Oracle +/// @notice Provides price and liquidity data useful for a wide variety of system designs +/// @dev Instances of stored oracle data, "observations", are collected in the oracle array +/// Every pool is initialized with an oracle array length of 1. Anyone can pay the SSTOREs to increase the +/// maximum length of the oracle array. New slots will be added when the array is fully populated. +/// Observations are overwritten when the full length of the oracle array is populated. +/// The most recent observation is available, independent of the length of the oracle array, by passing 0 to observe() +library Oracle { + struct Observation { + // the block timestamp of the observation + uint32 blockTimestamp; + // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized + int56 tickCumulative; + // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized + uint160 secondsPerLiquidityCumulativeX128; + // whether or not the observation is initialized + bool initialized; + } + + /// @notice Transforms a previous observation into a new observation, given the passage of time and the current tick and liquidity values + /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows + /// @param last The specified observation to be transformed + /// @param blockTimestamp The timestamp of the new observation + /// @param tick The active tick at the time of the new observation + /// @param liquidity The total in-range liquidity at the time of the new observation + /// @return Observation The newly populated observation + function transform( + Observation memory last, + uint32 blockTimestamp, + int24 tick, + uint128 liquidity + ) private pure returns (Observation memory) { + unchecked { + uint32 delta = blockTimestamp - last.blockTimestamp; + return + Observation({ + blockTimestamp: blockTimestamp, + tickCumulative: last.tickCumulative + int56(tick) * int56(uint56(delta)), + secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128 + + ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)), + initialized: true + }); + } + } + + /// @notice Initialize the oracle array by writing the first slot. Called once for the lifecycle of the observations array + /// @param self The stored oracle array + /// @param time The time of the oracle initialization, via block.timestamp truncated to uint32 + /// @return cardinality The number of populated elements in the oracle array + /// @return cardinalityNext The new length of the oracle array, independent of population + function initialize(Observation[65535] storage self, uint32 time) + internal + returns (uint16 cardinality, uint16 cardinalityNext) + { + self[0] = Observation({ + blockTimestamp: time, + tickCumulative: 0, + secondsPerLiquidityCumulativeX128: 0, + initialized: true + }); + return (1, 1); + } + + /// @notice Writes an oracle observation to the array + /// @dev Writable at most once per block. Index represents the most recently written element. cardinality and index must be tracked externally. + /// If the index is at the end of the allowable array length (according to cardinality), and the next cardinality + /// is greater than the current one, cardinality may be increased. This restriction is created to preserve ordering. + /// @param self The stored oracle array + /// @param index The index of the observation that was most recently written to the observations array + /// @param blockTimestamp The timestamp of the new observation + /// @param tick The active tick at the time of the new observation + /// @param liquidity The total in-range liquidity at the time of the new observation + /// @param cardinality The number of populated elements in the oracle array + /// @param cardinalityNext The new length of the oracle array, independent of population + /// @return indexUpdated The new index of the most recently written element in the oracle array + /// @return cardinalityUpdated The new cardinality of the oracle array + function write( + Observation[65535] storage self, + uint16 index, + uint32 blockTimestamp, + int24 tick, + uint128 liquidity, + uint16 cardinality, + uint16 cardinalityNext + ) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) { + Observation memory last = self[index]; + + // early return if we've already written an observation this block + if (last.blockTimestamp == blockTimestamp) return (index, cardinality); + + // if the conditions are right, we can bump the cardinality + if (cardinalityNext > cardinality && index == (cardinality - 1)) { + cardinalityUpdated = cardinalityNext; + } else { + cardinalityUpdated = cardinality; + } + + indexUpdated = (index + 1) % cardinalityUpdated; + self[indexUpdated] = transform(last, blockTimestamp, tick, liquidity); + } + + /// @notice Prepares the oracle array to store up to `next` observations + /// @param self The stored oracle array + /// @param current The current next cardinality of the oracle array + /// @param next The proposed next cardinality which will be populated in the oracle array + /// @return next The next cardinality which will be populated in the oracle array + function grow( + Observation[65535] storage self, + uint16 current, + uint16 next + ) internal returns (uint16) { + require(current > 0, 'I'); + // no-op if the passed next value isn't greater than the current next value + if (next <= current) return current; + // store in each slot to prevent fresh SSTOREs in swaps + // this data will not be used because the initialized boolean is still false + for (uint16 i = current; i < next; i++) self[i].blockTimestamp = 1; + return next; + } + + /// @notice comparator for 32-bit timestamps + /// @dev safe for 0 or 1 overflows, a and b _must_ be chronologically before or equal to time + /// @param time A timestamp truncated to 32 bits + /// @param a A comparison timestamp from which to determine the relative position of `time` + /// @param b From which to determine the relative position of `time` + /// @return bool Whether `a` is chronologically <= `b` + function lte( + uint32 time, + uint32 a, + uint32 b + ) private pure returns (bool) { + // if there hasn't been overflow, no need to adjust + if (a <= time && b <= time) return a <= b; + + uint256 aAdjusted = a > time ? a : a + 2**32; + uint256 bAdjusted = b > time ? b : b + 2**32; + + return aAdjusted <= bAdjusted; + } + + /// @notice Fetches the observations beforeOrAt and atOrAfter a target, i.e. where [beforeOrAt, atOrAfter] is satisfied. + /// The result may be the same observation, or adjacent observations. + /// @dev The answer must be contained in the array, used when the target is located within the stored observation + /// boundaries: older than the most recent observation and younger, or the same age as, the oldest observation + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param target The timestamp at which the reserved observation should be for + /// @param index The index of the observation that was most recently written to the observations array + /// @param cardinality The number of populated elements in the oracle array + /// @return beforeOrAt The observation recorded before, or at, the target + /// @return atOrAfter The observation recorded at, or after, the target + function binarySearch( + Observation[65535] storage self, + uint32 time, + uint32 target, + uint16 index, + uint16 cardinality + ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { + uint256 l = (index + 1) % cardinality; // oldest observation + uint256 r = l + cardinality - 1; // newest observation + uint256 i; + while (true) { + i = (l + r) / 2; + + beforeOrAt = self[i % cardinality]; + + // we've landed on an uninitialized tick, keep searching higher (more recently) + if (!beforeOrAt.initialized) { + l = i + 1; + continue; + } + + atOrAfter = self[(i + 1) % cardinality]; + + bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target); + + // check if we've found the answer! + if (targetAtOrAfter && lte(time, target, atOrAfter.blockTimestamp)) break; + + if (!targetAtOrAfter) r = i - 1; + else l = i + 1; + } + } + + /// @notice Fetches the observations beforeOrAt and atOrAfter a given target, i.e. where [beforeOrAt, atOrAfter] is satisfied + /// @dev Assumes there is at least 1 initialized observation. + /// Used by observeSingle() to compute the counterfactual accumulator values as of a given block timestamp. + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param target The timestamp at which the reserved observation should be for + /// @param tick The active tick at the time of the returned or simulated observation + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The total pool liquidity at the time of the call + /// @param cardinality The number of populated elements in the oracle array + /// @return beforeOrAt The observation which occurred at, or before, the given timestamp + /// @return atOrAfter The observation which occurred at, or after, the given timestamp + function getSurroundingObservations( + Observation[65535] storage self, + uint32 time, + uint32 target, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { + // optimistically set before to the newest observation + beforeOrAt = self[index]; + + // if the target is chronologically at or after the newest observation, we can early return + if (lte(time, beforeOrAt.blockTimestamp, target)) { + if (beforeOrAt.blockTimestamp == target) { + // if newest observation equals target, we're in the same block, so we can ignore atOrAfter + return (beforeOrAt, atOrAfter); + } else { + // otherwise, we need to transform + return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity)); + } + } + + // now, set before to the oldest observation + beforeOrAt = self[(index + 1) % cardinality]; + if (!beforeOrAt.initialized) beforeOrAt = self[0]; + + // ensure that the target is chronologically at or after the oldest observation + require(lte(time, beforeOrAt.blockTimestamp, target), 'OLD'); + + // if we've reached this point, we have to binary search + return binarySearch(self, time, target, index, cardinality); + } + + /// @dev Reverts if an observation at or before the desired observation timestamp does not exist. + /// 0 may be passed as `secondsAgo' to return the current cumulative values. + /// If called with a timestamp falling between two observations, returns the counterfactual accumulator values + /// at exactly the timestamp between the two observations. + /// @param self The stored oracle array + /// @param time The current block timestamp + /// @param secondsAgo The amount of time to look back, in seconds, at which point to return an observation + /// @param tick The current tick + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The current in-range pool liquidity + /// @param cardinality The number of populated elements in the oracle array + /// @return tickCumulative The tick * time elapsed since the pool was first initialized, as of `secondsAgo` + /// @return secondsPerLiquidityCumulativeX128 The time elapsed / max(1, liquidity) since the pool was first initialized, as of `secondsAgo` + function observeSingle( + Observation[65535] storage self, + uint32 time, + uint32 secondsAgo, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) internal view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) { + unchecked { + if (secondsAgo == 0) { + Observation memory last = self[index]; + if (last.blockTimestamp != time) last = transform(last, time, tick, liquidity); + return (last.tickCumulative, last.secondsPerLiquidityCumulativeX128); + } + + uint32 target = time - secondsAgo; + + (Observation memory beforeOrAt, Observation memory atOrAfter) = + getSurroundingObservations(self, time, target, tick, index, liquidity, cardinality); + + if (target == beforeOrAt.blockTimestamp) { + // we're at the left boundary + return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128); + } else if (target == atOrAfter.blockTimestamp) { + // we're at the right boundary + return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128); + } else { + // we're in the middle + uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp; + uint32 targetDelta = target - beforeOrAt.blockTimestamp; + return ( + beforeOrAt.tickCumulative + + ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / int56(uint56(observationTimeDelta))) * + int56(uint56(targetDelta)), + beforeOrAt.secondsPerLiquidityCumulativeX128 + + uint160( + (uint256( + atOrAfter.secondsPerLiquidityCumulativeX128 - beforeOrAt.secondsPerLiquidityCumulativeX128 + ) * targetDelta) / observationTimeDelta + ) + ); + } + } + } + + /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` + /// @dev Reverts if `secondsAgos` > oldest observation + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an observation + /// @param tick The current tick + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The current in-range pool liquidity + /// @param cardinality The number of populated elements in the oracle array + /// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo` + /// @return secondsPerLiquidityCumulativeX128s The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo` + function observe( + Observation[65535] storage self, + uint32 time, + uint32[] memory secondsAgos, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) { + require(cardinality > 0, 'I'); + + tickCumulatives = new int56[](secondsAgos.length); + secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length); + for (uint256 i = 0; i < secondsAgos.length; i++) { + (tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) = observeSingle( + self, + time, + secondsAgos[i], + tick, + index, + liquidity, + cardinality + ); + } + } +} + + +// File contracts/interfaces/IUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +/// @title An interface for a contract that is capable of deploying Uniswap V3 Pools +/// @notice A contract that constructs a pool must implement this to pass arguments to the pool +/// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash +/// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain +interface IUniswapV3PoolDeployer { + /// @notice Get the parameters to be used in constructing the pool, set transiently during pool creation. + /// @dev Called by the pool constructor to fetch the parameters of the pool + /// Returns factory The factory address + /// Returns token0 The first token of the pool by address sort order + /// Returns token1 The second token of the pool by address sort order + /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// Returns tickSpacing The minimum number of ticks between initialized ticks + function parameters() + external + view + returns ( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ); +} + + +// File contracts/interfaces/IUniswapV3Factory.sol + + +pragma solidity >=0.0; + +/// @title The interface for the Uniswap V3 Factory +/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees +interface IUniswapV3Factory { + /// @notice Emitted when the owner of the factory is changed + /// @param oldOwner The owner before the owner was changed + /// @param newOwner The owner after the owner was changed + event OwnerChanged(address indexed oldOwner, address indexed newOwner); + + /// @notice Emitted when a pool is created + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param pool The address of the created pool + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed fee, + int24 tickSpacing, + address pool + ); + + /// @notice Emitted when a new fee amount is enabled for pool creation via the factory + /// @param fee The enabled fee, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee + event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); + + /// @notice Returns the current owner of the factory + /// @dev Can be changed by the current owner via setOwner + /// @return The address of the factory owner + function owner() external view returns (address); + + /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled + /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context + /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee + /// @return The tick spacing + function feeAmountTickSpacing(uint24 fee) external view returns (int24); + + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The pool address + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + /// @notice Creates a pool for the given two tokens and fee + /// @param tokenA One of the two tokens in the desired pool + /// @param tokenB The other of the two tokens in the desired pool + /// @param fee The desired fee for the pool + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved + /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments + /// are invalid. + /// @return pool The address of the newly created pool + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) external returns (address pool); + + /// @notice Updates the owner of the factory + /// @dev Must be called by the current owner + /// @param _owner The new owner of the factory + function setOwner(address _owner) external; + + /// @notice Enables a fee amount with the given tickSpacing + /// @dev Fee amounts may never be removed once enabled + /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) + /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount + function enableFeeAmount(uint24 fee, int24 tickSpacing) external; +} + + +// File contracts/interfaces/callback/IUniswapV3MintCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#mint +/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface +interface IUniswapV3MintCallback { + /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint. + /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity + /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3SwapCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3FlashCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#flash +/// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface +interface IUniswapV3FlashCallback { + /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. + /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param fee0 The fee amount in token0 due to the pool by the end of the flash + /// @param fee1 The fee amount in token1 due to the pool by the end of the flash + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + + +// File contracts/UniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + + + + + + + + + + + + +contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { + using LowGasSafeMath for uint256; + using LowGasSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + using Tick for mapping(int24 => Tick.Info); + using TickBitmap for mapping(int16 => uint256); + using Position for mapping(bytes32 => Position.Info); + using Position for Position.Info; + using Oracle for Oracle.Observation[65535]; + + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override factory; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token0; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token1; + /// @inheritdoc IUniswapV3PoolImmutables + uint24 public immutable override fee; + + /// @inheritdoc IUniswapV3PoolImmutables + int24 public immutable override tickSpacing; + + /// @inheritdoc IUniswapV3PoolImmutables + uint128 public immutable override maxLiquidityPerTick; + + struct Slot0 { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // the most-recently updated index of the observations array + uint16 observationIndex; + // the current maximum number of observations that are being stored + uint16 observationCardinality; + // the next maximum number of observations to store, triggered in observations.write + uint16 observationCardinalityNext; + // the current protocol fee as a percentage of the swap fee taken on withdrawal + // represented as an integer denominator (1/x)% + uint8 feeProtocol; + // whether the pool is locked + bool unlocked; + } + /// @inheritdoc IUniswapV3PoolState + Slot0 public override slot0; + + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal0X128; + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal1X128; + + // accumulated protocol fees in token0/token1 units + struct ProtocolFees { + uint128 token0; + uint128 token1; + } + /// @inheritdoc IUniswapV3PoolState + ProtocolFees public override protocolFees; + + /// @inheritdoc IUniswapV3PoolState + uint128 public override liquidity; + + /// @inheritdoc IUniswapV3PoolState + mapping(int24 => Tick.Info) public override ticks; + /// @inheritdoc IUniswapV3PoolState + mapping(int16 => uint256) public override tickBitmap; + /// @inheritdoc IUniswapV3PoolState + mapping(bytes32 => Position.Info) public override positions; + /// @inheritdoc IUniswapV3PoolState + Oracle.Observation[65535] public override observations; + + /// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance + /// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because + /// we use balance checks to determine the payment status of interactions such as mint, swap and flash. + modifier lock() { + require(slot0.unlocked, 'LOK'); + slot0.unlocked = false; + _; + slot0.unlocked = true; + } + + /// @dev Prevents calling a function from anyone except the address returned by IUniswapV3Factory#owner() + modifier onlyFactoryOwner() { + require(msg.sender == IUniswapV3Factory(factory).owner()); + _; + } + + constructor() { + int24 _tickSpacing; + (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters(); + tickSpacing = _tickSpacing; + + maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing); + } + + /// @dev Common checks for valid tick inputs. + function checkTicks(int24 tickLower, int24 tickUpper) private pure { + require(tickLower < tickUpper, 'TLU'); + require(tickLower >= TickMath.MIN_TICK, 'TLM'); + require(tickUpper <= TickMath.MAX_TICK, 'TUM'); + } + + /// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests. + function _blockTimestamp() internal view virtual returns (uint32) { + return uint32(block.timestamp); // truncation is desired + } + + /// @dev Get the pool's balance of token0 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance0() private view returns (uint256) { + (bool success, bytes memory data) = + token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @dev Get the pool's balance of token1 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance1() private view returns (uint256) { + (bool success, bytes memory data) = + token1.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + override + noDelegateCall + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ) + { + unchecked { + checkTicks(tickLower, tickUpper); + + int56 tickCumulativeLower; + int56 tickCumulativeUpper; + uint160 secondsPerLiquidityOutsideLowerX128; + uint160 secondsPerLiquidityOutsideUpperX128; + uint32 secondsOutsideLower; + uint32 secondsOutsideUpper; + + { + Tick.Info storage lower = ticks[tickLower]; + Tick.Info storage upper = ticks[tickUpper]; + bool initializedLower; + (tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = ( + lower.tickCumulativeOutside, + lower.secondsPerLiquidityOutsideX128, + lower.secondsOutside, + lower.initialized + ); + require(initializedLower); + + bool initializedUpper; + (tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = ( + upper.tickCumulativeOutside, + upper.secondsPerLiquidityOutsideX128, + upper.secondsOutside, + upper.initialized + ); + require(initializedUpper); + } + + Slot0 memory _slot0 = slot0; + + if (_slot0.tick < tickLower) { + return ( + tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128, + secondsOutsideLower - secondsOutsideUpper + ); + } else if (_slot0.tick < tickUpper) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + _slot0.tick, + _slot0.observationIndex, + liquidity, + _slot0.observationCardinality + ); + return ( + tickCumulative - tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityCumulativeX128 - + secondsPerLiquidityOutsideLowerX128 - + secondsPerLiquidityOutsideUpperX128, + time - secondsOutsideLower - secondsOutsideUpper + ); + } else { + return ( + tickCumulativeUpper - tickCumulativeLower, + secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128, + secondsOutsideUpper - secondsOutsideLower + ); + } + } + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function observe(uint32[] calldata secondsAgos) + external + view + override + noDelegateCall + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return + observations.observe( + _blockTimestamp(), + secondsAgos, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + } + + /// @inheritdoc IUniswapV3PoolActions + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) + external + override + lock + noDelegateCall + { + uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event + uint16 observationCardinalityNextNew = + observations.grow(observationCardinalityNextOld, observationCardinalityNext); + slot0.observationCardinalityNext = observationCardinalityNextNew; + if (observationCardinalityNextOld != observationCardinalityNextNew) + emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew); + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev not locked because it initializes unlocked + function initialize(uint160 sqrtPriceX96) external override { + require(slot0.sqrtPriceX96 == 0, 'AI'); + + int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); + + (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp()); + + slot0 = Slot0({ + sqrtPriceX96: sqrtPriceX96, + tick: tick, + observationIndex: 0, + observationCardinality: cardinality, + observationCardinalityNext: cardinalityNext, + feeProtocol: 0, + unlocked: true + }); + + emit Initialize(sqrtPriceX96, tick); + } + + struct ModifyPositionParams { + // the address that owns the position + address owner; + // the lower and upper tick of the position + int24 tickLower; + int24 tickUpper; + // any change in liquidity + int128 liquidityDelta; + } + + /// @dev Effect some changes to a position + /// @param params the position details and the change to the position's liquidity to effect + /// @return position a storage pointer referencing the position with the given owner and tick range + /// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient + /// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient + function _modifyPosition(ModifyPositionParams memory params) + private + noDelegateCall + returns ( + Position.Info storage position, + int256 amount0, + int256 amount1 + ) + { + checkTicks(params.tickLower, params.tickUpper); + + Slot0 memory _slot0 = slot0; // SLOAD for gas optimization + + position = _updatePosition( + params.owner, + params.tickLower, + params.tickUpper, + params.liquidityDelta, + _slot0.tick + ); + + if (params.liquidityDelta != 0) { + if (_slot0.tick < params.tickLower) { + // current tick is below the passed range; liquidity can only become in range by crossing from left to + // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it + amount0 = SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } else if (_slot0.tick < params.tickUpper) { + // current tick is inside the passed range + uint128 liquidityBefore = liquidity; // SLOAD for gas optimization + + // write an oracle entry + (slot0.observationIndex, slot0.observationCardinality) = observations.write( + _slot0.observationIndex, + _blockTimestamp(), + _slot0.tick, + liquidityBefore, + _slot0.observationCardinality, + _slot0.observationCardinalityNext + ); + + amount0 = SqrtPriceMath.getAmount0Delta( + _slot0.sqrtPriceX96, + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + _slot0.sqrtPriceX96, + params.liquidityDelta + ); + + liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta); + } else { + // current tick is above the passed range; liquidity can only become in range by crossing from right to + // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } + } + } + + /// @dev Gets and updates a position with the given liquidity delta + /// @param owner the owner of the position + /// @param tickLower the lower tick of the position's tick range + /// @param tickUpper the upper tick of the position's tick range + /// @param tick the current tick, passed to avoid sloads + function _updatePosition( + address owner, + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta, + int24 tick + ) private returns (Position.Info storage position) { + position = positions.get(owner, tickLower, tickUpper); + + uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization + uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization + + // if we need to update the ticks, do it + bool flippedLower; + bool flippedUpper; + if (liquidityDelta != 0) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + + flippedLower = ticks.update( + tickLower, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + false, + maxLiquidityPerTick + ); + flippedUpper = ticks.update( + tickUpper, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + true, + maxLiquidityPerTick + ); + + if (flippedLower) { + tickBitmap.flipTick(tickLower, tickSpacing); + } + if (flippedUpper) { + tickBitmap.flipTick(tickUpper, tickSpacing); + } + } + + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = + ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128); + + position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128); + + // clear any tick data that is no longer needed + if (liquidityDelta < 0) { + if (flippedLower) { + ticks.clear(tickLower); + } + if (flippedUpper) { + ticks.clear(tickUpper); + } + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external override lock returns (uint256 amount0, uint256 amount1) { + require(amount > 0); + (, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: recipient, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(amount0Int); + amount1 = uint256(amount1Int); + + uint256 balance0Before; + uint256 balance1Before; + if (amount0 > 0) balance0Before = balance0(); + if (amount1 > 0) balance1Before = balance1(); + IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data); + if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0'); + if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1'); + + emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1); + } + + /// @inheritdoc IUniswapV3PoolActions + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock returns (uint128 amount0, uint128 amount1) { + unchecked { + // we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1} + Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper); + + amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested; + amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested; + + if (amount0 > 0) { + position.tokensOwed0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + position.tokensOwed1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1); + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external override lock returns (uint256 amount0, uint256 amount1) { + (Position.Info storage position, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: msg.sender, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: -int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(-amount0Int); + amount1 = uint256(-amount1Int); + + if (amount0 > 0 || amount1 > 0) { + (position.tokensOwed0, position.tokensOwed1) = ( + position.tokensOwed0 + uint128(amount0), + position.tokensOwed1 + uint128(amount1) + ); + } + + emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1); + } + + struct SwapCache { + // the protocol fee for the input token + uint8 feeProtocol; + // liquidity at the beginning of the swap + uint128 liquidityStart; + // the timestamp of the current block + uint32 blockTimestamp; + // the current value of the tick accumulator, computed only if we cross an initialized tick + int56 tickCumulative; + // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick + uint160 secondsPerLiquidityCumulativeX128; + // whether we've computed and cached the above two accumulators + bool computedLatestObservation; + } + + // the top level state of the swap, the results of which are recorded in storage at the end + struct SwapState { + // the amount remaining to be swapped in/out of the input/output asset + int256 amountSpecifiedRemaining; + // the amount already swapped out/in of the output/input asset + int256 amountCalculated; + // current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // the global fee growth of the input token + uint256 feeGrowthGlobalX128; + // amount of input token paid as protocol fee + uint128 protocolFee; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + } + + /// @inheritdoc IUniswapV3PoolActions + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external override noDelegateCall returns (int256 amount0, int256 amount1) { + unchecked { + require(amountSpecified != 0, 'AS'); + + Slot0 memory slot0Start = slot0; + + require(slot0Start.unlocked, 'LOK'); + require( + zeroForOne + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + 'SPL' + ); + + slot0.unlocked = false; + + SwapCache memory cache = + SwapCache({ + liquidityStart: liquidity, + blockTimestamp: _blockTimestamp(), + feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4), + secondsPerLiquidityCumulativeX128: 0, + tickCumulative: 0, + computedLatestObservation: false + }); + + bool exactInput = amountSpecified > 0; + + SwapState memory state = + SwapState({ + amountSpecifiedRemaining: amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128, + protocolFee: 0, + liquidity: cache.liquidityStart + }); + + // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) { + StepComputations memory step; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord( + state.tick, + tickSpacing, + zeroForOne + ); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + + // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted + (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( + state.sqrtPriceX96, + (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + fee + ); + + if (exactInput) { + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); + state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256()); + } else { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256()); + } + + // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee + if (cache.feeProtocol > 0) { + uint256 delta = step.feeAmount / cache.feeProtocol; + step.feeAmount -= delta; + state.protocolFee += uint128(delta); + } + + // update global fee tracker + if (state.liquidity > 0) + state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity); + + // shift tick if we reached the next price + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + // check for the placeholder value, which we replace with the actual value the first time the swap + // crosses an initialized tick + if (!cache.computedLatestObservation) { + (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle( + cache.blockTimestamp, + 0, + slot0Start.tick, + slot0Start.observationIndex, + cache.liquidityStart, + slot0Start.observationCardinality + ); + cache.computedLatestObservation = true; + } + int128 liquidityNet = + ticks.cross( + step.tickNext, + (zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128), + (zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128), + cache.secondsPerLiquidityCumulativeX128, + cache.tickCumulative, + cache.blockTimestamp + ); + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); + } + + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + } + } + + // update tick and write an oracle entry if the tick change + if (state.tick != slot0Start.tick) { + (uint16 observationIndex, uint16 observationCardinality) = + observations.write( + slot0Start.observationIndex, + cache.blockTimestamp, + slot0Start.tick, + cache.liquidityStart, + slot0Start.observationCardinality, + slot0Start.observationCardinalityNext + ); + (slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = ( + state.sqrtPriceX96, + state.tick, + observationIndex, + observationCardinality + ); + } else { + // otherwise just update the price + slot0.sqrtPriceX96 = state.sqrtPriceX96; + } + + // update liquidity if it changed + if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity; + + // update fee growth global and, if necessary, protocol fees + // overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees + if (zeroForOne) { + feeGrowthGlobal0X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee; + } else { + feeGrowthGlobal1X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee; + } + + (amount0, amount1) = zeroForOne == exactInput + ? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated) + : (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining); + + // do the transfers and collect payment + if (zeroForOne) { + if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1)); + + uint256 balance0Before = balance0(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA'); + } else { + if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0)); + + uint256 balance1Before = balance1(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA'); + } + + emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick); + slot0.unlocked = true; + } + } + + /// @inheritdoc IUniswapV3PoolActions + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external override lock noDelegateCall { + unchecked { + uint128 _liquidity = liquidity; + require(_liquidity > 0, 'L'); + + uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6); + uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6); + uint256 balance0Before = balance0(); + uint256 balance1Before = balance1(); + + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); + + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); + + uint256 balance0After = balance0(); + uint256 balance1After = balance1(); + + require(balance0Before.add(fee0) <= balance0After, 'F0'); + require(balance1Before.add(fee1) <= balance1After, 'F1'); + + // sub is safe because we know balanceAfter is gt balanceBefore by at least fee + uint256 paid0 = balance0After - balance0Before; + uint256 paid1 = balance1After - balance1Before; + + if (paid0 > 0) { + uint8 feeProtocol0 = slot0.feeProtocol % 16; + uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0; + if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0); + feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - fees0, FixedPoint128.Q128, _liquidity); + } + if (paid1 > 0) { + uint8 feeProtocol1 = slot0.feeProtocol >> 4; + uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1; + if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1); + feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - fees1, FixedPoint128.Q128, _liquidity); + } + + emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1); + } + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner { + require( + (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) && + (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)) + ); + uint8 feeProtocolOld = slot0.feeProtocol; + slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4); + emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1); + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) { + amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested; + amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested; + + if (amount0 > 0) { + if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings + protocolFees.token0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings + protocolFees.token1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit CollectProtocol(msg.sender, recipient, amount0, amount1); + } +} + + +// File contracts/test/MockTimeUniswapV3Pool.sol + + +pragma solidity >=0.0; + +// used for testing time dependent behavior +contract MockTimeUniswapV3Pool is UniswapV3Pool { + // Monday, October 5, 2020 9:00:00 AM GMT-05:00 + uint256 public time = 1601906400; + + function setFeeGrowthGlobal0X128(uint256 _feeGrowthGlobal0X128) external { + feeGrowthGlobal0X128 = _feeGrowthGlobal0X128; + } + + function setFeeGrowthGlobal1X128(uint256 _feeGrowthGlobal1X128) external { + feeGrowthGlobal1X128 = _feeGrowthGlobal1X128; + } + + function advanceTime(uint256 by) external { + time += by; + } + + function _blockTimestamp() internal view override returns (uint32) { + return uint32(time); + } +} + + +// File contracts/test/MockTimeUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract MockTimeUniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + Parameters public override parameters; + + event PoolDeployed(address pool); + + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) external returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address( + new MockTimeUniswapV3Pool{salt: keccak256(abi.encodePacked(token0, token1, fee, tickSpacing))}() + ); + emit PoolDeployed(pool); + delete parameters; + } +} + + +// File contracts/test/NoDelegateCallTest.sol + + +pragma solidity >=0.0; + +contract NoDelegateCallTest is NoDelegateCall { + function canBeDelegateCalled() public view returns (uint256) { + return block.timestamp / 5; + } + + function cannotBeDelegateCalled() public view noDelegateCall returns (uint256) { + return block.timestamp / 5; + } + + function getGasCostOfCanBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + canBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function getGasCostOfCannotBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + cannotBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function callsIntoNoDelegateCallFunction() external view { + noDelegateCallPrivate(); + } + + function noDelegateCallPrivate() private view noDelegateCall {} +} + + +// File contracts/test/OracleTest.sol + + +pragma solidity >=0.0; +pragma abicoder v2; + +contract OracleTest { + using Oracle for Oracle.Observation[65535]; + + Oracle.Observation[65535] public observations; + + uint32 public time; + int24 public tick; + uint128 public liquidity; + uint16 public index; + uint16 public cardinality; + uint16 public cardinalityNext; + + struct InitializeParams { + uint32 time; + int24 tick; + uint128 liquidity; + } + + function initialize(InitializeParams calldata params) external { + require(cardinality == 0, 'already initialized'); + time = params.time; + tick = params.tick; + liquidity = params.liquidity; + (cardinality, cardinalityNext) = observations.initialize(params.time); + } + + function advanceTime(uint32 by) public { + unchecked { + time += by; + } + } + + struct UpdateParams { + uint32 advanceTimeBy; + int24 tick; + uint128 liquidity; + } + + // write an observation, then change tick and liquidity + function update(UpdateParams calldata params) external { + advanceTime(params.advanceTimeBy); + (index, cardinality) = observations.write(index, time, tick, liquidity, cardinality, cardinalityNext); + tick = params.tick; + liquidity = params.liquidity; + } + + function batchUpdate(UpdateParams[] calldata params) external { + // sload everything + int24 _tick = tick; + uint128 _liquidity = liquidity; + uint16 _index = index; + uint16 _cardinality = cardinality; + uint16 _cardinalityNext = cardinalityNext; + uint32 _time = time; + + for (uint256 i = 0; i < params.length; i++) { + _time += params[i].advanceTimeBy; + (_index, _cardinality) = observations.write( + _index, + _time, + _tick, + _liquidity, + _cardinality, + _cardinalityNext + ); + _tick = params[i].tick; + _liquidity = params[i].liquidity; + } + + // sstore everything + tick = _tick; + liquidity = _liquidity; + index = _index; + cardinality = _cardinality; + time = _time; + } + + function grow(uint16 _cardinalityNext) external { + cardinalityNext = observations.grow(cardinalityNext, _cardinalityNext); + } + + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return observations.observe(time, secondsAgos, tick, index, liquidity, cardinality); + } + + function getGasCostOfObserve(uint32[] calldata secondsAgos) external view returns (uint256) { + (uint32 _time, int24 _tick, uint128 _liquidity, uint16 _index) = (time, tick, liquidity, index); + uint256 gasBefore = gasleft(); + observations.observe(_time, secondsAgos, _tick, _index, _liquidity, cardinality); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/OracleEchidnaTest.sol + + +pragma solidity >=0.0; + +contract OracleEchidnaTest { + OracleTest private oracle; + + bool private initialized; + uint32 private timePassed; + + constructor() { + oracle = new OracleTest(); + } + + function initialize( + uint32 time, + int24 tick, + uint128 liquidity + ) external { + oracle.initialize(OracleTest.InitializeParams({time: time, tick: tick, liquidity: liquidity})); + initialized = true; + } + + function limitTimePassed(uint32 by) private { + require(timePassed + by >= timePassed); + timePassed += by; + } + + function advanceTime(uint32 by) public { + limitTimePassed(by); + oracle.advanceTime(by); + } + + // write an observation, then change tick and liquidity + function update( + uint32 advanceTimeBy, + int24 tick, + uint128 liquidity + ) external { + limitTimePassed(advanceTimeBy); + oracle.update(OracleTest.UpdateParams({advanceTimeBy: advanceTimeBy, tick: tick, liquidity: liquidity})); + } + + function grow(uint16 cardinality) external { + oracle.grow(cardinality); + } + + function checkTimeWeightedResultAssertions(uint32 secondsAgo0, uint32 secondsAgo1) private view { + require(secondsAgo0 != secondsAgo1); + require(initialized); + // secondsAgo0 should be the larger one + if (secondsAgo0 < secondsAgo1) (secondsAgo0, secondsAgo1) = (secondsAgo1, secondsAgo0); + + uint32 timeElapsed = secondsAgo0 - secondsAgo1; + + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo0; + secondsAgos[1] = secondsAgo1; + + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + int56 timeWeightedTick = (tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(timeElapsed)); + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(timeElapsed) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + assert(timeWeightedTick <= type(int24).max); + assert(timeWeightedTick >= type(int24).min); + } + + function echidna_indexAlwaysLtCardinality() external view returns (bool) { + return oracle.index() < oracle.cardinality() || !initialized; + } + + function echidna_AlwaysInitialized() external view returns (bool) { + (, , , bool isInitialized) = oracle.observations(0); + return oracle.cardinality() == 0 || isInitialized; + } + + function echidna_cardinalityAlwaysLteNext() external view returns (bool) { + return oracle.cardinality() <= oracle.cardinalityNext(); + } + + function echidna_canAlwaysObserve0IfInitialized() external view returns (bool) { + if (!initialized) { + return true; + } + uint32[] memory arr = new uint32[](1); + arr[0] = 0; + (bool success, ) = address(oracle).staticcall(abi.encodeWithSelector(OracleTest.observe.selector, arr)); + return success; + } + + function checkTwoAdjacentObservationsTickCumulativeModTimeElapsedAlways0(uint16 index) external view { + uint16 cardinality = oracle.cardinality(); + // check that the observations are initialized, and that the index is not the oldest observation + require(index < cardinality && index != (oracle.index() + 1) % cardinality); + + (uint32 blockTimestamp0, int56 tickCumulative0, , bool initialized0) = + oracle.observations(index == 0 ? cardinality - 1 : index - 1); + (uint32 blockTimestamp1, int56 tickCumulative1, , bool initialized1) = oracle.observations(index); + + require(initialized0); + require(initialized1); + + uint32 timeElapsed = blockTimestamp1 - blockTimestamp0; + assert(timeElapsed > 0); + assert((tickCumulative1 - tickCumulative0) % int56(uint56(timeElapsed)) == 0); + } + + function checkTimeWeightedAveragesAlwaysFitsType(uint32 secondsAgo) external view { + require(initialized); + require(secondsAgo > 0); + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo; + secondsAgos[1] = 0; + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + + // compute the time weighted tick, rounded towards negative infinity + int56 numerator = tickCumulatives[1] - tickCumulatives[0]; + int56 timeWeightedTick = numerator / int56(uint56(secondsAgo)); + if (numerator < 0 && numerator % int56(uint56(secondsAgo)) != 0) { + timeWeightedTick--; + } + + // the time weighted averages fit in their respective accumulated types + assert(timeWeightedTick <= type(int24).max && timeWeightedTick >= type(int24).min); + + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(secondsAgo) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + } +} + + +// File contracts/test/SqrtPriceMathEchidnaTest.sol + + +pragma solidity >=0.0; + + + +contract SqrtPriceMathEchidnaTest { + function mulDivRoundingUpInvariants( + uint256 x, + uint256 y, + uint256 z + ) external pure { + require(z > 0); + uint256 notRoundedUp = FullMath.mulDiv(x, y, z); + uint256 roundedUp = FullMath.mulDivRoundingUp(x, y, z); + assert(roundedUp >= notRoundedUp); + assert(roundedUp - notRoundedUp < 2); + if (roundedUp - notRoundedUp == 1) { + assert(mulmod(x, y, z) > 0); + } else { + assert(mulmod(x, y, z) == 0); + } + } + + function getNextSqrtPriceFromInputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true)); + } else { + assert(sqrtQ >= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true)); + } + } + + function getNextSqrtPriceFromOutputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + } else { + assert(sqrtQ > 0); // this has to be true, otherwise we need another require + assert(sqrtQ >= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + } + } + + function getNextSqrtPriceFromAmount0RoundingUpInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 <= sqrtPX96); + } else { + assert(sqrtQX96 >= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getNextSqrtPriceFromAmount1RoundingDownInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 >= sqrtPX96); + } else { + assert(sqrtQX96 <= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getAmount0DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount0Down = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, false); + assert(amount0Down == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + + uint256 amount0Up = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true); + assert(amount0Up == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, true)); + + assert(amount0Down <= amount0Up); + // diff is 0 or 1 + assert(amount0Up - amount0Down < 2); + } + + // ensure that chained division is always equal to the full-precision case for + // liquidity * (sqrt(P) - sqrt(Q)) / (sqrt(P) * sqrt(Q)) + function getAmount0DeltaEquivalency( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity, + bool roundUp + ) external pure { + require(sqrtP >= sqrtQ); + require(sqrtP > 0 && sqrtQ > 0); + require((sqrtP * sqrtQ) / sqrtP == sqrtQ); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtP - sqrtQ; + uint256 denominator = uint256(sqrtP) * sqrtQ; + + uint256 safeResult = + roundUp + ? FullMath.mulDivRoundingUp(numerator1, numerator2, denominator) + : FullMath.mulDiv(numerator1, numerator2, denominator); + uint256 fullResult = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, roundUp); + + assert(safeResult == fullResult); + } + + function getAmount1DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount1Down = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, false); + assert(amount1Down == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + + uint256 amount1Up = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true); + assert(amount1Up == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, true)); + + assert(amount1Down <= amount1Up); + // diff is 0 or 1 + assert(amount1Up - amount1Down < 2); + } + + function getAmount0DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity); + if (liquidity < 0) assert(amount0 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount0 == 0); + else assert(amount0 > 0); + } + if (liquidity == 0) assert(amount0 == 0); + } + + function getAmount1DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity); + if (liquidity < 0) assert(amount1 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount1 == 0); + else assert(amount1 > 0); + } + if (liquidity == 0) assert(amount1 == 0); + } + + function getOutOfRangeMintInvariants( + uint160 sqrtA, + uint160 sqrtB, + int128 liquidity + ) external pure { + require(sqrtA > 0 && sqrtB > 0); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtA, sqrtB, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtA, sqrtB, liquidity); + + if (sqrtA == sqrtB) { + assert(amount0 == 0); + assert(amount1 == 0); + } else { + assert(amount0 > 0); + assert(amount1 > 0); + } + } + + function getInRangeMintInvariants( + uint160 sqrtLower, + uint160 sqrtCurrent, + uint160 sqrtUpper, + int128 liquidity + ) external pure { + require(sqrtLower > 0); + require(sqrtLower < sqrtUpper); + require(sqrtLower <= sqrtCurrent && sqrtCurrent <= sqrtUpper); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtCurrent, sqrtUpper, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtCurrent, liquidity); + + assert(amount0 > 0 || amount1 > 0); + } +} + + +// File contracts/test/SqrtPriceMathTest.sol + + +pragma solidity >=0.0; + +contract SqrtPriceMathTest { + function getNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + return gasBefore - gasleft(); + } + + function getNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + return gasBefore - gasleft(); + } + + function getAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount0) { + return SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount1) { + return SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getGasCostOfGetAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } + + function getGasCostOfGetAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/SwapMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract SwapMathEchidnaTest { + function checkComputeSwapStepInvariants( + uint160 sqrtPriceRaw, + uint160 sqrtPriceTargetRaw, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external pure { + require(sqrtPriceRaw > 0); + require(sqrtPriceTargetRaw > 0); + require(feePips > 0); + require(feePips < 1e6); + + (uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = + SwapMath.computeSwapStep(sqrtPriceRaw, sqrtPriceTargetRaw, liquidity, amountRemaining, feePips); + + assert(amountIn <= type(uint256).max - feeAmount); + + if (amountRemaining < 0) { + assert(amountOut <= uint256(-amountRemaining)); + } else { + assert(amountIn + feeAmount <= uint256(amountRemaining)); + } + + if (sqrtPriceRaw == sqrtPriceTargetRaw) { + assert(amountIn == 0); + assert(amountOut == 0); + assert(feeAmount == 0); + assert(sqrtQ == sqrtPriceTargetRaw); + } + + // didn't reach price target, entire amount must be consumed + if (sqrtQ != sqrtPriceTargetRaw) { + if (amountRemaining < 0) assert(amountOut == uint256(-amountRemaining)); + else assert(amountIn + feeAmount == uint256(amountRemaining)); + } + + // next price is between price and price target + if (sqrtPriceTargetRaw <= sqrtPriceRaw) { + assert(sqrtQ <= sqrtPriceRaw); + assert(sqrtQ >= sqrtPriceTargetRaw); + } else { + assert(sqrtQ >= sqrtPriceRaw); + assert(sqrtQ <= sqrtPriceTargetRaw); + } + } +} + + +// File contracts/test/SwapMathTest.sol + + +pragma solidity >=0.0; + +contract SwapMathTest { + function computeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + external + pure + returns ( + uint160 sqrtQ, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + return SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + } + + function getGasCostOfComputeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/TestERC20.sol + + +pragma solidity >=0.0; + +contract TestERC20 is IERC20Minimal { + mapping(address => uint256) public override balanceOf; + mapping(address => mapping(address => uint256)) public override allowance; + + constructor(uint256 amountToMint) { + mint(msg.sender, amountToMint); + } + + function mint(address to, uint256 amount) public { + uint256 balanceNext = balanceOf[to] + amount; + require(balanceNext >= amount, 'overflow balance'); + balanceOf[to] = balanceNext; + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + uint256 balanceBefore = balanceOf[msg.sender]; + require(balanceBefore >= amount, 'insufficient balance'); + balanceOf[msg.sender] = balanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'recipient balance overflow'); + balanceOf[recipient] = balanceRecipient + amount; + + emit Transfer(msg.sender, recipient, amount); + return true; + } + + function approve(address spender, uint256 amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external override returns (bool) { + uint256 allowanceBefore = allowance[sender][msg.sender]; + require(allowanceBefore >= amount, 'allowance insufficient'); + + allowance[sender][msg.sender] = allowanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'overflow balance recipient'); + balanceOf[recipient] = balanceRecipient + amount; + uint256 balanceSender = balanceOf[sender]; + require(balanceSender >= amount, 'underflow balance sender'); + balanceOf[sender] = balanceSender - amount; + + emit Transfer(sender, recipient, amount); + return true; + } +} + + +// File contracts/test/TestUniswapV3Callee.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Callee is IUniswapV3MintCallback, IUniswapV3SwapCallback, IUniswapV3FlashCallback { + using SafeCast for uint256; + + function swapExact0For1( + address pool, + uint256 amount0In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, amount0In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap0ForExact1( + address pool, + uint256 amount1Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, -amount1Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapExact1For0( + address pool, + uint256 amount1In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, amount1In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap1ForExact0( + address pool, + uint256 amount0Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, -amount0Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapToLowerSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, true, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + function swapToHigherSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, false, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit SwapCallback(amount0Delta, amount1Delta); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } else { + // if both are not gt 0, both must be 0. + assert(amount0Delta == 0 && amount1Delta == 0); + } + } + + function mint( + address pool, + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external { + IUniswapV3Pool(pool).mint(recipient, tickLower, tickUpper, amount, abi.encode(msg.sender)); + } + + event MintCallback(uint256 amount0Owed, uint256 amount1Owed); + + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit MintCallback(amount0Owed, amount1Owed); + if (amount0Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, amount0Owed); + if (amount1Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, amount1Owed); + } + + event FlashCallback(uint256 fee0, uint256 fee1); + + function flash( + address pool, + address recipient, + uint256 amount0, + uint256 amount1, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).flash(recipient, amount0, amount1, abi.encode(msg.sender, pay0, pay1)); + } + + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + emit FlashCallback(fee0, fee1); + + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, pay0); + if (pay1 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, pay1); + } +} + + +// File contracts/test/TestUniswapV3ReentrantCallee.sol + + +pragma solidity >=0.0; + +contract TestUniswapV3ReentrantCallee is IUniswapV3SwapCallback { + string private constant expectedReason = 'LOK'; + + function swapToReenter(address pool) external { + IUniswapV3Pool(pool).swap(address(0), false, 1, TickMath.MAX_SQRT_RATIO - 1, new bytes(0)); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata + ) external override { + // try to reenter swap + try IUniswapV3Pool(msg.sender).swap(address(0), false, 1, 0, new bytes(0)) {} catch Error( + string memory reason + ) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter mint + try IUniswapV3Pool(msg.sender).mint(address(0), 0, 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collect + try IUniswapV3Pool(msg.sender).collect(address(0), 0, 0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter burn + try IUniswapV3Pool(msg.sender).burn(0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter flash + try IUniswapV3Pool(msg.sender).flash(address(0), 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collectProtocol + try IUniswapV3Pool(msg.sender).collectProtocol(address(0), 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + require(false, 'Unable to reenter'); + } +} + + +// File contracts/test/TestUniswapV3Router.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Router is IUniswapV3SwapCallback { + using SafeCast for uint256; + + // flash swaps for an exact amount of token0 in the output pool + function swapForExact0Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount0Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + false, + -amount0Out.toInt256(), + TickMath.MAX_SQRT_RATIO - 1, + abi.encode(pools, msg.sender) + ); + } + + // flash swaps for an exact amount of token1 in the output pool + function swapForExact1Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount1Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + true, + -amount1Out.toInt256(), + TickMath.MIN_SQRT_RATIO + 1, + abi.encode(pools, msg.sender) + ); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) public override { + emit SwapCallback(amount0Delta, amount1Delta); + + (address[] memory pools, address payer) = abi.decode(data, (address[], address)); + + if (pools.length == 1) { + // get the address and amount of the token that we need to pay + address tokenToBePaid = + amount0Delta > 0 ? IUniswapV3Pool(msg.sender).token0() : IUniswapV3Pool(msg.sender).token1(); + int256 amountToBePaid = amount0Delta > 0 ? amount0Delta : amount1Delta; + + bool zeroForOne = tokenToBePaid == IUniswapV3Pool(pools[0]).token1(); + IUniswapV3Pool(pools[0]).swap( + msg.sender, + zeroForOne, + -amountToBePaid, + zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + abi.encode(new address[](0), payer) + ); + } else { + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom( + payer, + msg.sender, + uint256(amount0Delta) + ); + } else { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom( + payer, + msg.sender, + uint256(amount1Delta) + ); + } + } + } +} + + +// File contracts/test/TestUniswapV3SwapPay.sol + + +pragma solidity >=0.0; + + +contract TestUniswapV3SwapPay is IUniswapV3SwapCallback { + function swap( + address pool, + address recipient, + bool zeroForOne, + uint160 sqrtPriceX96, + int256 amountSpecified, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).swap( + recipient, + zeroForOne, + amountSpecified, + sqrtPriceX96, + abi.encode(msg.sender, pay0, pay1) + ); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata data + ) external override { + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(pay0)); + } else if (pay1 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(pay1)); + } + } +} + + +// File contracts/test/TickBitmapEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapEchidnaTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) private bitmap; + + // returns whether the given tick is initialized + function isInitialized(int24 tick) private view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } + + function flipTick(int24 tick) external { + bool before = isInitialized(tick); + bitmap.flipTick(tick, 1); + assert(isInitialized(tick) == !before); + } + + function checkNextInitializedTickWithinOneWordInvariants(int24 tick, bool lte) external view { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + if (lte) { + // type(int24).min + 256 + require(tick >= -8388352); + assert(next <= tick); + assert(tick - next < 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick; i > next; i--) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } else { + // type(int24).max - 256 + require(tick < 8388351); + assert(next > tick); + assert(next - tick <= 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick + 1; i < next; i++) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } + } +} + + +// File contracts/test/TickBitmapTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) public bitmap; + + function flipTick(int24 tick) external { + bitmap.flipTick(tick, 1); + } + + function getGasCostOfFlipTick(int24 tick) external returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.flipTick(tick, 1); + return gasBefore - gasleft(); + } + + function nextInitializedTickWithinOneWord(int24 tick, bool lte) + external + view + returns (int24 next, bool initialized) + { + return bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + } + + function getGasCostOfNextInitializedTickWithinOneWord(int24 tick, bool lte) external view returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + return gasBefore - gasleft(); + } + + // returns whether the given tick is initialized + function isInitialized(int24 tick) external view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } +} + + +// File contracts/test/TickEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickEchidnaTest { + function checkTickSpacingToParametersInvariants(int24 tickSpacing) external pure { + require(tickSpacing <= TickMath.MAX_TICK); + require(tickSpacing > 0); + + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + + uint128 maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + + // symmetry around 0 tick + assert(maxTick == -minTick); + // positive max tick + assert(maxTick > 0); + // divisibility + assert((maxTick - minTick) % tickSpacing == 0); + + uint256 numTicks = uint256(int256((maxTick - minTick) / tickSpacing)) + 1; + // max liquidity at every tick is less than the cap + assert(uint256(maxLiquidityPerTick) * numTicks <= type(uint128).max); + } +} + + +// File contracts/test/TickMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickMathEchidnaTest { + // uniqueness and increasing order + function checkGetSqrtRatioAtTickInvariants(int24 tick) external pure { + uint160 ratio = TickMath.getSqrtRatioAtTick(tick); + assert(TickMath.getSqrtRatioAtTick(tick - 1) < ratio && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(ratio >= TickMath.MIN_SQRT_RATIO); + assert(ratio <= TickMath.MAX_SQRT_RATIO); + } + + // the ratio is always between the returned tick and the returned tick+1 + function checkGetTickAtSqrtRatioInvariants(uint160 ratio) external pure { + int24 tick = TickMath.getTickAtSqrtRatio(ratio); + assert(ratio >= TickMath.getSqrtRatioAtTick(tick) && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(tick >= TickMath.MIN_TICK); + assert(tick < TickMath.MAX_TICK); + } +} + + +// File contracts/test/TickMathTest.sol + + +pragma solidity >=0.0; + +contract TickMathTest { + function getSqrtRatioAtTick(int24 tick) external pure returns (uint160) { + return TickMath.getSqrtRatioAtTick(tick); + } + + function getGasCostOfGetSqrtRatioAtTick(int24 tick) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getSqrtRatioAtTick(tick); + return gasBefore - gasleft(); + } + + function getTickAtSqrtRatio(uint160 sqrtPriceX96) external pure returns (int24) { + return TickMath.getTickAtSqrtRatio(sqrtPriceX96); + } + + function getGasCostOfGetTickAtSqrtRatio(uint160 sqrtPriceX96) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getTickAtSqrtRatio(sqrtPriceX96); + return gasBefore - gasleft(); + } + + function MIN_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MIN_SQRT_RATIO; + } + + function MAX_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MAX_SQRT_RATIO; + } +} + + +// File contracts/test/TickOverflowSafetyEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickOverflowSafetyEchidnaTest { + using Tick for mapping(int24 => Tick.Info); + + int24 private constant MIN_TICK = -16; + int24 private constant MAX_TICK = 16; + uint128 private constant MAX_LIQUIDITY = type(uint128).max / 32; + + mapping(int24 => Tick.Info) private ticks; + int24 private tick = 0; + + // used to track how much total liquidity has been added. should never be negative + int256 totalLiquidity = 0; + // half the cap of fee growth has happened, this can overflow + uint256 private feeGrowthGlobal0X128 = type(uint256).max / 2; + uint256 private feeGrowthGlobal1X128 = type(uint256).max / 2; + // how much total growth has happened, this cannot overflow + uint256 private totalGrowth0 = 0; + uint256 private totalGrowth1 = 0; + + function increaseFeeGrowthGlobal0X128(uint256 amount) external { + require(totalGrowth0 + amount > totalGrowth0); // overflow check + feeGrowthGlobal0X128 += amount; // overflow desired + totalGrowth0 += amount; + } + + function increaseFeeGrowthGlobal1X128(uint256 amount) external { + require(totalGrowth1 + amount > totalGrowth1); // overflow check + feeGrowthGlobal1X128 += amount; // overflow desired + totalGrowth1 += amount; + } + + function setPosition( + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta + ) external { + require(tickLower > MIN_TICK); + require(tickUpper < MAX_TICK); + require(tickLower < tickUpper); + bool flippedLower = + ticks.update( + tickLower, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + false, + MAX_LIQUIDITY + ); + bool flippedUpper = + ticks.update( + tickUpper, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + true, + MAX_LIQUIDITY + ); + + if (flippedLower) { + if (liquidityDelta < 0) { + assert(ticks[tickLower].liquidityGross == 0); + ticks.clear(tickLower); + } else assert(ticks[tickLower].liquidityGross > 0); + } + + if (flippedUpper) { + if (liquidityDelta < 0) { + assert(ticks[tickUpper].liquidityGross == 0); + ticks.clear(tickUpper); + } else assert(ticks[tickUpper].liquidityGross > 0); + } + + totalLiquidity += liquidityDelta; + // requires should have prevented this + assert(totalLiquidity >= 0); + + if (totalLiquidity == 0) { + totalGrowth0 = 0; + totalGrowth1 = 0; + } + } + + function moveToTick(int24 target) external { + require(target > MIN_TICK); + require(target < MAX_TICK); + while (tick != target) { + if (tick < target) { + if (ticks[tick + 1].liquidityGross > 0) + ticks.cross(tick + 1, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick++; + } else { + if (ticks[tick].liquidityGross > 0) + ticks.cross(tick, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick--; + } + } + } +} + + +// File contracts/test/TickTest.sol + + +pragma solidity >=0.0; + +contract TickTest { + using Tick for mapping(int24 => Tick.Info); + + mapping(int24 => Tick.Info) public ticks; + + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) external pure returns (uint128) { + return Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + } + + function setTick(int24 tick, Tick.Info memory info) external { + ticks[tick] = info; + } + + function getFeeGrowthInside( + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) external view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + return ticks.getFeeGrowthInside(tickLower, tickUpper, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128); + } + + function update( + int24 tick, + int24 tickCurrent, + int128 liquidityDelta, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time, + bool upper, + uint128 maxLiquidity + ) external returns (bool flipped) { + return + ticks.update( + tick, + tickCurrent, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + upper, + maxLiquidity + ); + } + + function clear(int24 tick) external { + ticks.clear(tick); + } + + function cross( + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) external returns (int128 liquidityNet) { + return + ticks.cross( + tick, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time + ); + } +} + + +// File contracts/test/UniswapV3PoolSwapTest.sol + + +pragma solidity >=0.0; + + +contract UniswapV3PoolSwapTest is IUniswapV3SwapCallback { + int256 private _amount0Delta; + int256 private _amount1Delta; + + function getSwapResult( + address pool, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96 + ) + external + returns ( + int256 amount0Delta, + int256 amount1Delta, + uint160 nextSqrtRatio + ) + { + (amount0Delta, amount1Delta) = IUniswapV3Pool(pool).swap( + address(0), + zeroForOne, + amountSpecified, + sqrtPriceLimitX96, + abi.encode(msg.sender) + ); + + (nextSqrtRatio, , , , , , ) = IUniswapV3Pool(pool).slot0(); + } + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } + } +} + + +// File contracts/test/UnsafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract UnsafeMathEchidnaTest { + function checkDivRoundingUp(uint256 x, uint256 d) external pure { + require(d > 0); + uint256 z = UnsafeMath.divRoundingUp(x, d); + uint256 diff = z - (x / d); + if (x % d == 0) { + assert(diff == 0); + } else { + assert(diff == 1); + } + } +} + + +// File contracts/UniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + /// @inheritdoc IUniswapV3PoolDeployer + Parameters public override parameters; + + /// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then + /// clearing it after deploying the pool. + /// @param factory The contract address of the Uniswap V3 factory + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The spacing between usable ticks + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) internal returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); + delete parameters; + } +} + + +// File contracts/UniswapV3Factory.sol + + +pragma solidity >=0.0; + + +/// @title Canonical Uniswap V3 factory +/// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees +contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall { + /// @inheritdoc IUniswapV3Factory + address public override owner; + + /// @inheritdoc IUniswapV3Factory + mapping(uint24 => int24) public override feeAmountTickSpacing; + /// @inheritdoc IUniswapV3Factory + mapping(address => mapping(address => mapping(uint24 => address))) public override getPool; + + constructor() { + owner = msg.sender; + emit OwnerChanged(address(0), msg.sender); + + feeAmountTickSpacing[500] = 10; + emit FeeAmountEnabled(500, 10); + feeAmountTickSpacing[3000] = 60; + emit FeeAmountEnabled(3000, 60); + feeAmountTickSpacing[10000] = 200; + emit FeeAmountEnabled(10000, 200); + } + + /// @inheritdoc IUniswapV3Factory + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) public override noDelegateCall returns (address pool) { + require(tokenA != tokenB); + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0)); + int24 tickSpacing = feeAmountTickSpacing[fee]; + require(tickSpacing != 0); + require(getPool[token0][token1][fee] == address(0)); + pool = deploy(address(this), token0, token1, fee, tickSpacing); + getPool[token0][token1][fee] = pool; + // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses + getPool[token1][token0][fee] = pool; + emit PoolCreated(token0, token1, fee, tickSpacing, pool); + } + + /// @inheritdoc IUniswapV3Factory + function setOwner(address _owner) external override { + require(msg.sender == owner); + emit OwnerChanged(owner, _owner); + owner = _owner; + } + + /// @inheritdoc IUniswapV3Factory + function enableFeeAmount(uint24 fee, int24 tickSpacing) public override { + require(msg.sender == owner); + require(fee < 1000000); + // tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that + // TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick + // 16384 ticks represents a >5x price change with ticks of 1 bips + require(tickSpacing > 0 && tickSpacing < 16384); + require(feeAmountTickSpacing[fee] == 0); + + feeAmountTickSpacing[fee] = tickSpacing; + emit FeeAmountEnabled(fee, tickSpacing); + } + + function runTest() public { + TestERC20 t1 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000001}(1000000000); + TestERC20 t2 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000002}(1000000000); + IUniswapV3Pool pool = IUniswapV3Pool(createPool(address(t1), address(t2), 500)); + + TestUniswapV3Callee test = new TestUniswapV3Callee(); + t1.approve(address(test), 1000000000); + t2.approve(address(test), 1000000000); + pool.initialize(761446703485210103287273052203988822378723970342); + + test.mint(address(pool), address(this), int24(-1000), int24(1000), uint128(1000)); + + uint256 beforeSwap = t1.balanceOf(address(this)); + test.swapExact0For1(address(pool), 10, address(this), 4295128740); + uint256 afterSwap = t1.balanceOf(address(this)); + + require(beforeSwap != afterSwap, 'WRO'); + } +} + +// ==== +// compileViaYul: true +// compileToEOF: false +// ---- +// runTest() -> +// ~ emit PoolCreated(address,address,uint24,int24,address): #0x268c98cd4338d80f8a149b8d7bd8944dba9f5d7c, #0xe4b1482254b07c39df789bde7c8bab4edc34e459, #0x01f4, 0x0a, 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88 +// ~ emit Approval(address,address,uint256) from 0x268c98cd4338d80f8a149b8d7bd8944dba9f5d7c: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x3b9aca00 +// ~ emit Approval(address,address,uint256) from 0xe4b1482254b07c39df789bde7c8bab4edc34e459: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x3b9aca00 +// ~ emit Initialize(uint160,int24) from 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88: 0x85607379ff6f79edb3e272aaeae79d5263988d26, 0x0d56f8 +// ~ emit MintCallback(uint256,uint256) from 0x137aa4dfc0911524504fcd4d98501f179bc13b4a: 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe4b1482254b07c39df789bde7c8bab4edc34e459: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88, 0x65 +// ~ emit Mint(address,address,int24,int24,uint128,uint256,uint256) from 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18, #0x03e8, 0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x03e8, 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe4b1482254b07c39df789bde7c8bab4edc34e459: #0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88, #0xc06afe3a8444fc0004668591e8306bfb9968e79e, 0x09 +// ~ emit SwapCallback(int256,int256) from 0x137aa4dfc0911524504fcd4d98501f179bc13b4a: 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7 +// ~ emit Transfer(address,address,uint256) from 0x268c98cd4338d80f8a149b8d7bd8944dba9f5d7c: #0xc06afe3a8444fc0004668591e8306bfb9968e79e, #0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88, 0x0a +// ~ emit Swap(address,address,int256,int256,uint160,uint128,int24) from 0x7fa6e5ff76dc624cf2f2ecf4436889a9de300b88: #0x137aa4dfc0911524504fcd4d98501f179bc13b4a, #0xc06afe3a8444fc0004668591e8306bfb9968e79e, 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7, 0x010a9a2fd9e126942e9c978d07, 0x03e8, 0x032b diff --git a/test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol b/test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol new file mode 100644 index 000000000000..61b8a340ec42 --- /dev/null +++ b/test/libsolidity/semanticTests/UniswapV3Flattened_eof.sol @@ -0,0 +1,5191 @@ +// Sources flattened with hardhat v2.2.0 https://hardhat.org + +// File contracts/interfaces/pool/IUniswapV3PoolImmutables.sol + + +pragma solidity >=0.0; + +/// @title Pool state that never changes +/// @notice These parameters are fixed for a pool forever, i.e., the methods will always return the same values +interface IUniswapV3PoolImmutables { + /// @notice The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface + /// @return The contract address + function factory() external view returns (address); + + /// @notice The first of the two tokens of the pool, sorted by address + /// @return The token contract address + function token0() external view returns (address); + + /// @notice The second of the two tokens of the pool, sorted by address + /// @return The token contract address + function token1() external view returns (address); + + /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6 + /// @return The fee + function fee() external view returns (uint24); + + /// @notice The pool tick spacing + /// @dev Ticks can only be used at multiples of this value, minimum of 1 and always positive + /// e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... + /// This value is an int24 to avoid casting even though it is always positive. + /// @return The tick spacing + function tickSpacing() external view returns (int24); + + /// @notice The maximum amount of position liquidity that can use any tick in the range + /// @dev This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and + /// also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool + /// @return The max amount of liquidity per tick + function maxLiquidityPerTick() external view returns (uint128); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that can change +/// @notice These methods compose the pool's state, and can change with any frequency including multiple times +/// per transaction +interface IUniswapV3PoolState { + /// @notice The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas + /// when accessed externally. + /// @return sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value + /// tick The current tick of the pool, i.e. according to the last tick transition that was run. + /// This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick + /// boundary. + /// observationIndex The index of the last oracle observation that was written, + /// observationCardinality The current maximum number of observations stored in the pool, + /// observationCardinalityNext The next maximum number of observations, to be updated when the observation. + /// feeProtocol The protocol fee for both tokens of the pool. + /// Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 + /// is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. + /// unlocked Whether the pool is currently locked to reentrancy + function slot0() + external + view + returns ( + uint160 sqrtPriceX96, + int24 tick, + uint16 observationIndex, + uint16 observationCardinality, + uint16 observationCardinalityNext, + uint8 feeProtocol, + bool unlocked + ); + + /// @notice The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal0X128() external view returns (uint256); + + /// @notice The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool + /// @dev This value can overflow the uint256 + function feeGrowthGlobal1X128() external view returns (uint256); + + /// @notice The amounts of token0 and token1 that are owed to the protocol + /// @dev Protocol fees will never exceed uint128 max in either token + function protocolFees() external view returns (uint128 token0, uint128 token1); + + /// @notice The currently in range liquidity available to the pool + /// @dev This value has no relationship to the total liquidity across all ticks + function liquidity() external view returns (uint128); + + /// @notice Look up information about a specific tick in the pool + /// @param tick The tick to look up + /// @return liquidityGross the total amount of position liquidity that uses the pool either as tick lower or + /// tick upper, + /// liquidityNet how much liquidity changes when the pool price crosses the tick, + /// feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, + /// feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, + /// tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick + /// secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, + /// secondsOutside the seconds spent on the other side of the tick from the current tick, + /// initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. + /// Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. + /// In addition, these values are only relative and must be used only in comparison to previous snapshots for + /// a specific position. + function ticks(int24 tick) + external + view + returns ( + uint128 liquidityGross, + int128 liquidityNet, + uint256 feeGrowthOutside0X128, + uint256 feeGrowthOutside1X128, + int56 tickCumulativeOutside, + uint160 secondsPerLiquidityOutsideX128, + uint32 secondsOutside, + bool initialized + ); + + /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information + function tickBitmap(int16 wordPosition) external view returns (uint256); + + /// @notice Returns the information about a position by the position's key + /// @param key The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper + /// @return _liquidity The amount of liquidity in the position, + /// Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, + /// Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, + /// Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, + /// Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke + function positions(bytes32 key) + external + view + returns ( + uint128 _liquidity, + uint256 feeGrowthInside0LastX128, + uint256 feeGrowthInside1LastX128, + uint128 tokensOwed0, + uint128 tokensOwed1 + ); + + /// @notice Returns data about a specific observation index + /// @param index The element of the observations array to fetch + /// @dev You most likely want to use #observe() instead of this method to get an observation as of some amount of time + /// ago, rather than at a specific index in the array. + /// @return blockTimestamp The timestamp of the observation, + /// Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, + /// Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, + /// Returns initialized whether the observation has been initialized and the values are safe to use + function observations(uint256 index) + external + view + returns ( + uint32 blockTimestamp, + int56 tickCumulative, + uint160 secondsPerLiquidityCumulativeX128, + bool initialized + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol + + +pragma solidity >=0.0; + +/// @title Pool state that is not stored +/// @notice Contains view functions to provide information about the pool that is computed rather than stored on the +/// blockchain. The functions here may have variable gas costs. +interface IUniswapV3PoolDerivedState { + /// @notice Returns the cumulative tick and liquidity as of each timestamp `secondsAgo` from the current block timestamp + /// @dev To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing + /// the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, + /// you must call it with secondsAgos = [3600, 0]. + /// @dev The time weighted average tick represents the geometric time weighted average price of the pool, in + /// log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio. + /// @param secondsAgos From how long ago each cumulative tick and liquidity value should be returned + /// @return tickCumulatives Cumulative tick values as of each `secondsAgos` from the current block timestamp + /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity-in-range value as of each `secondsAgos` from the current block + /// timestamp + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); + + /// @notice Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range + /// @dev Snapshots must only be compared to other snapshots, taken over a period for which a position existed. + /// I.e., snapshots cannot be compared if a position is not held for the entire period between when the first + /// snapshot is taken and the second snapshot is taken. + /// @param tickLower The lower tick of the range + /// @param tickUpper The upper tick of the range + /// @return tickCumulativeInside The snapshot of the tick accumulator for the range + /// @return secondsPerLiquidityInsideX128 The snapshot of seconds per liquidity for the range + /// @return secondsInside The snapshot of seconds per liquidity for the range + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolActions.sol + + +pragma solidity >=0.0; + +/// @title Permissionless pool actions +/// @notice Contains pool methods that can be called by anyone +interface IUniswapV3PoolActions { + /// @notice Sets the initial price for the pool + /// @dev Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value + /// @param sqrtPriceX96 the initial sqrt price of the pool as a Q64.96 + function initialize(uint160 sqrtPriceX96) external; + + /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position + /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback + /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends + /// on tickLower, tickUpper, the amount of liquidity, and the current price. + /// @param recipient The address for which the liquidity will be created + /// @param tickLower The lower tick of the position in which to add liquidity + /// @param tickUpper The upper tick of the position in which to add liquidity + /// @param amount The amount of liquidity to mint + /// @param data Any data that should be passed through to the callback + /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback + /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Collects tokens owed to a position + /// @dev Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. + /// Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or + /// amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the + /// actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity. + /// @param recipient The address which should receive the fees collected + /// @param tickLower The lower tick of the position for which to collect fees + /// @param tickUpper The upper tick of the position for which to collect fees + /// @param amount0Requested How much token0 should be withdrawn from the fees owed + /// @param amount1Requested How much token1 should be withdrawn from the fees owed + /// @return amount0 The amount of fees collected in token0 + /// @return amount1 The amount of fees collected in token1 + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); + + /// @notice Burn liquidity from the sender and account tokens owed for the liquidity to the position + /// @dev Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0 + /// @dev Fees must be collected separately via a call to #collect + /// @param tickLower The lower tick of the position for which to burn liquidity + /// @param tickUpper The upper tick of the position for which to burn liquidity + /// @param amount How much liquidity to burn + /// @return amount0 The amount of token0 sent to the recipient + /// @return amount1 The amount of token1 sent to the recipient + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external returns (uint256 amount0, uint256 amount1); + + /// @notice Swap token0 for token1, or token1 for token0 + /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback + /// @param recipient The address to receive the output of the swap + /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0 + /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative) + /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this + /// value after the swap. If one for zero, the price cannot be greater than this value after the swap + /// @param data Any data to be passed through to the callback + /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive + /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external returns (int256 amount0, int256 amount1); + + /// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback + /// @dev The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback + /// @dev Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling + /// with 0 amount{0,1} and sending the donation amount(s) from the callback + /// @param recipient The address which will receive the token0 and token1 amounts + /// @param amount0 The amount of token0 to send + /// @param amount1 The amount of token1 to send + /// @param data Any data to be passed through to the callback + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external; + + /// @notice Increase the maximum number of price and liquidity observations that this pool will store + /// @dev This method is no-op if the pool already has an observationCardinalityNext greater than or equal to + /// the input observationCardinalityNext. + /// @param observationCardinalityNext The desired minimum number of observations for the pool to store + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external; +} + + +// File contracts/interfaces/pool/IUniswapV3PoolOwnerActions.sol + + +pragma solidity >=0.0; + +/// @title Permissioned pool actions +/// @notice Contains pool methods that may only be called by the factory owner +interface IUniswapV3PoolOwnerActions { + /// @notice Set the denominator of the protocol's % share of the fees + /// @param feeProtocol0 new protocol fee for token0 of the pool + /// @param feeProtocol1 new protocol fee for token1 of the pool + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external; + + /// @notice Collect the protocol fee accrued to the pool + /// @param recipient The address to which collected protocol fees should be sent + /// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1 + /// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0 + /// @return amount0 The protocol fee collected in token0 + /// @return amount1 The protocol fee collected in token1 + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external returns (uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/pool/IUniswapV3PoolEvents.sol + + +pragma solidity >=0.0; + +/// @title Events emitted by a pool +/// @notice Contains all events emitted by the pool +interface IUniswapV3PoolEvents { + /// @notice Emitted exactly once by a pool when #initialize is first called on the pool + /// @dev Mint/Burn/Swap cannot be emitted by the pool before Initialize + /// @param sqrtPriceX96 The initial sqrt price of the pool, as a Q64.96 + /// @param tick The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool + event Initialize(uint160 sqrtPriceX96, int24 tick); + + /// @notice Emitted when liquidity is minted for a given position + /// @param sender The address that minted the liquidity + /// @param owner The owner of the position and recipient of any minted liquidity + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity minted to the position range + /// @param amount0 How much token0 was required for the minted liquidity + /// @param amount1 How much token1 was required for the minted liquidity + event Mint( + address sender, + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted when fees are collected by the owner of a position + /// @dev Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees + /// @param owner The owner of the position for which fees are collected + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount0 The amount of token0 fees collected + /// @param amount1 The amount of token1 fees collected + event Collect( + address indexed owner, + address recipient, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount0, + uint128 amount1 + ); + + /// @notice Emitted when a position's liquidity is removed + /// @dev Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect + /// @param owner The owner of the position for which liquidity is removed + /// @param tickLower The lower tick of the position + /// @param tickUpper The upper tick of the position + /// @param amount The amount of liquidity to remove + /// @param amount0 The amount of token0 withdrawn + /// @param amount1 The amount of token1 withdrawn + event Burn( + address indexed owner, + int24 indexed tickLower, + int24 indexed tickUpper, + uint128 amount, + uint256 amount0, + uint256 amount1 + ); + + /// @notice Emitted by the pool for any swaps between token0 and token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the output of the swap + /// @param amount0 The delta of the token0 balance of the pool + /// @param amount1 The delta of the token1 balance of the pool + /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 + /// @param liquidity The liquidity of the pool after the swap + /// @param tick The log base 1.0001 of price of the pool after the swap + event Swap( + address indexed sender, + address indexed recipient, + int256 amount0, + int256 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick + ); + + /// @notice Emitted by the pool for any flashes of token0/token1 + /// @param sender The address that initiated the swap call, and that received the callback + /// @param recipient The address that received the tokens from flash + /// @param amount0 The amount of token0 that was flashed + /// @param amount1 The amount of token1 that was flashed + /// @param paid0 The amount of token0 paid for the flash, which can exceed the amount0 plus the fee + /// @param paid1 The amount of token1 paid for the flash, which can exceed the amount1 plus the fee + event Flash( + address indexed sender, + address indexed recipient, + uint256 amount0, + uint256 amount1, + uint256 paid0, + uint256 paid1 + ); + + /// @notice Emitted by the pool for increases to the number of observations that can be stored + /// @dev observationCardinalityNext is not the observation cardinality until an observation is written at the index + /// just before a mint/swap/burn. + /// @param observationCardinalityNextOld The previous value of the next observation cardinality + /// @param observationCardinalityNextNew The updated value of the next observation cardinality + event IncreaseObservationCardinalityNext( + uint16 observationCardinalityNextOld, + uint16 observationCardinalityNextNew + ); + + /// @notice Emitted when the protocol fee is changed by the pool + /// @param feeProtocol0Old The previous value of the token0 protocol fee + /// @param feeProtocol1Old The previous value of the token1 protocol fee + /// @param feeProtocol0New The updated value of the token0 protocol fee + /// @param feeProtocol1New The updated value of the token1 protocol fee + event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New); + + /// @notice Emitted when the collected protocol fees are withdrawn by the factory owner + /// @param sender The address that collects the protocol fees + /// @param recipient The address that receives the collected protocol fees + /// @param amount0 The amount of token0 protocol fees that is withdrawn + /// @param amount0 The amount of token1 protocol fees that is withdrawn + event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1); +} + + +// File contracts/interfaces/IUniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + +/// @title The interface for a Uniswap V3 Pool +/// @notice A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform +/// to the ERC20 specification +/// @dev The pool interface is broken up into many smaller pieces +interface IUniswapV3Pool is + IUniswapV3PoolImmutables, + IUniswapV3PoolState, + IUniswapV3PoolDerivedState, + IUniswapV3PoolActions, + IUniswapV3PoolOwnerActions, + IUniswapV3PoolEvents +{ + +} + + +// File contracts/libraries/FullMath.sol + + +pragma solidity >=0.0; + +/// @title Contains 512-bit math functions +/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision +/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = uint256(-int256(denominator)) & denominator; + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + unchecked { + result = mulDiv(a, b, denominator); + if (mulmod(a, b, denominator) > 0) { + require(result < type(uint256).max); + result++; + } + } + } +} + + +// File contracts/libraries/FixedPoint128.sol + + +pragma solidity >=0.0; + +/// @title FixedPoint128 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +library FixedPoint128 { + uint256 internal constant Q128 = 0x100000000000000000000000000000000; +} + + +// File contracts/libraries/LiquidityMath.sol + + +pragma solidity >=0.0; + +/// @title Math library for liquidity +library LiquidityMath { + /// @notice Add a signed liquidity delta to liquidity and revert if it overflows or underflows + /// @param x The liquidity before change + /// @param y The delta by which liquidity should be changed + /// @return z The liquidity delta + function addDelta(uint128 x, int128 y) internal pure returns (uint128 z) { + if (y < 0) { + unchecked { + require((z = x - uint128(-y)) < x, 'LS'); + } + } else { + unchecked { + require((z = x + uint128(y)) >= x, 'LA'); + } + } + } +} + + +// File contracts/libraries/Position.sol + + +pragma solidity >=0.0; + + + +/// @title Position +/// @notice Positions represent an owner address' liquidity between a lower and upper tick boundary +/// @dev Positions store additional state for tracking fees owed to the position +library Position { + // info stored for each user's position + struct Info { + // the amount of liquidity owned by this position + uint128 liquidity; + // fee growth per unit of liquidity as of the last update to liquidity or fees owed + uint256 feeGrowthInside0LastX128; + uint256 feeGrowthInside1LastX128; + // the fees owed to the position owner in token0/token1 + uint128 tokensOwed0; + uint128 tokensOwed1; + } + + /// @notice Returns the Info struct of a position, given an owner and position boundaries + /// @param self The mapping containing all user positions + /// @param owner The address of the position owner + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @return position The position info struct of the given owners' position + function get( + mapping(bytes32 => Info) storage self, + address owner, + int24 tickLower, + int24 tickUpper + ) internal view returns (Position.Info storage position) { + position = self[keccak256(abi.encodePacked(owner, tickLower, tickUpper))]; + } + + /// @notice Credits accumulated fees to a user's position + /// @param self The individual position to update + /// @param liquidityDelta The change in pool liquidity as a result of the position update + /// @param feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries + /// @param feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries + function update( + Info storage self, + int128 liquidityDelta, + uint256 feeGrowthInside0X128, + uint256 feeGrowthInside1X128 + ) internal { + unchecked { + Info memory _self = self; + + uint128 liquidityNext; + if (liquidityDelta == 0) { + require(_self.liquidity > 0, 'NP'); // disallow pokes for 0 liquidity positions + liquidityNext = _self.liquidity; + } else { + liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta); + } + + // calculate accumulated fees + uint128 tokensOwed0 = + uint128( + FullMath.mulDiv( + feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + uint128 tokensOwed1 = + uint128( + FullMath.mulDiv( + feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, + _self.liquidity, + FixedPoint128.Q128 + ) + ); + + // update the position + if (liquidityDelta != 0) self.liquidity = liquidityNext; + self.feeGrowthInside0LastX128 = feeGrowthInside0X128; + self.feeGrowthInside1LastX128 = feeGrowthInside1X128; + if (tokensOwed0 > 0 || tokensOwed1 > 0) { + // overflow is acceptable, have to withdraw before you hit type(uint128).max fees + self.tokensOwed0 += tokensOwed0; + self.tokensOwed1 += tokensOwed1; + } + } + } +} + + +// File contracts/libraries/LowGasSafeMath.sol + + +pragma solidity >=0.0; + +/// @title Optimized overflow and underflow safe math operations +/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost +library LowGasSafeMath { + /// @notice Returns x + y, reverts if sum overflows uint256 + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x + y) >= x); + } + + /// @notice Returns x - y, reverts if underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { + require((z = x - y) <= x); + } + + /// @notice Returns x * y, reverts if overflows + /// @param x The multiplicand + /// @param y The multiplier + /// @return z The product of x and y + function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { + require(x == 0 || (z = x * y) / x == y); + } + + /// @notice Returns x + y, reverts if overflows or underflows + /// @param x The augend + /// @param y The addend + /// @return z The sum of x and y + function add(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x + y) >= x == (y >= 0)); + } + + /// @notice Returns x - y, reverts if overflows or underflows + /// @param x The minuend + /// @param y The subtrahend + /// @return z The difference of x and y + function sub(int256 x, int256 y) internal pure returns (int256 z) { + require((z = x - y) <= x == (y >= 0)); + } +} + + +// File contracts/libraries/SafeCast.sol + + +pragma solidity >=0.0; + +/// @title Safe casting methods +/// @notice Contains methods for safely casting between types +library SafeCast { + /// @notice Cast a uint256 to a uint160, revert on overflow + /// @param y The uint256 to be downcasted + /// @return z The downcasted integer, now type uint160 + function toUint160(uint256 y) internal pure returns (uint160 z) { + require((z = uint160(y)) == y); + } + + /// @notice Cast a int256 to a int128, revert on overflow or underflow + /// @param y The int256 to be downcasted + /// @return z The downcasted integer, now type int128 + function toInt128(int256 y) internal pure returns (int128 z) { + require((z = int128(y)) == y); + } + + /// @notice Cast a uint256 to a int256, revert on overflow + /// @param y The uint256 to be casted + /// @return z The casted integer, now type int256 + function toInt256(uint256 y) internal pure returns (int256 z) { + require(y < 2**255); + z = int256(y); + } +} + + +// File contracts/libraries/UnsafeMath.sol + + +pragma solidity >=0.0; + +/// @title Math functions that do not check inputs or outputs +/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks +library UnsafeMath { + /// @notice Returns ceil(x / y) + /// @dev division by 0 has unspecified behavior, and must be checked externally + /// @param x The dividend + /// @param y The divisor + /// @return z The quotient, ceil(x / y) + function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := add(div(x, y), gt(mod(x, y), 0)) + } + } +} + + +// File contracts/libraries/FixedPoint96.sol + + +pragma solidity >=0.0; + +/// @title FixedPoint96 +/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format) +/// @dev Used in SqrtPriceMath.sol +library FixedPoint96 { + uint8 internal constant RESOLUTION = 96; + uint256 internal constant Q96 = 0x1000000000000000000000000; +} + + +// File contracts/libraries/SqrtPriceMath.sol + + +pragma solidity >=0.0; + + + + +/// @title Functions based on Q64.96 sqrt price and liquidity +/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas +library SqrtPriceMath { + using LowGasSafeMath for uint256; + using SafeCast for uint256; + + /// @notice Gets the next sqrt price given a delta of token0 + /// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the + /// price less in order to not send too much output. + /// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96), + /// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount). + /// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token0 to add or remove from virtual reserves + /// @param add Whether to add or remove the amount of token0 + /// @return The price after adding or removing amount, depending on add + function getNextSqrtPriceFromAmount0RoundingUp( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price + if (amount == 0) return sqrtPX96; + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + + if (add) { + uint256 product; + if ((product = amount * sqrtPX96) / amount == sqrtPX96) { + uint256 denominator = numerator1 + product; + if (denominator >= numerator1) + // always fits in 160 bits + return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator)); + } + + return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))); + } else { + uint256 product; + // if the product overflows, we know the denominator underflows + // in addition, we must check that the denominator does not underflow + require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product); + uint256 denominator = numerator1 - product; + return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160(); + } + } + } + + /// @notice Gets the next sqrt price given a delta of token1 + /// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least + /// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the + /// price less in order to not send too much output. + /// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity + /// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta + /// @param liquidity The amount of usable liquidity + /// @param amount How much of token1 to add, or remove, from virtual reserves + /// @param add Whether to add, or remove, the amount of token1 + /// @return The price after adding or removing `amount` + function getNextSqrtPriceFromAmount1RoundingDown( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) internal pure returns (uint160) { + unchecked { + // if we're adding (subtracting), rounding down requires rounding the quotient down (up) + // in both cases, avoid a mulDiv for most inputs + if (add) { + uint256 quotient = + ( + amount <= type(uint160).max + ? (amount << FixedPoint96.RESOLUTION) / liquidity + : FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) + ); + + return uint256(sqrtPX96).add(quotient).toUint160(); + } else { + uint256 quotient = + ( + amount <= type(uint160).max + ? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity) + : FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) + ); + + require(sqrtPX96 > quotient); + // always fits 160 bits + return uint160(sqrtPX96 - quotient); + } + } + } + + /// @notice Gets the next sqrt price given an input amount of token0 or token1 + /// @dev Throws if price or liquidity are 0, or if the next price is out of bounds + /// @param sqrtPX96 The starting price, i.e., before accounting for the input amount + /// @param liquidity The amount of usable liquidity + /// @param amountIn How much of token0, or token1, is being swapped in + /// @param zeroForOne Whether the amount in is token0 or token1 + /// @return sqrtQX96 The price after adding the input amount to token0 or token1 + function getNextSqrtPriceFromInput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we don't pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) + : getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true); + } + + /// @notice Gets the next sqrt price given an output amount of token0 or token1 + /// @dev Throws if price or liquidity are 0 or the next price is out of bounds + /// @param sqrtPX96 The starting price before accounting for the output amount + /// @param liquidity The amount of usable liquidity + /// @param amountOut How much of token0, or token1, is being swapped out + /// @param zeroForOne Whether the amount out is token0 or token1 + /// @return sqrtQX96 The price after removing the output amount of token0 or token1 + function getNextSqrtPriceFromOutput( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) internal pure returns (uint160 sqrtQX96) { + require(sqrtPX96 > 0); + require(liquidity > 0); + + // round to make sure that we pass the target price + return + zeroForOne + ? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) + : getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false); + } + + /// @notice Gets the amount0 delta between two prices + /// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), + /// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up or down + /// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount0) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96; + + require(sqrtRatioAX96 > 0); + + return + roundUp + ? UnsafeMath.divRoundingUp( + FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), + sqrtRatioAX96 + ) + : FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96; + } + + /// @notice Gets the amount1 delta between two prices + /// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower)) + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The amount of usable liquidity + /// @param roundUp Whether to round the amount up, or down + /// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + uint128 liquidity, + bool roundUp + ) internal pure returns (uint256 amount1) { + if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96); + + return + roundUp + ? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96) + : FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96); + } + + /// @notice Helper that gets signed token0 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount0 delta + /// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices + function getAmount0Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount0) { + return + liquidity < 0 + ? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } + + /// @notice Helper that gets signed token1 delta + /// @param sqrtRatioAX96 A sqrt price + /// @param sqrtRatioBX96 Another sqrt price + /// @param liquidity The change in liquidity for which to compute the amount1 delta + /// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices + function getAmount1Delta( + uint160 sqrtRatioAX96, + uint160 sqrtRatioBX96, + int128 liquidity + ) internal pure returns (int256 amount1) { + return + liquidity < 0 + ? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256() + : getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256(); + } +} + + +// File contracts/libraries/SwapMath.sol + + +pragma solidity >=0.0; + + +/// @title Computes the result of a swap within ticks +/// @notice Contains methods for computing the result of a swap within a single tick price range, i.e., a single tick. +library SwapMath { + /// @notice Computes the result of swapping some amount in, or amount out, given the parameters of the swap + /// @dev The fee, plus the amount in, will never exceed the amount remaining if the swap's `amountSpecified` is positive + /// @param sqrtRatioCurrentX96 The current sqrt price of the pool + /// @param sqrtRatioTargetX96 The price that cannot be exceeded, from which the direction of the swap is inferred + /// @param liquidity The usable liquidity + /// @param amountRemaining How much input or output amount is remaining to be swapped in/out + /// @param feePips The fee taken from the input amount, expressed in hundredths of a bip + /// @return sqrtRatioNextX96 The price after swapping the amount in/out, not to exceed the price target + /// @return amountIn The amount to be swapped in, of either token0 or token1, based on the direction of the swap + /// @return amountOut The amount to be received, of either token0 or token1, based on the direction of the swap + /// @return feeAmount The amount of input that will be taken as a fee + function computeSwapStep( + uint160 sqrtRatioCurrentX96, + uint160 sqrtRatioTargetX96, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + internal + pure + returns ( + uint160 sqrtRatioNextX96, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96; + bool exactIn = amountRemaining >= 0; + + if (exactIn) { + uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining), 1e6 - feePips, 1e6); + amountIn = zeroForOne + ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true); + if (amountRemainingLessFee >= amountIn) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( + sqrtRatioCurrentX96, + liquidity, + amountRemainingLessFee, + zeroForOne + ); + } else { + amountOut = zeroForOne + ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false); + if (uint256(-amountRemaining) >= amountOut) sqrtRatioNextX96 = sqrtRatioTargetX96; + else + sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( + sqrtRatioCurrentX96, + liquidity, + uint256(-amountRemaining), + zeroForOne + ); + } + + bool max = sqrtRatioTargetX96 == sqrtRatioNextX96; + + // get the input/output amounts + if (zeroForOne) { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false); + } else { + amountIn = max && exactIn + ? amountIn + : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true); + amountOut = max && !exactIn + ? amountOut + : SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false); + } + + // cap the output amount to not exceed the remaining output amount + if (!exactIn && amountOut > uint256(-amountRemaining)) { + amountOut = uint256(-amountRemaining); + } + + if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) { + // we didn't reach the target, so take the remainder of the maximum input as fee + feeAmount = uint256(amountRemaining) - amountIn; + } else { + feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); + } + } +} + + +// File contracts/libraries/TickMath.sol + + +pragma solidity >=0.0; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(int256(MAX_TICK)), 'T'); + + uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint256).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + } + + /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio + function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R'); + uint256 ratio = uint256(sqrtPriceX96) << 32; + + uint256 r = ratio; + uint256 msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; + } +} + + +// File contracts/libraries/Tick.sol + + +pragma solidity >=0.0; + + + +/// @title Tick +/// @notice Contains functions for managing tick processes and relevant calculations +library Tick { + using LowGasSafeMath for int256; + using SafeCast for int256; + + // info stored for each initialized individual tick + struct Info { + // the total position liquidity that references this tick + uint128 liquidityGross; + // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), + int128 liquidityNet; + // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint256 feeGrowthOutside0X128; + uint256 feeGrowthOutside1X128; + // the cumulative tick value on the other side of the tick + int56 tickCumulativeOutside; + // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint160 secondsPerLiquidityOutsideX128; + // the seconds spent on the other side of the tick (relative to the current tick) + // only has relative meaning, not absolute — the value depends on when the tick is initialized + uint32 secondsOutside; + // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 + // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks + bool initialized; + } + + /// @notice Derives max liquidity per tick from given tick spacing + /// @dev Executed within the pool constructor + /// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing` + /// e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ... + /// @return The max liquidity per tick + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128) { + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1; + return type(uint128).max / numTicks; + } + + /// @notice Retrieves fee growth data + /// @param self The mapping containing all tick information for initialized ticks + /// @param tickLower The lower tick boundary of the position + /// @param tickUpper The upper tick boundary of the position + /// @param tickCurrent The current tick + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @return feeGrowthInside0X128 The all-time fee growth in token0, per unit of liquidity, inside the position's tick boundaries + /// @return feeGrowthInside1X128 The all-time fee growth in token1, per unit of liquidity, inside the position's tick boundaries + function getFeeGrowthInside( + mapping(int24 => Tick.Info) storage self, + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + Info storage lower = self[tickLower]; + Info storage upper = self[tickUpper]; + + // calculate fee growth below + uint256 feeGrowthBelow0X128; + uint256 feeGrowthBelow1X128; + unchecked { + if (tickCurrent >= tickLower) { + feeGrowthBelow0X128 = lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = lower.feeGrowthOutside1X128; + } else { + feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128; + feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128; + } + + // calculate fee growth above + uint256 feeGrowthAbove0X128; + uint256 feeGrowthAbove1X128; + if (tickCurrent < tickUpper) { + feeGrowthAbove0X128 = upper.feeGrowthOutside0X128; + feeGrowthAbove1X128 = upper.feeGrowthOutside1X128; + } else { + feeGrowthAbove0X128 = feeGrowthGlobal0X128 - upper.feeGrowthOutside0X128; + feeGrowthAbove1X128 = feeGrowthGlobal1X128 - upper.feeGrowthOutside1X128; + } + + feeGrowthInside0X128 = feeGrowthGlobal0X128 - feeGrowthBelow0X128 - feeGrowthAbove0X128; + feeGrowthInside1X128 = feeGrowthGlobal1X128 - feeGrowthBelow1X128 - feeGrowthAbove1X128; + } + } + + /// @notice Updates a tick and returns true if the tick was flipped from initialized to uninitialized, or vice versa + /// @param self The mapping containing all tick information for initialized ticks + /// @param tick The tick that will be updated + /// @param tickCurrent The current tick + /// @param liquidityDelta A new amount of liquidity to be added (subtracted) when tick is crossed from left to right (right to left) + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @param secondsPerLiquidityCumulativeX128 The all-time seconds per max(1, liquidity) of the pool + /// @param tickCumulative The tick * time elapsed since the pool was first initialized + /// @param time The current block timestamp cast to a uint32 + /// @param upper true for updating a position's upper tick, or false for updating a position's lower tick + /// @param maxLiquidity The maximum liquidity allocation for a single tick + /// @return flipped Whether the tick was flipped from initialized to uninitialized, or vice versa + function update( + mapping(int24 => Tick.Info) storage self, + int24 tick, + int24 tickCurrent, + int128 liquidityDelta, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time, + bool upper, + uint128 maxLiquidity + ) internal returns (bool flipped) { + Tick.Info storage info = self[tick]; + + uint128 liquidityGrossBefore = info.liquidityGross; + uint128 liquidityGrossAfter = LiquidityMath.addDelta(liquidityGrossBefore, liquidityDelta); + + require(liquidityGrossAfter <= maxLiquidity, 'LO'); + + flipped = (liquidityGrossAfter == 0) != (liquidityGrossBefore == 0); + + if (liquidityGrossBefore == 0) { + // by convention, we assume that all growth before a tick was initialized happened _below_ the tick + if (tick <= tickCurrent) { + info.feeGrowthOutside0X128 = feeGrowthGlobal0X128; + info.feeGrowthOutside1X128 = feeGrowthGlobal1X128; + info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128; + info.tickCumulativeOutside = tickCumulative; + info.secondsOutside = time; + } + info.initialized = true; + } + + info.liquidityGross = liquidityGrossAfter; + + // when the lower (upper) tick is crossed left to right (right to left), liquidity must be added (removed) + info.liquidityNet = upper + ? int256(info.liquidityNet).sub(liquidityDelta).toInt128() + : int256(info.liquidityNet).add(liquidityDelta).toInt128(); + } + + /// @notice Clears tick data + /// @param self The mapping containing all initialized tick information for initialized ticks + /// @param tick The tick that will be cleared + function clear(mapping(int24 => Tick.Info) storage self, int24 tick) internal { + delete self[tick]; + } + + /// @notice Transitions to next tick as needed by price movement + /// @param self The mapping containing all tick information for initialized ticks + /// @param tick The destination tick of the transition + /// @param feeGrowthGlobal0X128 The all-time global fee growth, per unit of liquidity, in token0 + /// @param feeGrowthGlobal1X128 The all-time global fee growth, per unit of liquidity, in token1 + /// @param secondsPerLiquidityCumulativeX128 The current seconds per liquidity + /// @param tickCumulative The tick * time elapsed since the pool was first initialized + /// @param time The current block.timestamp + /// @return liquidityNet The amount of liquidity added (subtracted) when tick is crossed from left to right (right to left) + function cross( + mapping(int24 => Tick.Info) storage self, + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) internal returns (int128 liquidityNet) { + Tick.Info storage info = self[tick]; + info.feeGrowthOutside0X128 = feeGrowthGlobal0X128 - info.feeGrowthOutside0X128; + info.feeGrowthOutside1X128 = feeGrowthGlobal1X128 - info.feeGrowthOutside1X128; + info.secondsPerLiquidityOutsideX128 = secondsPerLiquidityCumulativeX128 - info.secondsPerLiquidityOutsideX128; + info.tickCumulativeOutside = tickCumulative - info.tickCumulativeOutside; + info.secondsOutside = time - info.secondsOutside; + liquidityNet = info.liquidityNet; + } +} + + +// File contracts/libraries/BitMath.sol + + +pragma solidity >=0.0; + +/// @title BitMath +/// @dev This library provides functionality for computing bit properties of an unsigned integer +library BitMath { + /// @notice Returns the index of the most significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @dev The function satisfies the property: + /// x >= 2**mostSignificantBit(x) and x < 2**(mostSignificantBit(x)+1) + /// @param x the value for which to compute the most significant bit, must be greater than 0 + /// @return r the index of the most significant bit + function mostSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + if (x >= 0x100000000000000000000000000000000) { + x >>= 128; + r += 128; + } + if (x >= 0x10000000000000000) { + x >>= 64; + r += 64; + } + if (x >= 0x100000000) { + x >>= 32; + r += 32; + } + if (x >= 0x10000) { + x >>= 16; + r += 16; + } + if (x >= 0x100) { + x >>= 8; + r += 8; + } + if (x >= 0x10) { + x >>= 4; + r += 4; + } + if (x >= 0x4) { + x >>= 2; + r += 2; + } + if (x >= 0x2) r += 1; + } + + /// @notice Returns the index of the least significant bit of the number, + /// where the least significant bit is at index 0 and the most significant bit is at index 255 + /// @dev The function satisfies the property: + /// (x & 2**leastSignificantBit(x)) != 0 and (x & (2**(leastSignificantBit(x)) - 1)) == 0) + /// @param x the value for which to compute the least significant bit, must be greater than 0 + /// @return r the index of the least significant bit + function leastSignificantBit(uint256 x) internal pure returns (uint8 r) { + require(x > 0); + + r = 255; + if (x & type(uint128).max > 0) { + r -= 128; + } else { + x >>= 128; + } + if (x & type(uint64).max > 0) { + r -= 64; + } else { + x >>= 64; + } + if (x & type(uint32).max > 0) { + r -= 32; + } else { + x >>= 32; + } + if (x & type(uint16).max > 0) { + r -= 16; + } else { + x >>= 16; + } + if (x & type(uint8).max > 0) { + r -= 8; + } else { + x >>= 8; + } + if (x & 0xf > 0) { + r -= 4; + } else { + x >>= 4; + } + if (x & 0x3 > 0) { + r -= 2; + } else { + x >>= 2; + } + if (x & 0x1 > 0) r -= 1; + } +} + + +// File contracts/libraries/TickBitmap.sol + + +pragma solidity >=0.0; + +/// @title Packed tick initialized state library +/// @notice Stores a packed mapping of tick index to its initialized state +/// @dev The mapping uses int16 for keys since ticks are represented as int24 and there are 256 (2^8) values per word. +library TickBitmap { + /// @notice Computes the position in the mapping where the initialized bit for a tick lives + /// @param tick The tick for which to compute the position + /// @return wordPos The key in the mapping containing the word in which the bit is stored + /// @return bitPos The bit position in the word where the flag is stored + function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) { + wordPos = int16(tick >> 8); + bitPos = uint8(uint24(tick % 256)); + } + + /// @notice Flips the initialized state for a given tick from false to true, or vice versa + /// @param self The mapping in which to flip the tick + /// @param tick The tick to flip + /// @param tickSpacing The spacing between usable ticks + function flipTick( + mapping(int16 => uint256) storage self, + int24 tick, + int24 tickSpacing + ) internal { + require(tick % tickSpacing == 0); // ensure that the tick is spaced + (int16 wordPos, uint8 bitPos) = position(tick / tickSpacing); + uint256 mask = 1 << bitPos; + self[wordPos] ^= mask; + } + + /// @notice Returns the next initialized tick contained in the same word (or adjacent word) as the tick that is either + /// to the left (less than or equal to) or right (greater than) of the given tick + /// @param self The mapping in which to compute the next initialized tick + /// @param tick The starting tick + /// @param tickSpacing The spacing between usable ticks + /// @param lte Whether to search for the next initialized tick to the left (less than or equal to the starting tick) + /// @return next The next initialized or uninitialized tick up to 256 ticks away from the current tick + /// @return initialized Whether the next tick is initialized, as the function only searches within up to 256 ticks + function nextInitializedTickWithinOneWord( + mapping(int16 => uint256) storage self, + int24 tick, + int24 tickSpacing, + bool lte + ) internal view returns (int24 next, bool initialized) { + int24 compressed = tick / tickSpacing; + if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity + + if (lte) { + (int16 wordPos, uint8 bitPos) = position(compressed); + // all the 1s at or to the right of the current bitPos + uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing + : (compressed - int24(uint24(bitPos))) * tickSpacing; + } else { + // start from the word of the next tick, since the current tick state doesn't matter + (int16 wordPos, uint8 bitPos) = position(compressed + 1); + // all the 1s at or to the left of the bitPos + uint256 mask = ~((1 << bitPos) - 1); + uint256 masked = self[wordPos] & mask; + + // if there are no initialized ticks to the left of the current tick, return leftmost in the word + initialized = masked != 0; + // overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick + next = initialized + ? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing + : (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing; + } + } +} + + +// File contracts/interfaces/IERC20Minimal.sol + + +pragma solidity >=0.0; + +/// @title Minimal ERC20 interface for Uniswap +/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 +interface IERC20Minimal { + /// @notice Returns the balance of a token + /// @param account The account for which to look up the number of tokens it has, i.e. its balance + /// @return The number of tokens held by the account + function balanceOf(address account) external view returns (uint256); + + /// @notice Transfers the amount of token from the `msg.sender` to the recipient + /// @param recipient The account that will receive the amount transferred + /// @param amount The number of tokens to send from the sender to the recipient + /// @return Returns true for a successful transfer, false for an unsuccessful transfer + function transfer(address recipient, uint256 amount) external returns (bool); + + /// @notice Returns the current allowance given to a spender by an owner + /// @param owner The account of the token owner + /// @param spender The account of the token spender + /// @return The current allowance granted by `owner` to `spender` + function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` + /// @param spender The account which will be allowed to spend a given amount of the owners tokens + /// @param amount The amount of tokens allowed to be used by `spender` + /// @return Returns true for a successful approval, false for unsuccessful + function approve(address spender, uint256 amount) external returns (bool); + + /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` + /// @param sender The account from which the transfer will be initiated + /// @param recipient The recipient of the transfer + /// @param amount The amount of the transfer + /// @return Returns true for a successful transfer, false for unsuccessful + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. + /// @param from The account from which the tokens were sent, i.e. the balance decreased + /// @param to The account to which the tokens were sent, i.e. the balance increased + /// @param value The amount of tokens that were transferred + event Transfer(address indexed from, address indexed to, uint256 value); + + /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. + /// @param owner The account that approved spending of its tokens + /// @param spender The account for which the spending allowance was modified + /// @param value The new allowance from the owner to the spender + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +// File contracts/libraries/TransferHelper.sol + + +pragma solidity >=0.0; + +/// @title TransferHelper +/// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false +library TransferHelper { + /// @notice Transfers tokens from msg.sender to a recipient + /// @dev Calls transfer on token contract, errors with TF if transfer fails + /// @param token The contract address of the token which will be transferred + /// @param to The recipient of the transfer + /// @param value The value of the transfer + function safeTransfer( + address token, + address to, + uint256 value + ) internal { + (bool success, bytes memory data) = + token.call(abi.encodeWithSelector(IERC20Minimal.transfer.selector, to, value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'TF'); + } +} + + +// File contracts/test/BitMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract BitMathEchidnaTest { + function mostSignificantBitInvariant(uint256 input) external pure { + uint8 msb = BitMath.mostSignificantBit(input); + assert(input >= (uint256(2)**msb)); + assert(msb == 255 || input < uint256(2)**(msb + 1)); + } + + function leastSignificantBitInvariant(uint256 input) external pure { + uint8 lsb = BitMath.leastSignificantBit(input); + assert(input & (uint256(2)**lsb) != 0); + assert(input & (uint256(2)**lsb - 1) == 0); + } +} + + +// File contracts/test/BitMathTest.sol + + +pragma solidity >=0.0; + +contract BitMathTest { + function mostSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.mostSignificantBit(x); + } + + function getGasCostOfMostSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.mostSignificantBit(x); + return gasBefore - gasleft(); + } + + function leastSignificantBit(uint256 x) external pure returns (uint8 r) { + return BitMath.leastSignificantBit(x); + } + + function getGasCostOfLeastSignificantBit(uint256 x) external view returns (uint256) { + uint256 gasBefore = gasleft(); + BitMath.leastSignificantBit(x); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/FullMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract FullMathEchidnaTest { + function checkMulDivRounding( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + + uint256 ceiled = FullMath.mulDivRoundingUp(x, y, d); + uint256 floored = FullMath.mulDiv(x, y, d); + + if (mulmod(x, y, d) > 0) { + assert(ceiled - floored == 1); + } else { + assert(ceiled == floored); + } + } + + function checkMulDiv( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDiv(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 <= x); + assert(y2 <= y); + + assert(x - x2 < d); + assert(y - y2 < d); + } + + function checkMulDivRoundingUp( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDivRoundingUp(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 >= x); + assert(y2 >= y); + + assert(x2 - x < d); + assert(y2 - y < d); + } +} + + +// File contracts/test/FullMathTest.sol + + +pragma solidity >=0.0; + +contract FullMathTest { + function mulDiv( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDiv(x, y, z); + } + + function mulDivRoundingUp( + uint256 x, + uint256 y, + uint256 z + ) external pure returns (uint256) { + return FullMath.mulDivRoundingUp(x, y, z); + } +} + + +// File contracts/test/LiquidityMathTest.sol + + +pragma solidity >=0.0; + +contract LiquidityMathTest { + function addDelta(uint128 x, int128 y) external pure returns (uint128 z) { + return LiquidityMath.addDelta(x, y); + } + + function getGasCostOfAddDelta(uint128 x, int128 y) external view returns (uint256) { + uint256 gasBefore = gasleft(); + LiquidityMath.addDelta(x, y); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/LowGasSafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract LowGasSafeMathEchidnaTest { + function checkAdd(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(z >= x && z >= y); + } + + function checkSub(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(z <= x); + } + + function checkMul(uint256 x, uint256 y) external pure { + uint256 z = LowGasSafeMath.mul(x, y); + assert(z == x * y); + assert(x == 0 || y == 0 || (z >= x && z >= y)); + } + + function checkAddi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.add(x, y); + assert(z == x + y); + assert(y < 0 ? z < x : z >= x); + } + + function checkSubi(int256 x, int256 y) external pure { + int256 z = LowGasSafeMath.sub(x, y); + assert(z == x - y); + assert(y < 0 ? z > x : z <= x); + } +} + + +// File contracts/NoDelegateCall.sol + + +pragma solidity >=0.0; + +/// @title Prevents delegatecall to a contract +/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract +abstract contract NoDelegateCall { + /// @dev The original address of this contract + address private immutable original; + + constructor() { + // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. + // In other words, this variable won't change when it's checked at runtime. + original = address(this); + } + + /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, + /// and the use of immutable means the address bytes are copied in every place the modifier is used. + function checkNotDelegateCall() private view { + require(address(this) == original); + } + + /// @notice Prevents delegatecall into the modified method + modifier noDelegateCall() { + checkNotDelegateCall(); + _; + } +} + + +// File contracts/libraries/Oracle.sol + + +pragma solidity >=0.0; + +/// @title Oracle +/// @notice Provides price and liquidity data useful for a wide variety of system designs +/// @dev Instances of stored oracle data, "observations", are collected in the oracle array +/// Every pool is initialized with an oracle array length of 1. Anyone can pay the SSTOREs to increase the +/// maximum length of the oracle array. New slots will be added when the array is fully populated. +/// Observations are overwritten when the full length of the oracle array is populated. +/// The most recent observation is available, independent of the length of the oracle array, by passing 0 to observe() +library Oracle { + struct Observation { + // the block timestamp of the observation + uint32 blockTimestamp; + // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized + int56 tickCumulative; + // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized + uint160 secondsPerLiquidityCumulativeX128; + // whether or not the observation is initialized + bool initialized; + } + + /// @notice Transforms a previous observation into a new observation, given the passage of time and the current tick and liquidity values + /// @dev blockTimestamp _must_ be chronologically equal to or greater than last.blockTimestamp, safe for 0 or 1 overflows + /// @param last The specified observation to be transformed + /// @param blockTimestamp The timestamp of the new observation + /// @param tick The active tick at the time of the new observation + /// @param liquidity The total in-range liquidity at the time of the new observation + /// @return Observation The newly populated observation + function transform( + Observation memory last, + uint32 blockTimestamp, + int24 tick, + uint128 liquidity + ) private pure returns (Observation memory) { + unchecked { + uint32 delta = blockTimestamp - last.blockTimestamp; + return + Observation({ + blockTimestamp: blockTimestamp, + tickCumulative: last.tickCumulative + int56(tick) * int56(uint56(delta)), + secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128 + + ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)), + initialized: true + }); + } + } + + /// @notice Initialize the oracle array by writing the first slot. Called once for the lifecycle of the observations array + /// @param self The stored oracle array + /// @param time The time of the oracle initialization, via block.timestamp truncated to uint32 + /// @return cardinality The number of populated elements in the oracle array + /// @return cardinalityNext The new length of the oracle array, independent of population + function initialize(Observation[65535] storage self, uint32 time) + internal + returns (uint16 cardinality, uint16 cardinalityNext) + { + self[0] = Observation({ + blockTimestamp: time, + tickCumulative: 0, + secondsPerLiquidityCumulativeX128: 0, + initialized: true + }); + return (1, 1); + } + + /// @notice Writes an oracle observation to the array + /// @dev Writable at most once per block. Index represents the most recently written element. cardinality and index must be tracked externally. + /// If the index is at the end of the allowable array length (according to cardinality), and the next cardinality + /// is greater than the current one, cardinality may be increased. This restriction is created to preserve ordering. + /// @param self The stored oracle array + /// @param index The index of the observation that was most recently written to the observations array + /// @param blockTimestamp The timestamp of the new observation + /// @param tick The active tick at the time of the new observation + /// @param liquidity The total in-range liquidity at the time of the new observation + /// @param cardinality The number of populated elements in the oracle array + /// @param cardinalityNext The new length of the oracle array, independent of population + /// @return indexUpdated The new index of the most recently written element in the oracle array + /// @return cardinalityUpdated The new cardinality of the oracle array + function write( + Observation[65535] storage self, + uint16 index, + uint32 blockTimestamp, + int24 tick, + uint128 liquidity, + uint16 cardinality, + uint16 cardinalityNext + ) internal returns (uint16 indexUpdated, uint16 cardinalityUpdated) { + Observation memory last = self[index]; + + // early return if we've already written an observation this block + if (last.blockTimestamp == blockTimestamp) return (index, cardinality); + + // if the conditions are right, we can bump the cardinality + if (cardinalityNext > cardinality && index == (cardinality - 1)) { + cardinalityUpdated = cardinalityNext; + } else { + cardinalityUpdated = cardinality; + } + + indexUpdated = (index + 1) % cardinalityUpdated; + self[indexUpdated] = transform(last, blockTimestamp, tick, liquidity); + } + + /// @notice Prepares the oracle array to store up to `next` observations + /// @param self The stored oracle array + /// @param current The current next cardinality of the oracle array + /// @param next The proposed next cardinality which will be populated in the oracle array + /// @return next The next cardinality which will be populated in the oracle array + function grow( + Observation[65535] storage self, + uint16 current, + uint16 next + ) internal returns (uint16) { + require(current > 0, 'I'); + // no-op if the passed next value isn't greater than the current next value + if (next <= current) return current; + // store in each slot to prevent fresh SSTOREs in swaps + // this data will not be used because the initialized boolean is still false + for (uint16 i = current; i < next; i++) self[i].blockTimestamp = 1; + return next; + } + + /// @notice comparator for 32-bit timestamps + /// @dev safe for 0 or 1 overflows, a and b _must_ be chronologically before or equal to time + /// @param time A timestamp truncated to 32 bits + /// @param a A comparison timestamp from which to determine the relative position of `time` + /// @param b From which to determine the relative position of `time` + /// @return bool Whether `a` is chronologically <= `b` + function lte( + uint32 time, + uint32 a, + uint32 b + ) private pure returns (bool) { + // if there hasn't been overflow, no need to adjust + if (a <= time && b <= time) return a <= b; + + uint256 aAdjusted = a > time ? a : a + 2**32; + uint256 bAdjusted = b > time ? b : b + 2**32; + + return aAdjusted <= bAdjusted; + } + + /// @notice Fetches the observations beforeOrAt and atOrAfter a target, i.e. where [beforeOrAt, atOrAfter] is satisfied. + /// The result may be the same observation, or adjacent observations. + /// @dev The answer must be contained in the array, used when the target is located within the stored observation + /// boundaries: older than the most recent observation and younger, or the same age as, the oldest observation + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param target The timestamp at which the reserved observation should be for + /// @param index The index of the observation that was most recently written to the observations array + /// @param cardinality The number of populated elements in the oracle array + /// @return beforeOrAt The observation recorded before, or at, the target + /// @return atOrAfter The observation recorded at, or after, the target + function binarySearch( + Observation[65535] storage self, + uint32 time, + uint32 target, + uint16 index, + uint16 cardinality + ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { + uint256 l = (index + 1) % cardinality; // oldest observation + uint256 r = l + cardinality - 1; // newest observation + uint256 i; + while (true) { + i = (l + r) / 2; + + beforeOrAt = self[i % cardinality]; + + // we've landed on an uninitialized tick, keep searching higher (more recently) + if (!beforeOrAt.initialized) { + l = i + 1; + continue; + } + + atOrAfter = self[(i + 1) % cardinality]; + + bool targetAtOrAfter = lte(time, beforeOrAt.blockTimestamp, target); + + // check if we've found the answer! + if (targetAtOrAfter && lte(time, target, atOrAfter.blockTimestamp)) break; + + if (!targetAtOrAfter) r = i - 1; + else l = i + 1; + } + } + + /// @notice Fetches the observations beforeOrAt and atOrAfter a given target, i.e. where [beforeOrAt, atOrAfter] is satisfied + /// @dev Assumes there is at least 1 initialized observation. + /// Used by observeSingle() to compute the counterfactual accumulator values as of a given block timestamp. + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param target The timestamp at which the reserved observation should be for + /// @param tick The active tick at the time of the returned or simulated observation + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The total pool liquidity at the time of the call + /// @param cardinality The number of populated elements in the oracle array + /// @return beforeOrAt The observation which occurred at, or before, the given timestamp + /// @return atOrAfter The observation which occurred at, or after, the given timestamp + function getSurroundingObservations( + Observation[65535] storage self, + uint32 time, + uint32 target, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) private view returns (Observation memory beforeOrAt, Observation memory atOrAfter) { + // optimistically set before to the newest observation + beforeOrAt = self[index]; + + // if the target is chronologically at or after the newest observation, we can early return + if (lte(time, beforeOrAt.blockTimestamp, target)) { + if (beforeOrAt.blockTimestamp == target) { + // if newest observation equals target, we're in the same block, so we can ignore atOrAfter + return (beforeOrAt, atOrAfter); + } else { + // otherwise, we need to transform + return (beforeOrAt, transform(beforeOrAt, target, tick, liquidity)); + } + } + + // now, set before to the oldest observation + beforeOrAt = self[(index + 1) % cardinality]; + if (!beforeOrAt.initialized) beforeOrAt = self[0]; + + // ensure that the target is chronologically at or after the oldest observation + require(lte(time, beforeOrAt.blockTimestamp, target), 'OLD'); + + // if we've reached this point, we have to binary search + return binarySearch(self, time, target, index, cardinality); + } + + /// @dev Reverts if an observation at or before the desired observation timestamp does not exist. + /// 0 may be passed as `secondsAgo' to return the current cumulative values. + /// If called with a timestamp falling between two observations, returns the counterfactual accumulator values + /// at exactly the timestamp between the two observations. + /// @param self The stored oracle array + /// @param time The current block timestamp + /// @param secondsAgo The amount of time to look back, in seconds, at which point to return an observation + /// @param tick The current tick + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The current in-range pool liquidity + /// @param cardinality The number of populated elements in the oracle array + /// @return tickCumulative The tick * time elapsed since the pool was first initialized, as of `secondsAgo` + /// @return secondsPerLiquidityCumulativeX128 The time elapsed / max(1, liquidity) since the pool was first initialized, as of `secondsAgo` + function observeSingle( + Observation[65535] storage self, + uint32 time, + uint32 secondsAgo, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) internal view returns (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) { + unchecked { + if (secondsAgo == 0) { + Observation memory last = self[index]; + if (last.blockTimestamp != time) last = transform(last, time, tick, liquidity); + return (last.tickCumulative, last.secondsPerLiquidityCumulativeX128); + } + + uint32 target = time - secondsAgo; + + (Observation memory beforeOrAt, Observation memory atOrAfter) = + getSurroundingObservations(self, time, target, tick, index, liquidity, cardinality); + + if (target == beforeOrAt.blockTimestamp) { + // we're at the left boundary + return (beforeOrAt.tickCumulative, beforeOrAt.secondsPerLiquidityCumulativeX128); + } else if (target == atOrAfter.blockTimestamp) { + // we're at the right boundary + return (atOrAfter.tickCumulative, atOrAfter.secondsPerLiquidityCumulativeX128); + } else { + // we're in the middle + uint32 observationTimeDelta = atOrAfter.blockTimestamp - beforeOrAt.blockTimestamp; + uint32 targetDelta = target - beforeOrAt.blockTimestamp; + return ( + beforeOrAt.tickCumulative + + ((atOrAfter.tickCumulative - beforeOrAt.tickCumulative) / int56(uint56(observationTimeDelta))) * + int56(uint56(targetDelta)), + beforeOrAt.secondsPerLiquidityCumulativeX128 + + uint160( + (uint256( + atOrAfter.secondsPerLiquidityCumulativeX128 - beforeOrAt.secondsPerLiquidityCumulativeX128 + ) * targetDelta) / observationTimeDelta + ) + ); + } + } + } + + /// @notice Returns the accumulator values as of each time seconds ago from the given time in the array of `secondsAgos` + /// @dev Reverts if `secondsAgos` > oldest observation + /// @param self The stored oracle array + /// @param time The current block.timestamp + /// @param secondsAgos Each amount of time to look back, in seconds, at which point to return an observation + /// @param tick The current tick + /// @param index The index of the observation that was most recently written to the observations array + /// @param liquidity The current in-range pool liquidity + /// @param cardinality The number of populated elements in the oracle array + /// @return tickCumulatives The tick * time elapsed since the pool was first initialized, as of each `secondsAgo` + /// @return secondsPerLiquidityCumulativeX128s The cumulative seconds / max(1, liquidity) since the pool was first initialized, as of each `secondsAgo` + function observe( + Observation[65535] storage self, + uint32 time, + uint32[] memory secondsAgos, + int24 tick, + uint16 index, + uint128 liquidity, + uint16 cardinality + ) internal view returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) { + require(cardinality > 0, 'I'); + + tickCumulatives = new int56[](secondsAgos.length); + secondsPerLiquidityCumulativeX128s = new uint160[](secondsAgos.length); + for (uint256 i = 0; i < secondsAgos.length; i++) { + (tickCumulatives[i], secondsPerLiquidityCumulativeX128s[i]) = observeSingle( + self, + time, + secondsAgos[i], + tick, + index, + liquidity, + cardinality + ); + } + } +} + + +// File contracts/interfaces/IUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +/// @title An interface for a contract that is capable of deploying Uniswap V3 Pools +/// @notice A contract that constructs a pool must implement this to pass arguments to the pool +/// @dev This is used to avoid having constructor arguments in the pool contract, which results in the init code hash +/// of the pool being constant allowing the CREATE2 address of the pool to be cheaply computed on-chain +interface IUniswapV3PoolDeployer { + /// @notice Get the parameters to be used in constructing the pool, set transiently during pool creation. + /// @dev Called by the pool constructor to fetch the parameters of the pool + /// Returns factory The factory address + /// Returns token0 The first token of the pool by address sort order + /// Returns token1 The second token of the pool by address sort order + /// Returns fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// Returns tickSpacing The minimum number of ticks between initialized ticks + function parameters() + external + view + returns ( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ); +} + + +// File contracts/interfaces/IUniswapV3Factory.sol + + +pragma solidity >=0.0; + +/// @title The interface for the Uniswap V3 Factory +/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees +interface IUniswapV3Factory { + /// @notice Emitted when the owner of the factory is changed + /// @param oldOwner The owner before the owner was changed + /// @param newOwner The owner after the owner was changed + event OwnerChanged(address indexed oldOwner, address indexed newOwner); + + /// @notice Emitted when a pool is created + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks + /// @param pool The address of the created pool + event PoolCreated( + address indexed token0, + address indexed token1, + uint24 indexed fee, + int24 tickSpacing, + address pool + ); + + /// @notice Emitted when a new fee amount is enabled for pool creation via the factory + /// @param fee The enabled fee, denominated in hundredths of a bip + /// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee + event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing); + + /// @notice Returns the current owner of the factory + /// @dev Can be changed by the current owner via setOwner + /// @return The address of the factory owner + function owner() external view returns (address); + + /// @notice Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled + /// @dev A fee amount can never be removed, so this value should be hard coded or cached in the calling context + /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee + /// @return The tick spacing + function feeAmountTickSpacing(uint24 fee) external view returns (int24); + + /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist + /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order + /// @param tokenA The contract address of either token0 or token1 + /// @param tokenB The contract address of the other token + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @return pool The pool address + function getPool( + address tokenA, + address tokenB, + uint24 fee + ) external view returns (address pool); + + /// @notice Creates a pool for the given two tokens and fee + /// @param tokenA One of the two tokens in the desired pool + /// @param tokenB The other of the two tokens in the desired pool + /// @param fee The desired fee for the pool + /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved + /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments + /// are invalid. + /// @return pool The address of the newly created pool + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) external returns (address pool); + + /// @notice Updates the owner of the factory + /// @dev Must be called by the current owner + /// @param _owner The new owner of the factory + function setOwner(address _owner) external; + + /// @notice Enables a fee amount with the given tickSpacing + /// @dev Fee amounts may never be removed once enabled + /// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6) + /// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount + function enableFeeAmount(uint24 fee, int24 tickSpacing) external; +} + + +// File contracts/interfaces/callback/IUniswapV3MintCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#mint +/// @notice Any contract that calls IUniswapV3PoolActions#mint must implement this interface +interface IUniswapV3MintCallback { + /// @notice Called to `msg.sender` after minting liquidity to a position from IUniswapV3Pool#mint. + /// @dev In the implementation you must pay the pool tokens owed for the minted liquidity. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param amount0Owed The amount of token0 due to the pool for the minted liquidity + /// @param amount1Owed The amount of token1 due to the pool for the minted liquidity + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#mint call + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3SwapCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#swap +/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface +interface IUniswapV3SwapCallback { + /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. + /// @dev In the implementation you must pay the pool tokens owed for the swap. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. + /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. + /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by + /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external; +} + + +// File contracts/interfaces/callback/IUniswapV3FlashCallback.sol + + +pragma solidity >=0.0; + +/// @title Callback for IUniswapV3PoolActions#flash +/// @notice Any contract that calls IUniswapV3PoolActions#flash must implement this interface +interface IUniswapV3FlashCallback { + /// @notice Called to `msg.sender` after transferring to the recipient from IUniswapV3Pool#flash. + /// @dev In the implementation you must repay the pool the tokens sent by flash plus the computed fee amounts. + /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. + /// @param fee0 The fee amount in token0 due to the pool by the end of the flash + /// @param fee1 The fee amount in token1 due to the pool by the end of the flash + /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#flash call + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external; +} + + +// File contracts/UniswapV3Pool.sol + + +pragma solidity >=0.0; + + + + + + + + + + + + + + + + + +contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall { + using LowGasSafeMath for uint256; + using LowGasSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + using Tick for mapping(int24 => Tick.Info); + using TickBitmap for mapping(int16 => uint256); + using Position for mapping(bytes32 => Position.Info); + using Position for Position.Info; + using Oracle for Oracle.Observation[65535]; + + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override factory; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token0; + /// @inheritdoc IUniswapV3PoolImmutables + address public immutable override token1; + /// @inheritdoc IUniswapV3PoolImmutables + uint24 public immutable override fee; + + /// @inheritdoc IUniswapV3PoolImmutables + int24 public immutable override tickSpacing; + + /// @inheritdoc IUniswapV3PoolImmutables + uint128 public immutable override maxLiquidityPerTick; + + struct Slot0 { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // the most-recently updated index of the observations array + uint16 observationIndex; + // the current maximum number of observations that are being stored + uint16 observationCardinality; + // the next maximum number of observations to store, triggered in observations.write + uint16 observationCardinalityNext; + // the current protocol fee as a percentage of the swap fee taken on withdrawal + // represented as an integer denominator (1/x)% + uint8 feeProtocol; + // whether the pool is locked + bool unlocked; + } + /// @inheritdoc IUniswapV3PoolState + Slot0 public override slot0; + + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal0X128; + /// @inheritdoc IUniswapV3PoolState + uint256 public override feeGrowthGlobal1X128; + + // accumulated protocol fees in token0/token1 units + struct ProtocolFees { + uint128 token0; + uint128 token1; + } + /// @inheritdoc IUniswapV3PoolState + ProtocolFees public override protocolFees; + + /// @inheritdoc IUniswapV3PoolState + uint128 public override liquidity; + + /// @inheritdoc IUniswapV3PoolState + mapping(int24 => Tick.Info) public override ticks; + /// @inheritdoc IUniswapV3PoolState + mapping(int16 => uint256) public override tickBitmap; + /// @inheritdoc IUniswapV3PoolState + mapping(bytes32 => Position.Info) public override positions; + /// @inheritdoc IUniswapV3PoolState + Oracle.Observation[65535] public override observations; + + /// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance + /// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because + /// we use balance checks to determine the payment status of interactions such as mint, swap and flash. + modifier lock() { + require(slot0.unlocked, 'LOK'); + slot0.unlocked = false; + _; + slot0.unlocked = true; + } + + /// @dev Prevents calling a function from anyone except the address returned by IUniswapV3Factory#owner() + modifier onlyFactoryOwner() { + require(msg.sender == IUniswapV3Factory(factory).owner()); + _; + } + + constructor() { + int24 _tickSpacing; + (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters(); + tickSpacing = _tickSpacing; + + maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing); + } + + /// @dev Common checks for valid tick inputs. + function checkTicks(int24 tickLower, int24 tickUpper) private pure { + require(tickLower < tickUpper, 'TLU'); + require(tickLower >= TickMath.MIN_TICK, 'TLM'); + require(tickUpper <= TickMath.MAX_TICK, 'TUM'); + } + + /// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests. + function _blockTimestamp() internal view virtual returns (uint32) { + return uint32(block.timestamp); // truncation is desired + } + + /// @dev Get the pool's balance of token0 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance0() private view returns (uint256) { + (bool success, bytes memory data) = + token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @dev Get the pool's balance of token1 + /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize + /// check + function balance1() private view returns (uint256) { + (bool success, bytes memory data) = + token1.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this))); + require(success && data.length >= 32); + return abi.decode(data, (uint256)); + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function snapshotCumulativesInside(int24 tickLower, int24 tickUpper) + external + view + override + noDelegateCall + returns ( + int56 tickCumulativeInside, + uint160 secondsPerLiquidityInsideX128, + uint32 secondsInside + ) + { + unchecked { + checkTicks(tickLower, tickUpper); + + int56 tickCumulativeLower; + int56 tickCumulativeUpper; + uint160 secondsPerLiquidityOutsideLowerX128; + uint160 secondsPerLiquidityOutsideUpperX128; + uint32 secondsOutsideLower; + uint32 secondsOutsideUpper; + + { + Tick.Info storage lower = ticks[tickLower]; + Tick.Info storage upper = ticks[tickUpper]; + bool initializedLower; + (tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = ( + lower.tickCumulativeOutside, + lower.secondsPerLiquidityOutsideX128, + lower.secondsOutside, + lower.initialized + ); + require(initializedLower); + + bool initializedUpper; + (tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = ( + upper.tickCumulativeOutside, + upper.secondsPerLiquidityOutsideX128, + upper.secondsOutside, + upper.initialized + ); + require(initializedUpper); + } + + Slot0 memory _slot0 = slot0; + + if (_slot0.tick < tickLower) { + return ( + tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128, + secondsOutsideLower - secondsOutsideUpper + ); + } else if (_slot0.tick < tickUpper) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + _slot0.tick, + _slot0.observationIndex, + liquidity, + _slot0.observationCardinality + ); + return ( + tickCumulative - tickCumulativeLower - tickCumulativeUpper, + secondsPerLiquidityCumulativeX128 - + secondsPerLiquidityOutsideLowerX128 - + secondsPerLiquidityOutsideUpperX128, + time - secondsOutsideLower - secondsOutsideUpper + ); + } else { + return ( + tickCumulativeUpper - tickCumulativeLower, + secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128, + secondsOutsideUpper - secondsOutsideLower + ); + } + } + } + + /// @inheritdoc IUniswapV3PoolDerivedState + function observe(uint32[] calldata secondsAgos) + external + view + override + noDelegateCall + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return + observations.observe( + _blockTimestamp(), + secondsAgos, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + } + + /// @inheritdoc IUniswapV3PoolActions + function increaseObservationCardinalityNext(uint16 observationCardinalityNext) + external + override + lock + noDelegateCall + { + uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event + uint16 observationCardinalityNextNew = + observations.grow(observationCardinalityNextOld, observationCardinalityNext); + slot0.observationCardinalityNext = observationCardinalityNextNew; + if (observationCardinalityNextOld != observationCardinalityNextNew) + emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew); + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev not locked because it initializes unlocked + function initialize(uint160 sqrtPriceX96) external override { + require(slot0.sqrtPriceX96 == 0, 'AI'); + + int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); + + (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp()); + + slot0 = Slot0({ + sqrtPriceX96: sqrtPriceX96, + tick: tick, + observationIndex: 0, + observationCardinality: cardinality, + observationCardinalityNext: cardinalityNext, + feeProtocol: 0, + unlocked: true + }); + + emit Initialize(sqrtPriceX96, tick); + } + + struct ModifyPositionParams { + // the address that owns the position + address owner; + // the lower and upper tick of the position + int24 tickLower; + int24 tickUpper; + // any change in liquidity + int128 liquidityDelta; + } + + /// @dev Effect some changes to a position + /// @param params the position details and the change to the position's liquidity to effect + /// @return position a storage pointer referencing the position with the given owner and tick range + /// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient + /// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient + function _modifyPosition(ModifyPositionParams memory params) + private + noDelegateCall + returns ( + Position.Info storage position, + int256 amount0, + int256 amount1 + ) + { + checkTicks(params.tickLower, params.tickUpper); + + Slot0 memory _slot0 = slot0; // SLOAD for gas optimization + + position = _updatePosition( + params.owner, + params.tickLower, + params.tickUpper, + params.liquidityDelta, + _slot0.tick + ); + + if (params.liquidityDelta != 0) { + if (_slot0.tick < params.tickLower) { + // current tick is below the passed range; liquidity can only become in range by crossing from left to + // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it + amount0 = SqrtPriceMath.getAmount0Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } else if (_slot0.tick < params.tickUpper) { + // current tick is inside the passed range + uint128 liquidityBefore = liquidity; // SLOAD for gas optimization + + // write an oracle entry + (slot0.observationIndex, slot0.observationCardinality) = observations.write( + _slot0.observationIndex, + _blockTimestamp(), + _slot0.tick, + liquidityBefore, + _slot0.observationCardinality, + _slot0.observationCardinalityNext + ); + + amount0 = SqrtPriceMath.getAmount0Delta( + _slot0.sqrtPriceX96, + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + _slot0.sqrtPriceX96, + params.liquidityDelta + ); + + liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta); + } else { + // current tick is above the passed range; liquidity can only become in range by crossing from right to + // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it + amount1 = SqrtPriceMath.getAmount1Delta( + TickMath.getSqrtRatioAtTick(params.tickLower), + TickMath.getSqrtRatioAtTick(params.tickUpper), + params.liquidityDelta + ); + } + } + } + + /// @dev Gets and updates a position with the given liquidity delta + /// @param owner the owner of the position + /// @param tickLower the lower tick of the position's tick range + /// @param tickUpper the upper tick of the position's tick range + /// @param tick the current tick, passed to avoid sloads + function _updatePosition( + address owner, + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta, + int24 tick + ) private returns (Position.Info storage position) { + position = positions.get(owner, tickLower, tickUpper); + + uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization + uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization + + // if we need to update the ticks, do it + bool flippedLower; + bool flippedUpper; + if (liquidityDelta != 0) { + uint32 time = _blockTimestamp(); + (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) = + observations.observeSingle( + time, + 0, + slot0.tick, + slot0.observationIndex, + liquidity, + slot0.observationCardinality + ); + + flippedLower = ticks.update( + tickLower, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + false, + maxLiquidityPerTick + ); + flippedUpper = ticks.update( + tickUpper, + tick, + liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + true, + maxLiquidityPerTick + ); + + if (flippedLower) { + tickBitmap.flipTick(tickLower, tickSpacing); + } + if (flippedUpper) { + tickBitmap.flipTick(tickUpper, tickSpacing); + } + } + + (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) = + ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128); + + position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128); + + // clear any tick data that is no longer needed + if (liquidityDelta < 0) { + if (flippedLower) { + ticks.clear(tickLower); + } + if (flippedUpper) { + ticks.clear(tickUpper); + } + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function mint( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount, + bytes calldata data + ) external override lock returns (uint256 amount0, uint256 amount1) { + require(amount > 0); + (, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: recipient, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(amount0Int); + amount1 = uint256(amount1Int); + + uint256 balance0Before; + uint256 balance1Before; + if (amount0 > 0) balance0Before = balance0(); + if (amount1 > 0) balance1Before = balance1(); + IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data); + if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0'); + if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1'); + + emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1); + } + + /// @inheritdoc IUniswapV3PoolActions + function collect( + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock returns (uint128 amount0, uint128 amount1) { + unchecked { + // we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1} + Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper); + + amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested; + amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested; + + if (amount0 > 0) { + position.tokensOwed0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + position.tokensOwed1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1); + } + } + + /// @inheritdoc IUniswapV3PoolActions + /// @dev noDelegateCall is applied indirectly via _modifyPosition + function burn( + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external override lock returns (uint256 amount0, uint256 amount1) { + (Position.Info storage position, int256 amount0Int, int256 amount1Int) = + _modifyPosition( + ModifyPositionParams({ + owner: msg.sender, + tickLower: tickLower, + tickUpper: tickUpper, + liquidityDelta: -int256(uint256(amount)).toInt128() + }) + ); + + amount0 = uint256(-amount0Int); + amount1 = uint256(-amount1Int); + + if (amount0 > 0 || amount1 > 0) { + (position.tokensOwed0, position.tokensOwed1) = ( + position.tokensOwed0 + uint128(amount0), + position.tokensOwed1 + uint128(amount1) + ); + } + + emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1); + } + + struct SwapCache { + // the protocol fee for the input token + uint8 feeProtocol; + // liquidity at the beginning of the swap + uint128 liquidityStart; + // the timestamp of the current block + uint32 blockTimestamp; + // the current value of the tick accumulator, computed only if we cross an initialized tick + int56 tickCumulative; + // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick + uint160 secondsPerLiquidityCumulativeX128; + // whether we've computed and cached the above two accumulators + bool computedLatestObservation; + } + + // the top level state of the swap, the results of which are recorded in storage at the end + struct SwapState { + // the amount remaining to be swapped in/out of the input/output asset + int256 amountSpecifiedRemaining; + // the amount already swapped out/in of the output/input asset + int256 amountCalculated; + // current sqrt(price) + uint160 sqrtPriceX96; + // the tick associated with the current price + int24 tick; + // the global fee growth of the input token + uint256 feeGrowthGlobalX128; + // amount of input token paid as protocol fee + uint128 protocolFee; + // the current liquidity in range + uint128 liquidity; + } + + struct StepComputations { + // the price at the beginning of the step + uint160 sqrtPriceStartX96; + // the next tick to swap to from the current tick in the swap direction + int24 tickNext; + // whether tickNext is initialized or not + bool initialized; + // sqrt(price) for the next tick (1/0) + uint160 sqrtPriceNextX96; + // how much is being swapped in in this step + uint256 amountIn; + // how much is being swapped out + uint256 amountOut; + // how much fee is being paid in + uint256 feeAmount; + } + + /// @inheritdoc IUniswapV3PoolActions + function swap( + address recipient, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata data + ) external override noDelegateCall returns (int256 amount0, int256 amount1) { + unchecked { + require(amountSpecified != 0, 'AS'); + + Slot0 memory slot0Start = slot0; + + require(slot0Start.unlocked, 'LOK'); + require( + zeroForOne + ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO + : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO, + 'SPL' + ); + + slot0.unlocked = false; + + SwapCache memory cache = + SwapCache({ + liquidityStart: liquidity, + blockTimestamp: _blockTimestamp(), + feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4), + secondsPerLiquidityCumulativeX128: 0, + tickCumulative: 0, + computedLatestObservation: false + }); + + bool exactInput = amountSpecified > 0; + + SwapState memory state = + SwapState({ + amountSpecifiedRemaining: amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128, + protocolFee: 0, + liquidity: cache.liquidityStart + }); + + // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) { + StepComputations memory step; + + step.sqrtPriceStartX96 = state.sqrtPriceX96; + + (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord( + state.tick, + tickSpacing, + zeroForOne + ); + + // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds + if (step.tickNext < TickMath.MIN_TICK) { + step.tickNext = TickMath.MIN_TICK; + } else if (step.tickNext > TickMath.MAX_TICK) { + step.tickNext = TickMath.MAX_TICK; + } + + // get the price for the next tick + step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext); + + // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted + (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( + state.sqrtPriceX96, + (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, + state.liquidity, + state.amountSpecifiedRemaining, + fee + ); + + if (exactInput) { + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); + state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256()); + } else { + state.amountSpecifiedRemaining += step.amountOut.toInt256(); + state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256()); + } + + // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee + if (cache.feeProtocol > 0) { + uint256 delta = step.feeAmount / cache.feeProtocol; + step.feeAmount -= delta; + state.protocolFee += uint128(delta); + } + + // update global fee tracker + if (state.liquidity > 0) + state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity); + + // shift tick if we reached the next price + if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { + // if the tick is initialized, run the tick transition + if (step.initialized) { + // check for the placeholder value, which we replace with the actual value the first time the swap + // crosses an initialized tick + if (!cache.computedLatestObservation) { + (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle( + cache.blockTimestamp, + 0, + slot0Start.tick, + slot0Start.observationIndex, + cache.liquidityStart, + slot0Start.observationCardinality + ); + cache.computedLatestObservation = true; + } + int128 liquidityNet = + ticks.cross( + step.tickNext, + (zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128), + (zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128), + cache.secondsPerLiquidityCumulativeX128, + cache.tickCumulative, + cache.blockTimestamp + ); + // if we're moving leftward, we interpret liquidityNet as the opposite sign + // safe because liquidityNet cannot be type(int128).min + if (zeroForOne) liquidityNet = -liquidityNet; + + state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); + } + + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; + } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { + // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved + state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96); + } + } + + // update tick and write an oracle entry if the tick change + if (state.tick != slot0Start.tick) { + (uint16 observationIndex, uint16 observationCardinality) = + observations.write( + slot0Start.observationIndex, + cache.blockTimestamp, + slot0Start.tick, + cache.liquidityStart, + slot0Start.observationCardinality, + slot0Start.observationCardinalityNext + ); + (slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = ( + state.sqrtPriceX96, + state.tick, + observationIndex, + observationCardinality + ); + } else { + // otherwise just update the price + slot0.sqrtPriceX96 = state.sqrtPriceX96; + } + + // update liquidity if it changed + if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity; + + // update fee growth global and, if necessary, protocol fees + // overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees + if (zeroForOne) { + feeGrowthGlobal0X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee; + } else { + feeGrowthGlobal1X128 = state.feeGrowthGlobalX128; + if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee; + } + + (amount0, amount1) = zeroForOne == exactInput + ? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated) + : (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining); + + // do the transfers and collect payment + if (zeroForOne) { + if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1)); + + uint256 balance0Before = balance0(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA'); + } else { + if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0)); + + uint256 balance1Before = balance1(); + IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data); + require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA'); + } + + emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick); + slot0.unlocked = true; + } + } + + /// @inheritdoc IUniswapV3PoolActions + function flash( + address recipient, + uint256 amount0, + uint256 amount1, + bytes calldata data + ) external override lock noDelegateCall { + unchecked { + uint128 _liquidity = liquidity; + require(_liquidity > 0, 'L'); + + uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6); + uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6); + uint256 balance0Before = balance0(); + uint256 balance1Before = balance1(); + + if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0); + if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1); + + IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data); + + uint256 balance0After = balance0(); + uint256 balance1After = balance1(); + + require(balance0Before.add(fee0) <= balance0After, 'F0'); + require(balance1Before.add(fee1) <= balance1After, 'F1'); + + // sub is safe because we know balanceAfter is gt balanceBefore by at least fee + uint256 paid0 = balance0After - balance0Before; + uint256 paid1 = balance1After - balance1Before; + + if (paid0 > 0) { + uint8 feeProtocol0 = slot0.feeProtocol % 16; + uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0; + if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0); + feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - fees0, FixedPoint128.Q128, _liquidity); + } + if (paid1 > 0) { + uint8 feeProtocol1 = slot0.feeProtocol >> 4; + uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1; + if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1); + feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - fees1, FixedPoint128.Q128, _liquidity); + } + + emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1); + } + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner { + require( + (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) && + (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10)) + ); + uint8 feeProtocolOld = slot0.feeProtocol; + slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4); + emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1); + } + + /// @inheritdoc IUniswapV3PoolOwnerActions + function collectProtocol( + address recipient, + uint128 amount0Requested, + uint128 amount1Requested + ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) { + amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested; + amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested; + + if (amount0 > 0) { + if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings + protocolFees.token0 -= amount0; + TransferHelper.safeTransfer(token0, recipient, amount0); + } + if (amount1 > 0) { + if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings + protocolFees.token1 -= amount1; + TransferHelper.safeTransfer(token1, recipient, amount1); + } + + emit CollectProtocol(msg.sender, recipient, amount0, amount1); + } +} + + +// File contracts/test/MockTimeUniswapV3Pool.sol + + +pragma solidity >=0.0; + +// used for testing time dependent behavior +contract MockTimeUniswapV3Pool is UniswapV3Pool { + // Monday, October 5, 2020 9:00:00 AM GMT-05:00 + uint256 public time = 1601906400; + + function setFeeGrowthGlobal0X128(uint256 _feeGrowthGlobal0X128) external { + feeGrowthGlobal0X128 = _feeGrowthGlobal0X128; + } + + function setFeeGrowthGlobal1X128(uint256 _feeGrowthGlobal1X128) external { + feeGrowthGlobal1X128 = _feeGrowthGlobal1X128; + } + + function advanceTime(uint256 by) external { + time += by; + } + + function _blockTimestamp() internal view override returns (uint32) { + return uint32(time); + } +} + + +// File contracts/test/MockTimeUniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract MockTimeUniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + Parameters public override parameters; + + event PoolDeployed(address pool); + + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) external returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address( + new MockTimeUniswapV3Pool{salt: keccak256(abi.encodePacked(token0, token1, fee, tickSpacing))}() + ); + emit PoolDeployed(pool); + delete parameters; + } +} + + +// File contracts/test/NoDelegateCallTest.sol + + +pragma solidity >=0.0; + +contract NoDelegateCallTest is NoDelegateCall { + function canBeDelegateCalled() public view returns (uint256) { + return block.timestamp / 5; + } + + function cannotBeDelegateCalled() public view noDelegateCall returns (uint256) { + return block.timestamp / 5; + } + + function getGasCostOfCanBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + canBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function getGasCostOfCannotBeDelegateCalled() external view returns (uint256) { + uint256 gasBefore = gasleft(); + cannotBeDelegateCalled(); + return gasBefore - gasleft(); + } + + function callsIntoNoDelegateCallFunction() external view { + noDelegateCallPrivate(); + } + + function noDelegateCallPrivate() private view noDelegateCall {} +} + + +// File contracts/test/OracleTest.sol + + +pragma solidity >=0.0; +pragma abicoder v2; + +contract OracleTest { + using Oracle for Oracle.Observation[65535]; + + Oracle.Observation[65535] public observations; + + uint32 public time; + int24 public tick; + uint128 public liquidity; + uint16 public index; + uint16 public cardinality; + uint16 public cardinalityNext; + + struct InitializeParams { + uint32 time; + int24 tick; + uint128 liquidity; + } + + function initialize(InitializeParams calldata params) external { + require(cardinality == 0, 'already initialized'); + time = params.time; + tick = params.tick; + liquidity = params.liquidity; + (cardinality, cardinalityNext) = observations.initialize(params.time); + } + + function advanceTime(uint32 by) public { + unchecked { + time += by; + } + } + + struct UpdateParams { + uint32 advanceTimeBy; + int24 tick; + uint128 liquidity; + } + + // write an observation, then change tick and liquidity + function update(UpdateParams calldata params) external { + advanceTime(params.advanceTimeBy); + (index, cardinality) = observations.write(index, time, tick, liquidity, cardinality, cardinalityNext); + tick = params.tick; + liquidity = params.liquidity; + } + + function batchUpdate(UpdateParams[] calldata params) external { + // sload everything + int24 _tick = tick; + uint128 _liquidity = liquidity; + uint16 _index = index; + uint16 _cardinality = cardinality; + uint16 _cardinalityNext = cardinalityNext; + uint32 _time = time; + + for (uint256 i = 0; i < params.length; i++) { + _time += params[i].advanceTimeBy; + (_index, _cardinality) = observations.write( + _index, + _time, + _tick, + _liquidity, + _cardinality, + _cardinalityNext + ); + _tick = params[i].tick; + _liquidity = params[i].liquidity; + } + + // sstore everything + tick = _tick; + liquidity = _liquidity; + index = _index; + cardinality = _cardinality; + time = _time; + } + + function grow(uint16 _cardinalityNext) external { + cardinalityNext = observations.grow(cardinalityNext, _cardinalityNext); + } + + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return observations.observe(time, secondsAgos, tick, index, liquidity, cardinality); + } + + function getGasCostOfObserve(uint32[] calldata secondsAgos) external view returns (uint256) { + (uint32 _time, int24 _tick, uint128 _liquidity, uint16 _index) = (time, tick, liquidity, index); + uint256 gasBefore = gasleft(); + observations.observe(_time, secondsAgos, _tick, _index, _liquidity, cardinality); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/OracleEchidnaTest.sol + + +pragma solidity >=0.0; + +contract OracleEchidnaTest { + OracleTest private oracle; + + bool private initialized; + uint32 private timePassed; + + constructor() { + oracle = new OracleTest(); + } + + function initialize( + uint32 time, + int24 tick, + uint128 liquidity + ) external { + oracle.initialize(OracleTest.InitializeParams({time: time, tick: tick, liquidity: liquidity})); + initialized = true; + } + + function limitTimePassed(uint32 by) private { + require(timePassed + by >= timePassed); + timePassed += by; + } + + function advanceTime(uint32 by) public { + limitTimePassed(by); + oracle.advanceTime(by); + } + + // write an observation, then change tick and liquidity + function update( + uint32 advanceTimeBy, + int24 tick, + uint128 liquidity + ) external { + limitTimePassed(advanceTimeBy); + oracle.update(OracleTest.UpdateParams({advanceTimeBy: advanceTimeBy, tick: tick, liquidity: liquidity})); + } + + function grow(uint16 cardinality) external { + oracle.grow(cardinality); + } + + function checkTimeWeightedResultAssertions(uint32 secondsAgo0, uint32 secondsAgo1) private view { + require(secondsAgo0 != secondsAgo1); + require(initialized); + // secondsAgo0 should be the larger one + if (secondsAgo0 < secondsAgo1) (secondsAgo0, secondsAgo1) = (secondsAgo1, secondsAgo0); + + uint32 timeElapsed = secondsAgo0 - secondsAgo1; + + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo0; + secondsAgos[1] = secondsAgo1; + + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + int56 timeWeightedTick = (tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(timeElapsed)); + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(timeElapsed) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + assert(timeWeightedTick <= type(int24).max); + assert(timeWeightedTick >= type(int24).min); + } + + function echidna_indexAlwaysLtCardinality() external view returns (bool) { + return oracle.index() < oracle.cardinality() || !initialized; + } + + function echidna_AlwaysInitialized() external view returns (bool) { + (, , , bool isInitialized) = oracle.observations(0); + return oracle.cardinality() == 0 || isInitialized; + } + + function echidna_cardinalityAlwaysLteNext() external view returns (bool) { + return oracle.cardinality() <= oracle.cardinalityNext(); + } + + function echidna_canAlwaysObserve0IfInitialized() external view returns (bool) { + if (!initialized) { + return true; + } + uint32[] memory arr = new uint32[](1); + arr[0] = 0; + (bool success, ) = address(oracle).staticcall(abi.encodeWithSelector(OracleTest.observe.selector, arr)); + return success; + } + + function checkTwoAdjacentObservationsTickCumulativeModTimeElapsedAlways0(uint16 index) external view { + uint16 cardinality = oracle.cardinality(); + // check that the observations are initialized, and that the index is not the oldest observation + require(index < cardinality && index != (oracle.index() + 1) % cardinality); + + (uint32 blockTimestamp0, int56 tickCumulative0, , bool initialized0) = + oracle.observations(index == 0 ? cardinality - 1 : index - 1); + (uint32 blockTimestamp1, int56 tickCumulative1, , bool initialized1) = oracle.observations(index); + + require(initialized0); + require(initialized1); + + uint32 timeElapsed = blockTimestamp1 - blockTimestamp0; + assert(timeElapsed > 0); + assert((tickCumulative1 - tickCumulative0) % int56(uint56(timeElapsed)) == 0); + } + + function checkTimeWeightedAveragesAlwaysFitsType(uint32 secondsAgo) external view { + require(initialized); + require(secondsAgo > 0); + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo; + secondsAgos[1] = 0; + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + oracle.observe(secondsAgos); + + // compute the time weighted tick, rounded towards negative infinity + int56 numerator = tickCumulatives[1] - tickCumulatives[0]; + int56 timeWeightedTick = numerator / int56(uint56(secondsAgo)); + if (numerator < 0 && numerator % int56(uint56(secondsAgo)) != 0) { + timeWeightedTick--; + } + + // the time weighted averages fit in their respective accumulated types + assert(timeWeightedTick <= type(int24).max && timeWeightedTick >= type(int24).min); + + uint256 timeWeightedHarmonicMeanLiquidity = + (uint256(secondsAgo) * type(uint160).max) / + (uint256(secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]) << 32); + assert(timeWeightedHarmonicMeanLiquidity <= type(uint128).max); + } +} + + +// File contracts/test/SqrtPriceMathEchidnaTest.sol + + +pragma solidity >=0.0; + + + +contract SqrtPriceMathEchidnaTest { + function mulDivRoundingUpInvariants( + uint256 x, + uint256 y, + uint256 z + ) external pure { + require(z > 0); + uint256 notRoundedUp = FullMath.mulDiv(x, y, z); + uint256 roundedUp = FullMath.mulDivRoundingUp(x, y, z); + assert(roundedUp >= notRoundedUp); + assert(roundedUp - notRoundedUp < 2); + if (roundedUp - notRoundedUp == 1) { + assert(mulmod(x, y, z) > 0); + } else { + assert(mulmod(x, y, z) == 0); + } + } + + function getNextSqrtPriceFromInputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true)); + } else { + assert(sqrtQ >= sqrtP); + assert(amountIn >= SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true)); + } + } + + function getNextSqrtPriceFromOutputInvariants( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure { + uint160 sqrtQ = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + + if (zeroForOne) { + assert(sqrtQ <= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + } else { + assert(sqrtQ > 0); // this has to be true, otherwise we need another require + assert(sqrtQ >= sqrtP); + assert(amountOut <= SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + } + } + + function getNextSqrtPriceFromAmount0RoundingUpInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 <= sqrtPX96); + } else { + assert(sqrtQX96 >= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getNextSqrtPriceFromAmount1RoundingDownInvariants( + uint160 sqrtPX96, + uint128 liquidity, + uint256 amount, + bool add + ) external pure { + require(sqrtPX96 > 0); + require(liquidity > 0); + uint160 sqrtQX96 = SqrtPriceMath.getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount, add); + + if (add) { + assert(sqrtQX96 >= sqrtPX96); + } else { + assert(sqrtQX96 <= sqrtPX96); + } + + if (amount == 0) { + assert(sqrtPX96 == sqrtQX96); + } + } + + function getAmount0DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount0Down = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, false); + assert(amount0Down == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, false)); + + uint256 amount0Up = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, true); + assert(amount0Up == SqrtPriceMath.getAmount0Delta(sqrtP, sqrtQ, liquidity, true)); + + assert(amount0Down <= amount0Up); + // diff is 0 or 1 + assert(amount0Up - amount0Down < 2); + } + + // ensure that chained division is always equal to the full-precision case for + // liquidity * (sqrt(P) - sqrt(Q)) / (sqrt(P) * sqrt(Q)) + function getAmount0DeltaEquivalency( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity, + bool roundUp + ) external pure { + require(sqrtP >= sqrtQ); + require(sqrtP > 0 && sqrtQ > 0); + require((sqrtP * sqrtQ) / sqrtP == sqrtQ); + + uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION; + uint256 numerator2 = sqrtP - sqrtQ; + uint256 denominator = uint256(sqrtP) * sqrtQ; + + uint256 safeResult = + roundUp + ? FullMath.mulDivRoundingUp(numerator1, numerator2, denominator) + : FullMath.mulDiv(numerator1, numerator2, denominator); + uint256 fullResult = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity, roundUp); + + assert(safeResult == fullResult); + } + + function getAmount1DeltaInvariants( + uint160 sqrtP, + uint160 sqrtQ, + uint128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + uint256 amount1Down = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, false); + assert(amount1Down == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, false)); + + uint256 amount1Up = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity, true); + assert(amount1Up == SqrtPriceMath.getAmount1Delta(sqrtQ, sqrtP, liquidity, true)); + + assert(amount1Down <= amount1Up); + // diff is 0 or 1 + assert(amount1Up - amount1Down < 2); + } + + function getAmount0DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtQ, sqrtP, liquidity); + if (liquidity < 0) assert(amount0 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount0 == 0); + else assert(amount0 > 0); + } + if (liquidity == 0) assert(amount0 == 0); + } + + function getAmount1DeltaSignedInvariants( + uint160 sqrtP, + uint160 sqrtQ, + int128 liquidity + ) external pure { + require(sqrtP > 0 && sqrtQ > 0); + + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtP, sqrtQ, liquidity); + if (liquidity < 0) assert(amount1 <= 0); + if (liquidity > 0) { + if (sqrtP == sqrtQ) assert(amount1 == 0); + else assert(amount1 > 0); + } + if (liquidity == 0) assert(amount1 == 0); + } + + function getOutOfRangeMintInvariants( + uint160 sqrtA, + uint160 sqrtB, + int128 liquidity + ) external pure { + require(sqrtA > 0 && sqrtB > 0); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtA, sqrtB, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtA, sqrtB, liquidity); + + if (sqrtA == sqrtB) { + assert(amount0 == 0); + assert(amount1 == 0); + } else { + assert(amount0 > 0); + assert(amount1 > 0); + } + } + + function getInRangeMintInvariants( + uint160 sqrtLower, + uint160 sqrtCurrent, + uint160 sqrtUpper, + int128 liquidity + ) external pure { + require(sqrtLower > 0); + require(sqrtLower < sqrtUpper); + require(sqrtLower <= sqrtCurrent && sqrtCurrent <= sqrtUpper); + require(liquidity > 0); + + int256 amount0 = SqrtPriceMath.getAmount0Delta(sqrtCurrent, sqrtUpper, liquidity); + int256 amount1 = SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtCurrent, liquidity); + + assert(amount0 > 0 || amount1 > 0); + } +} + + +// File contracts/test/SqrtPriceMathTest.sol + + +pragma solidity >=0.0; + +contract SqrtPriceMathTest { + function getNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromInput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountIn, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromInput(sqrtP, liquidity, amountIn, zeroForOne); + return gasBefore - gasleft(); + } + + function getNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external pure returns (uint160 sqrtQ) { + return SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + } + + function getGasCostOfGetNextSqrtPriceFromOutput( + uint160 sqrtP, + uint128 liquidity, + uint256 amountOut, + bool zeroForOne + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtP, liquidity, amountOut, zeroForOne); + return gasBefore - gasleft(); + } + + function getAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount0) { + return SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external pure returns (uint256 amount1) { + return SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + } + + function getGasCostOfGetAmount0Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount0Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } + + function getGasCostOfGetAmount1Delta( + uint160 sqrtLower, + uint160 sqrtUpper, + uint128 liquidity, + bool roundUp + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SqrtPriceMath.getAmount1Delta(sqrtLower, sqrtUpper, liquidity, roundUp); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/SwapMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract SwapMathEchidnaTest { + function checkComputeSwapStepInvariants( + uint160 sqrtPriceRaw, + uint160 sqrtPriceTargetRaw, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external pure { + require(sqrtPriceRaw > 0); + require(sqrtPriceTargetRaw > 0); + require(feePips > 0); + require(feePips < 1e6); + + (uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = + SwapMath.computeSwapStep(sqrtPriceRaw, sqrtPriceTargetRaw, liquidity, amountRemaining, feePips); + + assert(amountIn <= type(uint256).max - feeAmount); + + if (amountRemaining < 0) { + assert(amountOut <= uint256(-amountRemaining)); + } else { + assert(amountIn + feeAmount <= uint256(amountRemaining)); + } + + if (sqrtPriceRaw == sqrtPriceTargetRaw) { + assert(amountIn == 0); + assert(amountOut == 0); + assert(feeAmount == 0); + assert(sqrtQ == sqrtPriceTargetRaw); + } + + // didn't reach price target, entire amount must be consumed + if (sqrtQ != sqrtPriceTargetRaw) { + if (amountRemaining < 0) assert(amountOut == uint256(-amountRemaining)); + else assert(amountIn + feeAmount == uint256(amountRemaining)); + } + + // next price is between price and price target + if (sqrtPriceTargetRaw <= sqrtPriceRaw) { + assert(sqrtQ <= sqrtPriceRaw); + assert(sqrtQ >= sqrtPriceTargetRaw); + } else { + assert(sqrtQ >= sqrtPriceRaw); + assert(sqrtQ <= sqrtPriceTargetRaw); + } + } +} + + +// File contracts/test/SwapMathTest.sol + + +pragma solidity >=0.0; + +contract SwapMathTest { + function computeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) + external + pure + returns ( + uint160 sqrtQ, + uint256 amountIn, + uint256 amountOut, + uint256 feeAmount + ) + { + return SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + } + + function getGasCostOfComputeSwapStep( + uint160 sqrtP, + uint160 sqrtPTarget, + uint128 liquidity, + int256 amountRemaining, + uint24 feePips + ) external view returns (uint256) { + uint256 gasBefore = gasleft(); + SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips); + return gasBefore - gasleft(); + } +} + + +// File contracts/test/TestERC20.sol + + +pragma solidity >=0.0; + +contract TestERC20 is IERC20Minimal { + mapping(address => uint256) public override balanceOf; + mapping(address => mapping(address => uint256)) public override allowance; + + constructor(uint256 amountToMint) { + mint(msg.sender, amountToMint); + } + + function mint(address to, uint256 amount) public { + uint256 balanceNext = balanceOf[to] + amount; + require(balanceNext >= amount, 'overflow balance'); + balanceOf[to] = balanceNext; + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + uint256 balanceBefore = balanceOf[msg.sender]; + require(balanceBefore >= amount, 'insufficient balance'); + balanceOf[msg.sender] = balanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'recipient balance overflow'); + balanceOf[recipient] = balanceRecipient + amount; + + emit Transfer(msg.sender, recipient, amount); + return true; + } + + function approve(address spender, uint256 amount) external override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external override returns (bool) { + uint256 allowanceBefore = allowance[sender][msg.sender]; + require(allowanceBefore >= amount, 'allowance insufficient'); + + allowance[sender][msg.sender] = allowanceBefore - amount; + + uint256 balanceRecipient = balanceOf[recipient]; + require(balanceRecipient + amount >= balanceRecipient, 'overflow balance recipient'); + balanceOf[recipient] = balanceRecipient + amount; + uint256 balanceSender = balanceOf[sender]; + require(balanceSender >= amount, 'underflow balance sender'); + balanceOf[sender] = balanceSender - amount; + + emit Transfer(sender, recipient, amount); + return true; + } +} + + +// File contracts/test/TestUniswapV3Callee.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Callee is IUniswapV3MintCallback, IUniswapV3SwapCallback, IUniswapV3FlashCallback { + using SafeCast for uint256; + + function swapExact0For1( + address pool, + uint256 amount0In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, amount0In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap0ForExact1( + address pool, + uint256 amount1Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, true, -amount1Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapExact1For0( + address pool, + uint256 amount1In, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, amount1In.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swap1ForExact0( + address pool, + uint256 amount0Out, + address recipient, + uint160 sqrtPriceLimitX96 + ) external { + IUniswapV3Pool(pool).swap(recipient, false, -amount0Out.toInt256(), sqrtPriceLimitX96, abi.encode(msg.sender)); + } + + function swapToLowerSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, true, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + function swapToHigherSqrtPrice( + address pool, + uint160 sqrtPriceX96, + address recipient + ) external { + IUniswapV3Pool(pool).swap(recipient, false, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit SwapCallback(amount0Delta, amount1Delta); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } else { + // if both are not gt 0, both must be 0. + assert(amount0Delta == 0 && amount1Delta == 0); + } + } + + function mint( + address pool, + address recipient, + int24 tickLower, + int24 tickUpper, + uint128 amount + ) external { + IUniswapV3Pool(pool).mint(recipient, tickLower, tickUpper, amount, abi.encode(msg.sender)); + } + + event MintCallback(uint256 amount0Owed, uint256 amount1Owed); + + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + emit MintCallback(amount0Owed, amount1Owed); + if (amount0Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, amount0Owed); + if (amount1Owed > 0) + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, amount1Owed); + } + + event FlashCallback(uint256 fee0, uint256 fee1); + + function flash( + address pool, + address recipient, + uint256 amount0, + uint256 amount1, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).flash(recipient, amount0, amount1, abi.encode(msg.sender, pay0, pay1)); + } + + function uniswapV3FlashCallback( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + emit FlashCallback(fee0, fee1); + + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, pay0); + if (pay1 > 0) IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, pay1); + } +} + + +// File contracts/test/TestUniswapV3ReentrantCallee.sol + + +pragma solidity >=0.0; + +contract TestUniswapV3ReentrantCallee is IUniswapV3SwapCallback { + string private constant expectedReason = 'LOK'; + + function swapToReenter(address pool) external { + IUniswapV3Pool(pool).swap(address(0), false, 1, TickMath.MAX_SQRT_RATIO - 1, new bytes(0)); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata + ) external override { + // try to reenter swap + try IUniswapV3Pool(msg.sender).swap(address(0), false, 1, 0, new bytes(0)) {} catch Error( + string memory reason + ) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter mint + try IUniswapV3Pool(msg.sender).mint(address(0), 0, 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collect + try IUniswapV3Pool(msg.sender).collect(address(0), 0, 0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter burn + try IUniswapV3Pool(msg.sender).burn(0, 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter flash + try IUniswapV3Pool(msg.sender).flash(address(0), 0, 0, new bytes(0)) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + // try to reenter collectProtocol + try IUniswapV3Pool(msg.sender).collectProtocol(address(0), 0, 0) {} catch Error(string memory reason) { + require(keccak256(abi.encode(reason)) == keccak256(abi.encode(expectedReason))); + } + + require(false, 'Unable to reenter'); + } +} + + +// File contracts/test/TestUniswapV3Router.sol + + +pragma solidity >=0.0; + + + + +contract TestUniswapV3Router is IUniswapV3SwapCallback { + using SafeCast for uint256; + + // flash swaps for an exact amount of token0 in the output pool + function swapForExact0Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount0Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + false, + -amount0Out.toInt256(), + TickMath.MAX_SQRT_RATIO - 1, + abi.encode(pools, msg.sender) + ); + } + + // flash swaps for an exact amount of token1 in the output pool + function swapForExact1Multi( + address recipient, + address poolInput, + address poolOutput, + uint256 amount1Out + ) external { + address[] memory pools = new address[](1); + pools[0] = poolInput; + IUniswapV3Pool(poolOutput).swap( + recipient, + true, + -amount1Out.toInt256(), + TickMath.MIN_SQRT_RATIO + 1, + abi.encode(pools, msg.sender) + ); + } + + event SwapCallback(int256 amount0Delta, int256 amount1Delta); + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) public override { + emit SwapCallback(amount0Delta, amount1Delta); + + (address[] memory pools, address payer) = abi.decode(data, (address[], address)); + + if (pools.length == 1) { + // get the address and amount of the token that we need to pay + address tokenToBePaid = + amount0Delta > 0 ? IUniswapV3Pool(msg.sender).token0() : IUniswapV3Pool(msg.sender).token1(); + int256 amountToBePaid = amount0Delta > 0 ? amount0Delta : amount1Delta; + + bool zeroForOne = tokenToBePaid == IUniswapV3Pool(pools[0]).token1(); + IUniswapV3Pool(pools[0]).swap( + msg.sender, + zeroForOne, + -amountToBePaid, + zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1, + abi.encode(new address[](0), payer) + ); + } else { + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom( + payer, + msg.sender, + uint256(amount0Delta) + ); + } else { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom( + payer, + msg.sender, + uint256(amount1Delta) + ); + } + } + } +} + + +// File contracts/test/TestUniswapV3SwapPay.sol + + +pragma solidity >=0.0; + + +contract TestUniswapV3SwapPay is IUniswapV3SwapCallback { + function swap( + address pool, + address recipient, + bool zeroForOne, + uint160 sqrtPriceX96, + int256 amountSpecified, + uint256 pay0, + uint256 pay1 + ) external { + IUniswapV3Pool(pool).swap( + recipient, + zeroForOne, + amountSpecified, + sqrtPriceX96, + abi.encode(msg.sender, pay0, pay1) + ); + } + + function uniswapV3SwapCallback( + int256, + int256, + bytes calldata data + ) external override { + (address sender, uint256 pay0, uint256 pay1) = abi.decode(data, (address, uint256, uint256)); + + if (pay0 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(pay0)); + } else if (pay1 > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(pay1)); + } + } +} + + +// File contracts/test/TickBitmapEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapEchidnaTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) private bitmap; + + // returns whether the given tick is initialized + function isInitialized(int24 tick) private view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } + + function flipTick(int24 tick) external { + bool before = isInitialized(tick); + bitmap.flipTick(tick, 1); + assert(isInitialized(tick) == !before); + } + + function checkNextInitializedTickWithinOneWordInvariants(int24 tick, bool lte) external view { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + if (lte) { + // type(int24).min + 256 + require(tick >= -8388352); + assert(next <= tick); + assert(tick - next < 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick; i > next; i--) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } else { + // type(int24).max - 256 + require(tick < 8388351); + assert(next > tick); + assert(next - tick <= 256); + // all the ticks between the input tick and the next tick should be uninitialized + for (int24 i = tick + 1; i < next; i++) { + assert(!isInitialized(i)); + } + assert(isInitialized(next) == initialized); + } + } +} + + +// File contracts/test/TickBitmapTest.sol + + +pragma solidity >=0.0; + +contract TickBitmapTest { + using TickBitmap for mapping(int16 => uint256); + + mapping(int16 => uint256) public bitmap; + + function flipTick(int24 tick) external { + bitmap.flipTick(tick, 1); + } + + function getGasCostOfFlipTick(int24 tick) external returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.flipTick(tick, 1); + return gasBefore - gasleft(); + } + + function nextInitializedTickWithinOneWord(int24 tick, bool lte) + external + view + returns (int24 next, bool initialized) + { + return bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + } + + function getGasCostOfNextInitializedTickWithinOneWord(int24 tick, bool lte) external view returns (uint256) { + uint256 gasBefore = gasleft(); + bitmap.nextInitializedTickWithinOneWord(tick, 1, lte); + return gasBefore - gasleft(); + } + + // returns whether the given tick is initialized + function isInitialized(int24 tick) external view returns (bool) { + (int24 next, bool initialized) = bitmap.nextInitializedTickWithinOneWord(tick, 1, true); + return next == tick ? initialized : false; + } +} + + +// File contracts/test/TickEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickEchidnaTest { + function checkTickSpacingToParametersInvariants(int24 tickSpacing) external pure { + require(tickSpacing <= TickMath.MAX_TICK); + require(tickSpacing > 0); + + int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing; + int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing; + + uint128 maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + + // symmetry around 0 tick + assert(maxTick == -minTick); + // positive max tick + assert(maxTick > 0); + // divisibility + assert((maxTick - minTick) % tickSpacing == 0); + + uint256 numTicks = uint256(int256((maxTick - minTick) / tickSpacing)) + 1; + // max liquidity at every tick is less than the cap + assert(uint256(maxLiquidityPerTick) * numTicks <= type(uint128).max); + } +} + + +// File contracts/test/TickMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickMathEchidnaTest { + // uniqueness and increasing order + function checkGetSqrtRatioAtTickInvariants(int24 tick) external pure { + uint160 ratio = TickMath.getSqrtRatioAtTick(tick); + assert(TickMath.getSqrtRatioAtTick(tick - 1) < ratio && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(ratio >= TickMath.MIN_SQRT_RATIO); + assert(ratio <= TickMath.MAX_SQRT_RATIO); + } + + // the ratio is always between the returned tick and the returned tick+1 + function checkGetTickAtSqrtRatioInvariants(uint160 ratio) external pure { + int24 tick = TickMath.getTickAtSqrtRatio(ratio); + assert(ratio >= TickMath.getSqrtRatioAtTick(tick) && ratio < TickMath.getSqrtRatioAtTick(tick + 1)); + assert(tick >= TickMath.MIN_TICK); + assert(tick < TickMath.MAX_TICK); + } +} + + +// File contracts/test/TickMathTest.sol + + +pragma solidity >=0.0; + +contract TickMathTest { + function getSqrtRatioAtTick(int24 tick) external pure returns (uint160) { + return TickMath.getSqrtRatioAtTick(tick); + } + + function getGasCostOfGetSqrtRatioAtTick(int24 tick) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getSqrtRatioAtTick(tick); + return gasBefore - gasleft(); + } + + function getTickAtSqrtRatio(uint160 sqrtPriceX96) external pure returns (int24) { + return TickMath.getTickAtSqrtRatio(sqrtPriceX96); + } + + function getGasCostOfGetTickAtSqrtRatio(uint160 sqrtPriceX96) external view returns (uint256) { + uint256 gasBefore = gasleft(); + TickMath.getTickAtSqrtRatio(sqrtPriceX96); + return gasBefore - gasleft(); + } + + function MIN_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MIN_SQRT_RATIO; + } + + function MAX_SQRT_RATIO() external pure returns (uint160) { + return TickMath.MAX_SQRT_RATIO; + } +} + + +// File contracts/test/TickOverflowSafetyEchidnaTest.sol + + +pragma solidity >=0.0; + +contract TickOverflowSafetyEchidnaTest { + using Tick for mapping(int24 => Tick.Info); + + int24 private constant MIN_TICK = -16; + int24 private constant MAX_TICK = 16; + uint128 private constant MAX_LIQUIDITY = type(uint128).max / 32; + + mapping(int24 => Tick.Info) private ticks; + int24 private tick = 0; + + // used to track how much total liquidity has been added. should never be negative + int256 totalLiquidity = 0; + // half the cap of fee growth has happened, this can overflow + uint256 private feeGrowthGlobal0X128 = type(uint256).max / 2; + uint256 private feeGrowthGlobal1X128 = type(uint256).max / 2; + // how much total growth has happened, this cannot overflow + uint256 private totalGrowth0 = 0; + uint256 private totalGrowth1 = 0; + + function increaseFeeGrowthGlobal0X128(uint256 amount) external { + require(totalGrowth0 + amount > totalGrowth0); // overflow check + feeGrowthGlobal0X128 += amount; // overflow desired + totalGrowth0 += amount; + } + + function increaseFeeGrowthGlobal1X128(uint256 amount) external { + require(totalGrowth1 + amount > totalGrowth1); // overflow check + feeGrowthGlobal1X128 += amount; // overflow desired + totalGrowth1 += amount; + } + + function setPosition( + int24 tickLower, + int24 tickUpper, + int128 liquidityDelta + ) external { + require(tickLower > MIN_TICK); + require(tickUpper < MAX_TICK); + require(tickLower < tickUpper); + bool flippedLower = + ticks.update( + tickLower, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + false, + MAX_LIQUIDITY + ); + bool flippedUpper = + ticks.update( + tickUpper, + tick, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + 0, + 0, + uint32(block.timestamp), + true, + MAX_LIQUIDITY + ); + + if (flippedLower) { + if (liquidityDelta < 0) { + assert(ticks[tickLower].liquidityGross == 0); + ticks.clear(tickLower); + } else assert(ticks[tickLower].liquidityGross > 0); + } + + if (flippedUpper) { + if (liquidityDelta < 0) { + assert(ticks[tickUpper].liquidityGross == 0); + ticks.clear(tickUpper); + } else assert(ticks[tickUpper].liquidityGross > 0); + } + + totalLiquidity += liquidityDelta; + // requires should have prevented this + assert(totalLiquidity >= 0); + + if (totalLiquidity == 0) { + totalGrowth0 = 0; + totalGrowth1 = 0; + } + } + + function moveToTick(int24 target) external { + require(target > MIN_TICK); + require(target < MAX_TICK); + while (tick != target) { + if (tick < target) { + if (ticks[tick + 1].liquidityGross > 0) + ticks.cross(tick + 1, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick++; + } else { + if (ticks[tick].liquidityGross > 0) + ticks.cross(tick, feeGrowthGlobal0X128, feeGrowthGlobal1X128, 0, 0, uint32(block.timestamp)); + tick--; + } + } + } +} + + +// File contracts/test/TickTest.sol + + +pragma solidity >=0.0; + +contract TickTest { + using Tick for mapping(int24 => Tick.Info); + + mapping(int24 => Tick.Info) public ticks; + + function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) external pure returns (uint128) { + return Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing); + } + + function setTick(int24 tick, Tick.Info memory info) external { + ticks[tick] = info; + } + + function getFeeGrowthInside( + int24 tickLower, + int24 tickUpper, + int24 tickCurrent, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128 + ) external view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { + return ticks.getFeeGrowthInside(tickLower, tickUpper, tickCurrent, feeGrowthGlobal0X128, feeGrowthGlobal1X128); + } + + function update( + int24 tick, + int24 tickCurrent, + int128 liquidityDelta, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time, + bool upper, + uint128 maxLiquidity + ) external returns (bool flipped) { + return + ticks.update( + tick, + tickCurrent, + liquidityDelta, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time, + upper, + maxLiquidity + ); + } + + function clear(int24 tick) external { + ticks.clear(tick); + } + + function cross( + int24 tick, + uint256 feeGrowthGlobal0X128, + uint256 feeGrowthGlobal1X128, + uint160 secondsPerLiquidityCumulativeX128, + int56 tickCumulative, + uint32 time + ) external returns (int128 liquidityNet) { + return + ticks.cross( + tick, + feeGrowthGlobal0X128, + feeGrowthGlobal1X128, + secondsPerLiquidityCumulativeX128, + tickCumulative, + time + ); + } +} + + +// File contracts/test/UniswapV3PoolSwapTest.sol + + +pragma solidity >=0.0; + + +contract UniswapV3PoolSwapTest is IUniswapV3SwapCallback { + int256 private _amount0Delta; + int256 private _amount1Delta; + + function getSwapResult( + address pool, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96 + ) + external + returns ( + int256 amount0Delta, + int256 amount1Delta, + uint160 nextSqrtRatio + ) + { + (amount0Delta, amount1Delta) = IUniswapV3Pool(pool).swap( + address(0), + zeroForOne, + amountSpecified, + sqrtPriceLimitX96, + abi.encode(msg.sender) + ); + + (nextSqrtRatio, , , , , , ) = IUniswapV3Pool(pool).slot0(); + } + + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override { + address sender = abi.decode(data, (address)); + + if (amount0Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); + } else if (amount1Delta > 0) { + IERC20Minimal(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); + } + } +} + + +// File contracts/test/UnsafeMathEchidnaTest.sol + + +pragma solidity >=0.0; + +contract UnsafeMathEchidnaTest { + function checkDivRoundingUp(uint256 x, uint256 d) external pure { + require(d > 0); + uint256 z = UnsafeMath.divRoundingUp(x, d); + uint256 diff = z - (x / d); + if (x % d == 0) { + assert(diff == 0); + } else { + assert(diff == 1); + } + } +} + + +// File contracts/UniswapV3PoolDeployer.sol + + +pragma solidity >=0.0; + +contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer { + struct Parameters { + address factory; + address token0; + address token1; + uint24 fee; + int24 tickSpacing; + } + + /// @inheritdoc IUniswapV3PoolDeployer + Parameters public override parameters; + + /// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then + /// clearing it after deploying the pool. + /// @param factory The contract address of the Uniswap V3 factory + /// @param token0 The first token of the pool by address sort order + /// @param token1 The second token of the pool by address sort order + /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param tickSpacing The spacing between usable ticks + function deploy( + address factory, + address token0, + address token1, + uint24 fee, + int24 tickSpacing + ) internal returns (address pool) { + parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); + pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); + delete parameters; + } +} + + +// File contracts/UniswapV3Factory.sol + + +pragma solidity >=0.0; + + +/// @title Canonical Uniswap V3 factory +/// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees +contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall { + /// @inheritdoc IUniswapV3Factory + address public override owner; + + /// @inheritdoc IUniswapV3Factory + mapping(uint24 => int24) public override feeAmountTickSpacing; + /// @inheritdoc IUniswapV3Factory + mapping(address => mapping(address => mapping(uint24 => address))) public override getPool; + + constructor() { + owner = msg.sender; + emit OwnerChanged(address(0), msg.sender); + + feeAmountTickSpacing[500] = 10; + emit FeeAmountEnabled(500, 10); + feeAmountTickSpacing[3000] = 60; + emit FeeAmountEnabled(3000, 60); + feeAmountTickSpacing[10000] = 200; + emit FeeAmountEnabled(10000, 200); + } + + /// @inheritdoc IUniswapV3Factory + function createPool( + address tokenA, + address tokenB, + uint24 fee + ) public override noDelegateCall returns (address pool) { + require(tokenA != tokenB); + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0)); + int24 tickSpacing = feeAmountTickSpacing[fee]; + require(tickSpacing != 0); + require(getPool[token0][token1][fee] == address(0)); + pool = deploy(address(this), token0, token1, fee, tickSpacing); + getPool[token0][token1][fee] = pool; + // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses + getPool[token1][token0][fee] = pool; + emit PoolCreated(token0, token1, fee, tickSpacing, pool); + } + + /// @inheritdoc IUniswapV3Factory + function setOwner(address _owner) external override { + require(msg.sender == owner); + emit OwnerChanged(owner, _owner); + owner = _owner; + } + + /// @inheritdoc IUniswapV3Factory + function enableFeeAmount(uint24 fee, int24 tickSpacing) public override { + require(msg.sender == owner); + require(fee < 1000000); + // tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that + // TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick + // 16384 ticks represents a >5x price change with ticks of 1 bips + require(tickSpacing > 0 && tickSpacing < 16384); + require(feeAmountTickSpacing[fee] == 0); + + feeAmountTickSpacing[fee] = tickSpacing; + emit FeeAmountEnabled(fee, tickSpacing); + } + + function runTest() public { + TestERC20 t1 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000001}(1000000000); + TestERC20 t2 = new TestERC20{salt: 0x0000000000000000000000000000000000000000000000000000000000000002}(1000000000); + IUniswapV3Pool pool = IUniswapV3Pool(createPool(address(t1), address(t2), 500)); + + TestUniswapV3Callee test = new TestUniswapV3Callee(); + t1.approve(address(test), 1000000000); + t2.approve(address(test), 1000000000); + pool.initialize(761446703485210103287273052203988822378723970342); + + test.mint(address(pool), address(this), int24(-1000), int24(1000), uint128(1000)); + + uint256 beforeSwap = t1.balanceOf(address(this)); + test.swapExact0For1(address(pool), 10, address(this), 4295128740); + uint256 afterSwap = t1.balanceOf(address(this)); + + require(beforeSwap != afterSwap, 'WRO'); + } +} + +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// runTest() -> +// ~ emit PoolCreated(address,address,uint24,int24,address): #0xe25a2f4ab57397bff1e25190d8a25c7fdbf97872, #0xe815b5c5c31d4b73175f828b1d19460b97c65b26, #0x01f4, 0x0a, 0x8b800520e09d6d4598288ad0dfd35b6237cdd278 +// ~ emit Approval(address,address,uint256) from 0xe25a2f4ab57397bff1e25190d8a25c7fdbf97872: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, 0x3b9aca00 +// ~ emit Approval(address,address,uint256) from 0xe815b5c5c31d4b73175f828b1d19460b97c65b26: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, 0x3b9aca00 +// ~ emit Initialize(uint160,int24) from 0x8b800520e09d6d4598288ad0dfd35b6237cdd278: 0x85607379ff6f79edb3e272aaeae79d5263988d26, 0x0d56f8 +// ~ emit MintCallback(uint256,uint256) from 0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57: 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe815b5c5c31d4b73175f828b1d19460b97c65b26: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0x8b800520e09d6d4598288ad0dfd35b6237cdd278, 0x65 +// ~ emit Mint(address,address,int24,int24,uint128,uint256,uint256) from 0x8b800520e09d6d4598288ad0dfd35b6237cdd278: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18, #0x03e8, 0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, 0x03e8, 0x00, 0x65 +// ~ emit Transfer(address,address,uint256) from 0xe815b5c5c31d4b73175f828b1d19460b97c65b26: #0x8b800520e09d6d4598288ad0dfd35b6237cdd278, #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, 0x09 +// ~ emit SwapCallback(int256,int256) from 0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57: 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7 +// ~ emit Transfer(address,address,uint256) from 0xe25a2f4ab57397bff1e25190d8a25c7fdbf97872: #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, #0x8b800520e09d6d4598288ad0dfd35b6237cdd278, 0x0a +// ~ emit Swap(address,address,int256,int256,uint160,uint128,int24) from 0x8b800520e09d6d4598288ad0dfd35b6237cdd278: #0xb5db0c938f8795a32bf063b59dfe98abc9b0bc57, #0x28ce22d54b78ce62f8ecdf7723a7e567c983ee61, 0x0a, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7, 0x010a9a2fd9e126942e9c978d07, 0x03e8, 0x032b diff --git a/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol index ea6d781898f0..5b7c0ba97474 100644 --- a/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol +++ b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types.sol @@ -10,5 +10,7 @@ contract C { hash3 = keccak256(abi.encodePacked(this.f)); } } +// ==== +// compileToEOF: false // ---- // f() -> 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0x0e9229fb1d2cd02cee4b6c9f25497777014a8766e3479666d1c619066d2887ec diff --git a/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol new file mode 100644 index 000000000000..e87a886582f2 --- /dev/null +++ b/test/libsolidity/semanticTests/builtinFunctions/keccak256_packed_complex_types_eof.sol @@ -0,0 +1,17 @@ +contract C { + uint120[3] x; + function f() public returns (bytes32 hash1, bytes32 hash2, bytes32 hash3) { + uint120[] memory y = new uint120[](3); + x[0] = y[0] = uint120(type(uint).max - 1); + x[1] = y[1] = uint120(type(uint).max - 2); + x[2] = y[2] = uint120(type(uint).max - 3); + hash1 = keccak256(abi.encodePacked(x)); + hash2 = keccak256(abi.encodePacked(y)); + hash3 = keccak256(abi.encodePacked(this.f)); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0xba4f20407251e4607cd66b90bfea19ec6971699c03e4a4f3ea737d5818ac27ae, 0xb8fcf925e706038045e8e57a2620706dfa046b8977add2f3c9b2641f2ea6c8b7 diff --git a/test/libsolidity/semanticTests/constructor/callvalue_check.sol b/test/libsolidity/semanticTests/constructor/callvalue_check.sol index f45fbed925ad..faef55828254 100644 --- a/test/libsolidity/semanticTests/constructor/callvalue_check.sol +++ b/test/libsolidity/semanticTests/constructor/callvalue_check.sol @@ -9,26 +9,27 @@ contract B3 {} contract B4 { constructor() {} } contract C { - function createWithValue(bytes memory c, uint256 value) public payable returns (bool) { - uint256 y = 0; - assembly { y := create(value, add(c, 0x20), mload(c)) } - return y != 0; - } - function f(uint256 value) public payable returns (bool) { - return createWithValue(type(B1).creationCode, value); - } - function g(uint256 value) public payable returns (bool) { - return createWithValue(type(B2).creationCode, value); - } - function h(uint256 value) public payable returns (bool) { - return createWithValue(type(B3).creationCode, value); - } - function i(uint256 value) public payable returns (bool) { - return createWithValue(type(B4).creationCode, value); - } + function createWithValue(bytes memory c, uint256 value) public payable returns (bool) { + uint256 y = 0; + assembly { y := create(value, add(c, 0x20), mload(c)) } + return y != 0; + } + function f(uint256 value) public payable returns (bool) { + return createWithValue(type(B1).creationCode, value); + } + function g(uint256 value) public payable returns (bool) { + return createWithValue(type(B2).creationCode, value); + } + function h(uint256 value) public payable returns (bool) { + return createWithValue(type(B3).creationCode, value); + } + function i(uint256 value) public payable returns (bool) { + return createWithValue(type(B4).creationCode, value); + } } // ==== // EVMVersion: >homestead +// compileToEOF: false // ---- // f(uint256), 2000 ether: 0 -> true // f(uint256), 2000 ether: 100 -> false diff --git a/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol b/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol index 583f1ca37b3f..9f979fb74499 100644 --- a/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol +++ b/test/libsolidity/semanticTests/constructor/no_callvalue_check.sol @@ -15,6 +15,8 @@ contract C { return true; } } +// ==== +// compileToEOF: false // ---- // f(), 2000 ether -> true // gas irOptimized: 117623 diff --git a/test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol b/test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol new file mode 100644 index 000000000000..1442ab030630 --- /dev/null +++ b/test/libsolidity/semanticTests/constructor/no_callvalue_check_eof.sol @@ -0,0 +1,28 @@ +contract A1 {} +contract B1 is A1 { constructor() payable {} } + +contract A2 { constructor() {} } +contract B2 is A2 { constructor() payable {} } + +contract B3 { constructor() payable {} } + +contract C { + function f() public payable returns (bool) { + // Make sure none of these revert. + new B1{value: 10, salt: hex"00"}(); + new B2{value: 10, salt: hex"01"}(); + new B3{value: 10, salt: hex"02"}(); + return true; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f(), 2000 ether -> true +// gas irOptimized: 117623 +// gas irOptimized code: 1800 +// gas legacy: 117821 +// gas legacy code: 4800 +// gas legacyOptimized: 117690 +// gas legacyOptimized code: 4800 diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol index 64b80422c352..d774936444e7 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/bound_function.sol @@ -36,5 +36,7 @@ contract C { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol index 931109199649..a40dcee211ca 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/library_function.sol @@ -30,5 +30,7 @@ contract C { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol index 018d410557b0..fbbdae0cb9f8 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/module_function.sol @@ -32,5 +32,7 @@ contract C { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol index 0f9b023b08d9..735b5b68fff2 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/static_base_function.sol @@ -31,5 +31,7 @@ contract C is S { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol index b6ae85b838d8..3451598ad74a 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/subassembly_deduplication.sol @@ -37,5 +37,7 @@ contract C { x < 2 * type(A).creationCode.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol index 9accc54a2b05..dda110805196 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/super_function.sol @@ -31,5 +31,7 @@ contract C is S { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol b/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol index 866fe9dfc452..9e0388a7f800 100644 --- a/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol +++ b/test/libsolidity/semanticTests/deployedCodeExclusion/virtual_function.sol @@ -35,5 +35,7 @@ contract C is X { return x < data.length; } } +// ==== +// compileToEOF: false // ---- // test() -> true diff --git a/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol b/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol index bc70b5bef757..9c22d9507fb8 100644 --- a/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol +++ b/test/libsolidity/semanticTests/errors/errors_by_parameter_type.sol @@ -36,6 +36,7 @@ contract C { // ==== // compileViaYul: true +// compileToEOF: false // ---- // a() -> FAILURE, hex"92bbf6e8" // b() -> FAILURE, hex"47e26897", hex"0000000000000000000000000000000000000000000000000000000000000001" diff --git a/test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol b/test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol new file mode 100644 index 000000000000..2fd4126a0705 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/errors_by_parameter_type_eof.sol @@ -0,0 +1,46 @@ +pragma abicoder v2; + +struct S { + uint256 a; + bool b; + string s; +} + +error E(); +error E1(uint256); +error E2(string); +error E3(S); +error E4(address); +error E5(function() external pure); + +contract C { + function a() external pure { + require(false, E()); + } + function b() external pure { + require(false, E1(1)); + } + function c() external pure { + require(false, E2("string literal")); + } + function d() external pure { + require(false, E3(S(1, true, "string literal"))); + } + function e() external view { + require(false, E4(address(this))); + } + function f() external view { + require(false, E5(this.a)); + } +} + +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// a() -> FAILURE, hex"92bbf6e8" +// b() -> FAILURE, hex"47e26897", hex"0000000000000000000000000000000000000000000000000000000000000001" +// c() -> FAILURE, hex"8f372c34", hex"0000000000000000000000000000000000000000000000000000000000000020", hex"000000000000000000000000000000000000000000000000000000000000000e", hex"737472696e67206c69746572616c000000000000000000000000000000000000" +// d() -> FAILURE, hex"5717173e", hex"0000000000000000000000000000000000000000000000000000000000000020", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000060", hex"000000000000000000000000000000000000000000000000000000000000000e", hex"737472696e67206c69746572616c000000000000000000000000000000000000" +// e() -> FAILURE, hex"7efef9ea", hex"0000000000000000000000008e3f661b8facaa0fa7aa0113847501029db6517e" +// f() -> FAILURE, hex"0c3f12eb", hex"8e3f661b8facaa0fa7aa0113847501029db6517e0dbe671f0000000000000000" diff --git a/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol index 3af19ca64fbf..59a39d9152f7 100644 --- a/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol +++ b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter.sol @@ -14,5 +14,7 @@ contract C } } +// ==== +// compileToEOF: false // ---- // f() -> FAILURE, hex"271b1dfa", hex"c06afe3a8444fc0004668591e8306bfb9968e79ef37cdc8e0000000000000000" diff --git a/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol new file mode 100644 index 000000000000..0857fabd4a13 --- /dev/null +++ b/test/libsolidity/semanticTests/errors/require_error_function_pointer_parameter_eof.sol @@ -0,0 +1,21 @@ +error CustomError(function(uint256) external pure returns (uint256)); + +contract C +{ + function e(uint256 x) external pure returns (uint256) + { + return x; + } + + function f() external view + { + // more than one stack slot + require(false, CustomError(this.e)); + } +} + +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> FAILURE, hex"271b1dfa", hex"dfc163ea0fefc2097b7425134f69fcafa3742b0af37cdc8e0000000000000000" diff --git a/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol b/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol index 61e1bbdd6c0e..0add96c4421c 100644 --- a/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol +++ b/test/libsolidity/semanticTests/events/event_emit_from_other_contract.sol @@ -13,6 +13,8 @@ contract C { d.deposit(_id); } } +// ==== +// compileToEOF: false // ---- // constructor() -> // gas irOptimized: 113970 diff --git a/test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol b/test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol new file mode 100644 index 000000000000..81d52a075171 --- /dev/null +++ b/test/libsolidity/semanticTests/events/event_emit_from_other_contract_eof.sol @@ -0,0 +1,28 @@ +contract D { + event Deposit(address indexed _from, bytes32 indexed _id, uint _value); + function deposit(bytes32 _id) public payable { + emit Deposit(msg.sender, _id, msg.value); + } +} +contract C { + D d; + constructor() { + d = new D(); + } + function deposit(bytes32 _id) public payable { + d.deposit(_id); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor() -> +// gas irOptimized: 113970 +// gas irOptimized code: 51400 +// gas legacy: 119776 +// gas legacy code: 125000 +// gas legacyOptimized: 114187 +// gas legacyOptimized code: 57400 +// deposit(bytes32), 18 wei: 0x1234 -> +// ~ emit Deposit(address,bytes32,uint256) from 0x32b73100436177e8f2d2aa1214bb4c1230143ec2: #0x3cb69f8aa1103d7ce41821a1b2e1c85c2b63dfa4, #0x1234, 0x00 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function.sol b/test/libsolidity/semanticTests/events/event_indexed_function.sol index ea7574159500..a4006b196b83 100644 --- a/test/libsolidity/semanticTests/events/event_indexed_function.sol +++ b/test/libsolidity/semanticTests/events/event_indexed_function.sol @@ -4,6 +4,8 @@ contract C { emit Test(this.f); } } +// ==== +// compileToEOF: false // ---- // f() -> // ~ emit Test(function): #0xc06afe3a8444fc0004668591e8306bfb9968e79e26121ff00000000000000000 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function2.sol b/test/libsolidity/semanticTests/events/event_indexed_function2.sol index d4c47e868a1f..b0f2ee7a4d2b 100644 --- a/test/libsolidity/semanticTests/events/event_indexed_function2.sol +++ b/test/libsolidity/semanticTests/events/event_indexed_function2.sol @@ -8,6 +8,8 @@ contract C { emit TestB(this.f2); } } +// ==== +// compileToEOF: false // ---- // f1() -> // ~ emit TestA(function): #0xc06afe3a8444fc0004668591e8306bfb9968e79ec27fc3050000000000000000 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol b/test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol new file mode 100644 index 000000000000..202489dc5519 --- /dev/null +++ b/test/libsolidity/semanticTests/events/event_indexed_function2_eof.sol @@ -0,0 +1,18 @@ +contract C { + event TestA(function() external indexed); + event TestB(function(uint256) external indexed); + function f1() public { + emit TestA(this.f1); + } + function f2(uint256 a) public { + emit TestB(this.f2); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f1() -> +// ~ emit TestA(function): #0xa80a5d214a51e09e24fa3e854004da1ac3f5beffc27fc3050000000000000000 +// f2(uint256): 1 -> +// ~ emit TestB(function): #0xa80a5d214a51e09e24fa3e854004da1ac3f5beffbf3724af0000000000000000 diff --git a/test/libsolidity/semanticTests/events/event_indexed_function_eof.sol b/test/libsolidity/semanticTests/events/event_indexed_function_eof.sol new file mode 100644 index 000000000000..87f960c76910 --- /dev/null +++ b/test/libsolidity/semanticTests/events/event_indexed_function_eof.sol @@ -0,0 +1,12 @@ +contract C { + event Test(function() external indexed); + function f() public { + emit Test(this.f); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> +// ~ emit Test(function): #0x1141c91a4a817b60b5339bc09ac809cedc7649ab26121ff00000000000000000 diff --git a/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol b/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol index 4dff7f9162cc..981efc4c95d1 100644 --- a/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol +++ b/test/libsolidity/semanticTests/externalContracts/_stringutils/stringutils.sol @@ -220,7 +220,7 @@ library strings { // Mask out irrelevant bytes and check again uint256 mask = type(uint256).max; // 0xffff... if(shortest < 32) { - mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); + mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); } uint256 diff; // This depends on potential underflow. diff --git a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol index c53e49f1f032..2e26df4b8fe2 100644 --- a/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol +++ b/test/libsolidity/semanticTests/externalContracts/deposit_contract.sol @@ -174,6 +174,8 @@ contract DepositContract is IDepositContract, ERC165 { ret[7] = bytesValue[0]; } } +// ==== +// compileToEOF: false // ---- // constructor() // gas irOptimized: 809570 diff --git a/test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol b/test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol new file mode 100644 index 000000000000..5546f48603b9 --- /dev/null +++ b/test/libsolidity/semanticTests/externalContracts/deposit_contract_eof.sol @@ -0,0 +1,216 @@ +// ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━ +// ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓ +// ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛ +// ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━ +// ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓ +// ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛ +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +// SPDX-License-Identifier: CC0-1.0 + +// This interface is designed to be compatible with the Vyper version. +/// @notice This is the Ethereum 2.0 deposit contract interface. +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs +interface IDepositContract { + /// @notice A processed deposit event. + event DepositEvent( + bytes pubkey, + bytes withdrawal_credentials, + bytes amount, + bytes signature, + bytes index + ); + + /// @notice Submit a Phase 0 DepositData object. + /// @param pubkey A BLS12-381 public key. + /// @param withdrawal_credentials Commitment to a public key for withdrawals. + /// @param signature A BLS12-381 signature. + /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. + /// Used as a protection against malformed input. + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable; + + /// @notice Query the current deposit root hash. + /// @return The deposit root hash. + function get_deposit_root() external view returns (bytes32); + + /// @notice Query the current deposit count. + /// @return The deposit count encoded as a little endian 64-bit number. + function get_deposit_count() external view returns (bytes memory); +} + +// Based on official specification in https://eips.ethereum.org/EIPS/eip-165 +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceId The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceId` and + /// `interfaceId` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceId) external pure returns (bool); +} + +// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity. +// It tries to stay as close as possible to the original source code. +/// @notice This is the Ethereum 2.0 deposit contract interface. +/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs +contract DepositContract is IDepositContract, ERC165 { + uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; + // NOTE: this also ensures `deposit_count` will fit into 64-bits + uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; + uint256 deposit_count; + + bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; + + constructor() public { + // Compute hashes in empty sparse Merkle tree + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++) + zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height])); + } + + function get_deposit_root() override external view returns (bytes32) { + bytes32 node; + uint size = deposit_count; + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { + if ((size & 1) == 1) + node = sha256(abi.encodePacked(branch[height], node)); + else + node = sha256(abi.encodePacked(node, zero_hashes[height])); + size /= 2; + } + return sha256(abi.encodePacked( + node, + to_little_endian_64(uint64(deposit_count)), + bytes24(0) + )); + } + + function get_deposit_count() override external view returns (bytes memory) { + return to_little_endian_64(uint64(deposit_count)); + } + + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) override external payable { + // Extended ABI length checks since dynamic types are used. + require(pubkey.length == 48, "DepositContract: invalid pubkey length"); + require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); + require(signature.length == 96, "DepositContract: invalid signature length"); + + // Check deposit amount + require(msg.value >= 1 ether, "DepositContract: deposit value too low"); + require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei"); + uint deposit_amount = msg.value / 1 gwei; + require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); + + // Emit `DepositEvent` log + bytes memory amount = to_little_endian_64(uint64(deposit_amount)); + emit DepositEvent( + pubkey, + withdrawal_credentials, + amount, + signature, + to_little_endian_64(uint64(deposit_count)) + ); + + // Compute deposit data root (`DepositData` hash tree root) + bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0))); + bytes32 signature_root = sha256(abi.encodePacked( + sha256(abi.encodePacked(signature[:64])), + sha256(abi.encodePacked(signature[64:], bytes32(0))) + )); + bytes32 node = sha256(abi.encodePacked( + sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), + sha256(abi.encodePacked(amount, bytes24(0), signature_root)) + )); + + // Verify computed and expected deposit data roots match + require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"); + + // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`) + require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full"); + + // Add deposit data root to Merkle tree (update a single `branch` node) + deposit_count += 1; + uint size = deposit_count; + for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { + if ((size & 1) == 1) { + branch[height] = node; + return; + } + node = sha256(abi.encodePacked(branch[height], node)); + size /= 2; + } + // As the loop should always end prematurely with the `return` statement, + // this code should be unreachable. We assert `false` just to be safe. + assert(false); + } + + function supportsInterface(bytes4 interfaceId) override external pure returns (bool) { + return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId; + } + + function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { + ret = new bytes(8); + bytes8 bytesValue = bytes8(value); + // Byteswapping during copying to bytes. + ret[0] = bytesValue[7]; + ret[1] = bytesValue[6]; + ret[2] = bytesValue[5]; + ret[3] = bytesValue[4]; + ret[4] = bytesValue[3]; + ret[5] = bytesValue[2]; + ret[6] = bytesValue[1]; + ret[7] = bytesValue[0]; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor() +// gas irOptimized: 809602 +// gas irOptimized code: 558000 +// gas legacy: 919945 +// gas legacy code: 1437600 +// gas legacyOptimized: 848699 +// gas legacyOptimized code: 878200 +// supportsInterface(bytes4): 0x0 -> 0 +// supportsInterface(bytes4): 0xffffffff00000000000000000000000000000000000000000000000000000000 -> false # defined to be false by ERC-165 # +// supportsInterface(bytes4): 0x01ffc9a700000000000000000000000000000000000000000000000000000000 -> true # ERC-165 id # +// supportsInterface(bytes4): 0x8564090700000000000000000000000000000000000000000000000000000000 -> true # the deposit interface id # +// get_deposit_root() -> 0x691a2cb303bfa42437412cd455155952c395370b31a4be3adfb0a373e7ee7c5c +// gas irOptimized: 109178 +// gas legacy: 142735 +// gas legacyOptimized: 117558 +// get_deposit_count() -> 0x20, 8, 0 # TODO: check balance and logs after each deposit # +// deposit(bytes,bytes,bytes,bytes32), 32 ether: 0 -> FAILURE # Empty input # +// get_deposit_root() -> 0x691a2cb303bfa42437412cd455155952c395370b31a4be3adfb0a373e7ee7c5c +// gas irOptimized: 109178 +// gas legacy: 142735 +// gas legacyOptimized: 117558 +// get_deposit_count() -> 0x20, 8, 0 +// deposit(bytes,bytes,bytes,bytes32), 1 ether: 0x80, 0xe0, 0x120, 0xaa4a8d0b7d9077248630f1a4701ae9764e42271d7f22b7838778411857fd349e, 0x30, 0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f73292, 0x67a8811c397529dac52ae1342ba58c9500000000000000000000000000000000, 0x20, 0x00f50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71, 0x60, 0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb, 0x5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c132, 0x4b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8 -> # txhash: 0x7085c586686d666e8bb6e9477a0f0b09565b2060a11f1c4209d3a52295033832 # +// ~ emit DepositEvent(bytes,bytes,bytes,bytes,bytes): 0xa0, 0x0100, 0x0140, 0x0180, 0x0200, 0x30, 0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f73292, 0x67a8811c397529dac52ae1342ba58c9500000000000000000000000000000000, 0x20, 0xf50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71, 0x08, 0xca9a3b00000000000000000000000000000000000000000000000000000000, 0x60, 0xa29d01cc8c6296a8150e515b5995390ef841dc18948aa3e79be6d7c1851b4cbb, 0x5d6ff49fa70b9c782399506a22a85193151b9b691245cebafd2063012443c132, 0x4b6c36debaedefb7b2d71b0503ffdc00150aaffd42e63358238ec888901738b8, 0x08, 0x00 +// get_deposit_root() -> 0x9c655a38a141cd7aecbff6b73d31a1c3d8bbcc1de86b627ac57e7e8342ab2839 +// gas irOptimized: 109174 +// gas legacy: 142744 +// gas legacyOptimized: 117570 +// get_deposit_count() -> 0x20, 8, 0x0100000000000000000000000000000000000000000000000000000000000000 +// deposit(bytes,bytes,bytes,bytes32), 32 ether: 0x80, 0xe0, 0x120, 0xdbd986dc85ceb382708cf90a3500f500f0a393c5ece76963ac3ed72eccd2c301, 0x30, 0xb2ce0f79f90e7b3a113ca5783c65756f96c4b4673c2b5c1eb4efc22280259441, 0x06d601211e8866dc5b50dc48a244dd7c00000000000000000000000000000000, 0x20, 0x00344b6c73f71b11c56aba0d01b7d8ad83559f209d0a4101a515f6ad54c89771, 0x60, 0x945caaf82d18e78c033927d51f452ebcd76524497b91d7a11219cb3db6a1d369, 0x7595fc095ce489e46b2ef129591f2f6d079be4faaf345a02c5eb133c072e7c56, 0x0c6c3617eee66b4b878165c502357d49485326bc6b31bc96873f308c8f19c09d -> # txhash: 0x404d8e109822ce448e68f45216c12cb051b784d068fbe98317ab8e50c58304ac # +// ~ emit DepositEvent(bytes,bytes,bytes,bytes,bytes): 0xa0, 0x0100, 0x0140, 0x0180, 0x0200, 0x30, 0xb2ce0f79f90e7b3a113ca5783c65756f96c4b4673c2b5c1eb4efc22280259441, 0x06d601211e8866dc5b50dc48a244dd7c00000000000000000000000000000000, 0x20, 0x344b6c73f71b11c56aba0d01b7d8ad83559f209d0a4101a515f6ad54c89771, 0x08, 0x40597307000000000000000000000000000000000000000000000000000000, 0x60, 0x945caaf82d18e78c033927d51f452ebcd76524497b91d7a11219cb3db6a1d369, 0x7595fc095ce489e46b2ef129591f2f6d079be4faaf345a02c5eb133c072e7c56, 0x0c6c3617eee66b4b878165c502357d49485326bc6b31bc96873f308c8f19c09d, 0x08, 0x0100000000000000000000000000000000000000000000000000000000000000 +// get_deposit_root() -> 0xd6ccaa7e8ae43c12e2b885375964e2e5e6cb1708d13ff9fd63c8b3325b423f73 +// gas irOptimized: 109174 +// gas legacy: 142744 +// gas legacyOptimized: 117570 +// get_deposit_count() -> 0x20, 8, 0x0200000000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/externalContracts/snark.sol b/test/libsolidity/semanticTests/externalContracts/snark.sol index 906279d17935..0c8c3bf5243b 100644 --- a/test/libsolidity/semanticTests/externalContracts/snark.sol +++ b/test/libsolidity/semanticTests/externalContracts/snark.sol @@ -18,9 +18,9 @@ library Pairing { function P2() internal returns (G2Point memory) { return G2Point( [11559732032986387107991004021392285783925812861821192530917403151452391805634, - 10857046999023057135944570762232829481370756359578518086990519993285655852781], + 10857046999023057135944570762232829481370756359578518086990519993285655852781], [4082367875863433681332203403145435568316851327593401208105741076214120093531, - 8495653923123431417604973247489272438418190587263600148770280649306958101930] + 8495653923123431417604973247489272438418190587263600148770280649306958101930] ); } @@ -43,7 +43,7 @@ library Pairing { bool success; assembly { success := call(sub(gas(), 2000), 6, 0, input, 0xc0, r, 0x60) - // Use "invalid" to make gas estimation work + // Use "invalid" to make gas estimation work switch success case 0 { invalid() } } require(success); @@ -59,7 +59,7 @@ library Pairing { bool success; assembly { success := call(sub(gas(), 2000), 7, 0, input, 0x80, r, 0x60) - // Use "invalid" to make gas estimation work + // Use "invalid" to make gas estimation work switch success case 0 { invalid() } } require(success); @@ -87,7 +87,7 @@ library Pairing { bool success; assembly { success := call(sub(gas(), 2000), 8, 0, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) - // Use "invalid" to make gas estimation work + // Use "invalid" to make gas estimation work switch success case 0 { invalid() } } require(success); @@ -121,7 +121,7 @@ library Pairing { G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2, G1Point memory c1, G2Point memory c2, - G1Point memory d1, G2Point memory d2 + G1Point memory d1, G2Point memory d2 ) internal returns (bool) { G1Point[] memory p1 = new G1Point[](4); G2Point[] memory p2 = new G2Point[](4); @@ -289,6 +289,7 @@ contract Test { // // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // library: Pairing // f() -> true diff --git a/test/libsolidity/semanticTests/externalContracts/snark_eof.sol b/test/libsolidity/semanticTests/externalContracts/snark_eof.sol new file mode 100644 index 000000000000..ff1db7419b51 --- /dev/null +++ b/test/libsolidity/semanticTests/externalContracts/snark_eof.sol @@ -0,0 +1,312 @@ +library Pairing { + struct G1Point { + uint X; + uint Y; + } + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint[2] X; + uint[2] Y; + } + + /// @return the generator of G1 + function P1() internal returns (G1Point memory) { + return G1Point(1, 2); + } + + /// @return the generator of G2 + function P2() internal returns (G2Point memory) { + return G2Point( + [11559732032986387107991004021392285783925812861821192530917403151452391805634, + 10857046999023057135944570762232829481370756359578518086990519993285655852781], + [4082367875863433681332203403145435568316851327593401208105741076214120093531, + 8495653923123431417604973247489272438418190587263600148770280649306958101930] + ); + } + + /// @return the negation of p, i.e. p.add(p.negate()) should be zero. + function negate(G1Point memory p) internal returns (G1Point memory) { + // The prime q in the base field F_q for G1 + uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + if (p.X == 0 && p.Y == 0) + return G1Point(0, 0); + return G1Point(p.X, q - (p.Y % q)); + } + + /// @return r the sum of two points of G1 + function add(G1Point memory p1, G1Point memory p2) internal returns (G1Point memory r) { + uint[4] memory input; + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + uint return_flag; + assembly { + return_flag := extcall(6, input, 0xc0, 0) + // Use "invalid" to make gas estimation work + switch return_flag case 1 { invalid() } case 2 { invalid() } + + returndatacopy(r, 0, 64) + } + require(return_flag == 0); + } + + /// @return r the product of a point on G1 and a scalar, i.e. + /// p == p.mul(1) and p.add(p) == p.mul(2) for all points p. + function mul(G1Point memory p, uint s) internal returns (G1Point memory r) { + uint[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s; + uint return_flag; + assembly { + return_flag := extcall(7, input, 0x80, 0) + // Use "invalid" to make gas estimation work + switch return_flag case 1 { invalid() } case 2 { invalid() } + + returndatacopy(r, 0, 64) + } + require(return_flag == 0); + } + + /// @return the result of computing the pairing check + /// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 + /// For example pairing([P1(), P1().negate()], [P2(), P2()]) should + /// return true. + function pairing(G1Point[] memory p1, G2Point[] memory p2) internal returns (bool) { + require(p1.length == p2.length); + uint elements = p1.length; + uint inputSize = p1.length * 6; + uint[] memory input = new uint[](inputSize); + for (uint i = 0; i < elements; i++) + { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[0]; + input[i * 6 + 3] = p2[i].X[1]; + input[i * 6 + 4] = p2[i].Y[0]; + input[i * 6 + 5] = p2[i].Y[1]; + } + uint[1] memory out; + uint return_flag; + assembly { + return_flag := extcall(8, add(input, 0x20), mul(inputSize, 0x20), 0) + // Use "invalid" to make gas estimation work + switch return_flag case 1 { invalid() } case 2 { invalid() } + + returndatacopy(out, 0, 32) + } + require(return_flag == 0); + return out[0] != 0; + } + function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) internal returns (bool) { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } + function pairingProd3( + G1Point memory a1, G2Point memory a2, + G1Point memory b1, G2Point memory b2, + G1Point memory c1, G2Point memory c2 + ) internal returns (bool) { + G1Point[] memory p1 = new G1Point[](3); + G2Point[] memory p2 = new G2Point[](3); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + return pairing(p1, p2); + } + function pairingProd4( + G1Point memory a1, G2Point memory a2, + G1Point memory b1, G2Point memory b2, + G1Point memory c1, G2Point memory c2, + G1Point memory d1, G2Point memory d2 + ) internal returns (bool) { + G1Point[] memory p1 = new G1Point[](4); + G2Point[] memory p2 = new G2Point[](4); + p1[0] = a1; + p1[1] = b1; + p1[2] = c1; + p1[3] = d1; + p2[0] = a2; + p2[1] = b2; + p2[2] = c2; + p2[3] = d2; + return pairing(p1, p2); + } +} + +contract Test { + using Pairing for *; + struct VerifyingKey { + Pairing.G2Point A; + Pairing.G1Point B; + Pairing.G2Point C; + Pairing.G2Point gamma; + Pairing.G1Point gammaBeta1; + Pairing.G2Point gammaBeta2; + Pairing.G2Point Z; + Pairing.G1Point[] IC; + } + struct Proof { + Pairing.G1Point A; + Pairing.G1Point A_p; + Pairing.G2Point B; + Pairing.G1Point B_p; + Pairing.G1Point C; + Pairing.G1Point C_p; + Pairing.G1Point K; + Pairing.G1Point H; + } + function f() public returns (bool) { + Pairing.G1Point memory p1; + Pairing.G1Point memory p2; + p1.X = 1; p1.Y = 2; + p2.X = 1; p2.Y = 2; + Pairing.G1Point memory explicit_sum = Pairing.add(p1, p2); + Pairing.G1Point memory scalar_prod = Pairing.mul(p1, 2); + return (explicit_sum.X == scalar_prod.X && + explicit_sum.Y == scalar_prod.Y); + } + function g() public returns (bool) { + Pairing.G1Point memory x = Pairing.add(Pairing.P1(), Pairing.negate(Pairing.P1())); + // should be zero + return (x.X == 0 && x.Y == 0); + } + function testMul() public returns (bool) { + Pairing.G1Point memory p; + // @TODO The points here are reported to be not well-formed + p.X = 14125296762497065001182820090155008161146766663259912659363835465243039841726; + p.Y = 16229134936871442251132173501211935676986397196799085184804749187146857848057; + p = Pairing.mul(p, 13986731495506593864492662381614386532349950841221768152838255933892789078521); + return + p.X == 18256332256630856740336504687838346961237861778318632856900758565550522381207 && + p.Y == 6976682127058094634733239494758371323697222088503263230319702770853579280803; + } + function pair() public returns (bool) { + Pairing.G2Point memory fiveTimesP2 = Pairing.G2Point( + [4540444681147253467785307942530223364530218361853237193970751657229138047649, 20954117799226682825035885491234530437475518021362091509513177301640194298072], + [11631839690097995216017572651900167465857396346217730511548857041925508482915, 21508930868448350162258892668132814424284302804699005394342512102884055673846] + ); + // The prime p in the base field F_p for G1 + uint p = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + Pairing.G1Point[] memory g1points = new Pairing.G1Point[](2); + Pairing.G2Point[] memory g2points = new Pairing.G2Point[](2); + // check e(5 P1, P2)e(-P1, 5 P2) == 1 + g1points[0] = Pairing.P1().mul(5); + g1points[1] = Pairing.P1().negate(); + g2points[0] = Pairing.P2(); + g2points[1] = fiveTimesP2; + if (!Pairing.pairing(g1points, g2points)) + return false; + // check e(P1, P2)e(-P1, P2) == 1 + g1points[0] = Pairing.P1(); + g1points[1] = Pairing.P1(); + g1points[1].Y = p - g1points[1].Y; + g2points[0] = Pairing.P2(); + g2points[1] = Pairing.P2(); + if (!Pairing.pairing(g1points, g2points)) + return false; + return true; + } + function verifyingKey() internal returns (VerifyingKey memory vk) { + vk.A = Pairing.G2Point([0x209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7, 0x04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678], [0x2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d, 0x120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550]); + vk.B = Pairing.G1Point(0x2eca0c7238bf16e83e7a1e6c5d49540685ff51380f309842a98561558019fc02, 0x03d3260361bb8451de5ff5ecd17f010ff22f5c31cdf184e9020b06fa5997db84); + vk.C = Pairing.G2Point([0x2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb, 0x01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb3], [0x14a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713, 0x178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee24590]); + vk.gamma = Pairing.G2Point([0x25f83c8b6ab9de74e7da488ef02645c5a16a6652c3c71a15dc37fe3a5dcb7cb1, 0x22acdedd6308e3bb230d226d16a105295f523a8a02bfc5e8bd2da135ac4c245d], [0x065bbad92e7c4e31bf3757f1fe7362a63fbfee50e7dc68da116e67d600d9bf68, 0x06d302580dc0661002994e7cd3a7f224e7ddc27802777486bf80f40e4ca3cfdb]); + vk.gammaBeta1 = Pairing.G1Point(0x15794ab061441e51d01e94640b7e3084a07e02c78cf3103c542bc5b298669f21, 0x14db745c6780e9df549864cec19c2daf4531f6ec0c89cc1c7436cc4d8d300c6d); + vk.gammaBeta2 = Pairing.G2Point([0x1f39e4e4afc4bc74790a4a028aff2c3d2538731fb755edefd8cb48d6ea589b5e, 0x283f150794b6736f670d6a1033f9b46c6f5204f50813eb85c8dc4b59db1c5d39], [0x140d97ee4d2b36d99bc49974d18ecca3e7ad51011956051b464d9e27d46cc25e, 0x0764bb98575bd466d32db7b15f582b2d5c452b36aa394b789366e5e3ca5aabd4]); + vk.Z = Pairing.G2Point([0x217cee0a9ad79a4493b5253e2e4e3a39fc2df38419f230d341f60cb064a0ac29, 0x0a3d76f140db8418ba512272381446eb73958670f00cf46f1d9e64cba057b53c], [0x26f64a8ec70387a13e41430ed3ee4a7db2059cc5fc13c067194bcc0cb49a9855, 0x2fd72bd9edb657346127da132e5b82ab908f5816c826acb499e22f2412d1a2d7]); + vk.IC = new Pairing.G1Point[](10); + vk.IC[0] = Pairing.G1Point(0x0aee46a7ea6e80a3675026dfa84019deee2a2dedb1bbe11d7fe124cb3efb4b5a, 0x044747b6e9176e13ede3a4dfd0d33ccca6321b9acd23bf3683a60adc0366ebaf); + vk.IC[1] = Pairing.G1Point(0x1e39e9f0f91fa7ff8047ffd90de08785777fe61c0e3434e728fce4cf35047ddc, 0x2e0b64d75ebfa86d7f8f8e08abbe2e7ae6e0a1c0b34d028f19fa56e9450527cb); + vk.IC[2] = Pairing.G1Point(0x1c36e713d4d54e3a9644dffca1fc524be4868f66572516025a61ca542539d43f, 0x042dcc4525b82dfb242b09cb21909d5c22643dcdbe98c4d082cc2877e96b24db); + vk.IC[3] = Pairing.G1Point(0x17d5d09b4146424bff7e6fb01487c477bbfcd0cdbbc92d5d6457aae0b6717cc5, 0x02b5636903efbf46db9235bbe74045d21c138897fda32e079040db1a16c1a7a1); + vk.IC[4] = Pairing.G1Point(0x0f103f14a584d4203c27c26155b2c955f8dfa816980b24ba824e1972d6486a5d, 0x0c4165133b9f5be17c804203af781bcf168da7386620479f9b885ecbcd27b17b); + vk.IC[5] = Pairing.G1Point(0x232063b584fb76c8d07995bee3a38fa7565405f3549c6a918ddaa90ab971e7f8, 0x2ac9b135a81d96425c92d02296322ad56ffb16299633233e4880f95aafa7fda7); + vk.IC[6] = Pairing.G1Point(0x09b54f111d3b2d1b2fe1ae9669b3db3d7bf93b70f00647e65c849275de6dc7fe, 0x18b2e77c63a3e400d6d1f1fbc6e1a1167bbca603d34d03edea231eb0ab7b14b4); + vk.IC[7] = Pairing.G1Point(0x0c54b42137b67cc268cbb53ac62b00ecead23984092b494a88befe58445a244a, 0x18e3723d37fae9262d58b548a0575f59d9c3266db7afb4d5739555837f6b8b3e); + vk.IC[8] = Pairing.G1Point(0x0a6de0e2240aa253f46ce0da883b61976e3588146e01c9d8976548c145fe6e4a, 0x04fbaa3a4aed4bb77f30ebb07a3ec1c7d77a7f2edd75636babfeff97b1ea686e); + vk.IC[9] = Pairing.G1Point(0x111e2e2a5f8828f80ddad08f9f74db56dac1cc16c1cb278036f79a84cf7a116f, 0x1d7d62e192b219b9808faa906c5ced871788f6339e8d91b83ac1343e20a16b30); + } + function verify(uint[] memory input, Proof memory proof) internal returns (uint) { + VerifyingKey memory vk = verifyingKey(); + require(input.length + 1 == vk.IC.length); + // Compute the linear combination vk_x + Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + for (uint i = 0; i < input.length; i++) + vk_x = Pairing.add(vk_x, Pairing.mul(vk.IC[i + 1], input[i])); + vk_x = Pairing.add(vk_x, vk.IC[0]); + if (!Pairing.pairingProd2(proof.A, vk.A, Pairing.negate(proof.A_p), Pairing.P2())) return 1; + if (!Pairing.pairingProd2(vk.B, proof.B, Pairing.negate(proof.B_p), Pairing.P2())) return 2; + if (!Pairing.pairingProd2(proof.C, vk.C, Pairing.negate(proof.C_p), Pairing.P2())) return 3; + if (!Pairing.pairingProd3( + proof.K, vk.gamma, + Pairing.negate(Pairing.add(vk_x, Pairing.add(proof.A, proof.C))), vk.gammaBeta2, + Pairing.negate(vk.gammaBeta1), proof.B + )) return 4; + if (!Pairing.pairingProd3( + Pairing.add(vk_x, proof.A), proof.B, + Pairing.negate(proof.H), vk.Z, + Pairing.negate(proof.C), Pairing.P2() + )) return 5; + return 0; + } + event Verified(string); + function verifyTx() public returns (bool) { + uint[] memory input = new uint[](9); + Proof memory proof; + proof.A = Pairing.G1Point(12873740738727497448187997291915224677121726020054032516825496230827252793177, 21804419174137094775122804775419507726154084057848719988004616848382402162497); + proof.A_p = Pairing.G1Point(7742452358972543465462254569134860944739929848367563713587808717088650354556, 7324522103398787664095385319014038380128814213034709026832529060148225837366); + proof.B = Pairing.G2Point( + [8176651290984905087450403379100573157708110416512446269839297438960217797614, 15588556568726919713003060429893850972163943674590384915350025440408631945055], + [15347511022514187557142999444367533883366476794364262773195059233657571533367, 4265071979090628150845437155927259896060451682253086069461962693761322642015]); + proof.B_p = Pairing.G1Point(2979746655438963305714517285593753729335852012083057917022078236006592638393, 6470627481646078059765266161088786576504622012540639992486470834383274712950); + proof.C = Pairing.G1Point(6851077925310461602867742977619883934042581405263014789956638244065803308498, 10336382210592135525880811046708757754106524561907815205241508542912494488506); + proof.C_p = Pairing.G1Point(12491625890066296859584468664467427202390981822868257437245835716136010795448, 13818492518017455361318553880921248537817650587494176379915981090396574171686); + proof.H = Pairing.G1Point(12091046215835229523641173286701717671667447745509192321596954139357866668225, 14446807589950902476683545679847436767890904443411534435294953056557941441758); + proof.K = Pairing.G1Point(21341087976609916409401737322664290631992568431163400450267978471171152600502, 2942165230690572858696920423896381470344658299915828986338281196715687693170); + input[0] = 13986731495506593864492662381614386532349950841221768152838255933892789078521; + input[1] = 622860516154313070522697309645122400675542217310916019527100517240519630053; + input[2] = 11094488463398718754251685950409355128550342438297986977413505294941943071569; + input[3] = 6627643779954497813586310325594578844876646808666478625705401786271515864467; + input[4] = 2957286918163151606545409668133310005545945782087581890025685458369200827463; + input[5] = 1384290496819542862903939282897996566903332587607290986044945365745128311081; + input[6] = 5613571677741714971687805233468747950848449704454346829971683826953541367271; + input[7] = 9643208548031422463313148630985736896287522941726746581856185889848792022807; + input[8] = 18066496933330839731877828156604; + if (verify(input, proof) == 0) { + emit Verified("Successfully verified."); + return true; + } else { + return false; + } + } +} +/// Disabled because the point seems to be not well-formed, we need to find another example. +/// testMul() -> true +// +// ==== +// EVMVersion: >=constantinople +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// library: Pairing +// f() -> true +// g() -> true +// pair() -> true +// gas irOptimized: 270424 +// gas legacy: 275206 +// gas legacyOptimized: 266925 +// verifyTx() -> true +// ~ emit Verified(string): 0x20, 0x16, "Successfully verified." +// gas irOptimized: 785783 +// gas legacy: 801868 +// gas legacyOptimized: 770942 diff --git a/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol b/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol index 7a05ff5d0270..3df7b02d346c 100644 --- a/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol +++ b/test/libsolidity/semanticTests/freeFunctions/free_runtimecode.sol @@ -11,5 +11,7 @@ contract D { return test(); } } +// ==== +// compileToEOF: false // ---- // f() -> true diff --git a/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol b/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol index 229618384d1e..f34b2b1a30be 100644 --- a/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol +++ b/test/libsolidity/semanticTests/functionCall/calling_nonexisting_contract_throws.sol @@ -21,6 +21,8 @@ contract C { return 7; } } +// ==== +// compileToEOF: false // ---- // f() -> FAILURE // g() -> FAILURE diff --git a/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol b/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol index 6f7f020fe678..88dc405f2425 100644 --- a/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol +++ b/test/libsolidity/semanticTests/functionCall/external_call_at_construction_time.sol @@ -18,6 +18,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // f(uint256): 0 -> FAILURE // f(uint256): 1 -> FAILURE diff --git a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol index 472921c1d25c..e25ae41d150e 100644 --- a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol +++ b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting.sol @@ -20,6 +20,8 @@ contract C { return 1 + c; } } +// ==== +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 88853 diff --git a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol index d1b6e8e866ae..9e8590e510a3 100644 --- a/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol +++ b/test/libsolidity/semanticTests/functionCall/external_call_to_nonexisting_debugstrings.sol @@ -23,6 +23,7 @@ contract C { // ==== // EVMVersion: >=byzantium // revertStrings: debug +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 98698 diff --git a/test/libsolidity/semanticTests/functionCall/failed_create.sol b/test/libsolidity/semanticTests/functionCall/failed_create.sol index 657b4b5cfff6..0f583a2831db 100644 --- a/test/libsolidity/semanticTests/functionCall/failed_create.sol +++ b/test/libsolidity/semanticTests/functionCall/failed_create.sol @@ -15,6 +15,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // constructor(), 20 wei // gas irOptimized: 61548 diff --git a/test/libsolidity/semanticTests/functionCall/failed_create_eof.sol b/test/libsolidity/semanticTests/functionCall/failed_create_eof.sol new file mode 100644 index 000000000000..9457cdecbd2f --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/failed_create_eof.sol @@ -0,0 +1,38 @@ +contract D { constructor() payable {} } +contract C { + uint public x; + constructor() payable {} + function f(uint amount) public returns (D) { + x++; + return (new D){value: amount, salt: bytes32(x)}(); + } + function stack(uint depth) public payable returns (address) { + if (depth > 0) + return this.stack(depth - 1); + else + return address(f(0)); + } +} +// ==== +// EVMVersion: >=byzantium +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 20 wei +// gas irOptimized: 61548 +// gas irOptimized code: 104600 +// gas legacy: 70147 +// gas legacy code: 215400 +// gas legacyOptimized: 61715 +// gas legacyOptimized code: 106800 +// f(uint256): 20 -> 0x760ad2428f897a994a0377ef5a3b626dfe295672 +// x() -> 1 +// f(uint256): 20 -> FAILURE +// x() -> 1 +// stack(uint256): 1023 -> FAILURE +// gas irOptimized: 252410 +// gas legacy: 477722 +// gas legacyOptimized: 299567 +// x() -> 1 +// stack(uint256): 10 -> 0xd64ba06e96a2fa752f7c8e10c7b6912e518e40c2 +// x() -> 2 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol index e6187d06e2df..a8e1454cb615 100644 --- a/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic.sol @@ -36,6 +36,8 @@ contract test { myBal = address(this).balance; } } +// ==== +// compileToEOF: false // ---- // constructor(), 20 wei -> // gas irOptimized: 120218 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol new file mode 100644 index 000000000000..bb52384134d4 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_basic_eof.sol @@ -0,0 +1,52 @@ +contract helper { + bool flag; + + function getBalance() public payable returns (uint256 myBalance) { + return address(this).balance; + } + + function setFlag() public { + flag = true; + } + + function getFlag() public returns (bool fl) { + return flag; + } +} + + +contract test { + helper h; + + constructor() payable { + h = new helper(); + } + + function sendAmount(uint256 amount) public payable returns (uint256 bal) { + return h.getBalance{value: amount}(); + } + + function outOfGas() public returns (bool ret) { + h.setFlag(); + return true; + } + + function checkState() public returns (bool flagAfter, uint256 myBal) { + flagAfter = h.getFlag(); + myBal = address(this).balance; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 20 wei -> +// gas irOptimized: 120218 +// gas irOptimized code: 132000 +// gas legacy: 130568 +// gas legacy code: 261000 +// gas legacyOptimized: 121069 +// gas legacyOptimized code: 147000 +// sendAmount(uint256): 5 -> 5 +// outOfGas() -> true +// checkState() -> true, 15 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol index 41079af03955..a8a70bbe38d1 100644 --- a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol @@ -35,6 +35,8 @@ contract test { myBal = address(this).balance; } } +// ==== +// compileToEOF: false // ---- // constructor(), 20 wei -> // gas irOptimized: 120218 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol new file mode 100644 index 000000000000..a382519cc1c5 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax_eof.sol @@ -0,0 +1,51 @@ +contract helper { + bool flag; + + function getBalance() payable public returns(uint256 myBalance) { + return address(this).balance; + } + + function setFlag() public { + flag = true; + } + + function getFlag() public returns(bool fl) { + return flag; + } +} +contract test { + helper h; + constructor() payable { + h = new helper(); + } + + function sendAmount(uint amount) public payable returns(uint256 bal) { + return h.getBalance{value: amount}(); + } + + function outOfGas() public returns(bool ret) { + h.setFlag { + gas: 2 + }(); + return true; + } + + function checkState() public returns(bool flagAfter, uint myBal) { + flagAfter = h.getFlag(); + myBal = address(this).balance; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 20 wei -> +// gas irOptimized: 120218 +// gas irOptimized code: 132000 +// gas legacy: 130568 +// gas legacy code: 261000 +// gas legacyOptimized: 121069 +// gas legacyOptimized code: 147000 +// sendAmount(uint256): 5 -> 5 +// outOfGas() -> true +// checkState() -> true, 15 \ No newline at end of file diff --git a/test/libsolidity/semanticTests/functionTypes/address_member.sol b/test/libsolidity/semanticTests/functionTypes/address_member.sol index a5f56f3dc688..bf3ea77b52d1 100644 --- a/test/libsolidity/semanticTests/functionTypes/address_member.sol +++ b/test/libsolidity/semanticTests/functionTypes/address_member.sol @@ -6,5 +6,7 @@ contract C { a2 = [this.f.address][0]; } } +// ==== +// compileToEOF: false // ---- // f() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e, 0xc06afe3a8444fc0004668591e8306bfb9968e79e diff --git a/test/libsolidity/semanticTests/functionTypes/address_member_eof.sol b/test/libsolidity/semanticTests/functionTypes/address_member_eof.sol new file mode 100644 index 000000000000..3d657a23222b --- /dev/null +++ b/test/libsolidity/semanticTests/functionTypes/address_member_eof.sol @@ -0,0 +1,13 @@ +contract C { + function f() public view returns (address a1, address a2) { + a1 = this.f.address; + this.f.address; + [this.f.address][0]; + a2 = [this.f.address][0]; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0xab639b56c881a5b607f4a706bcf1d7d383b83703, 0xab639b56c881a5b607f4a706bcf1d7d383b83703 diff --git a/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol index d5e105e5f536..26f9206aca0c 100644 --- a/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol +++ b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage.sol @@ -19,6 +19,8 @@ contract C { delete x; } } +// ==== +// compileToEOF: false // ---- // x() -> 0 // y() -> 0 diff --git a/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol new file mode 100644 index 000000000000..2ad999449a59 --- /dev/null +++ b/test/libsolidity/semanticTests/functionTypes/function_external_delete_storage_eof.sol @@ -0,0 +1,40 @@ +contract C { + function() external public x; + uint public y = 0; + + function increment() public { + ++y; + } + + function set() external { + x = this.increment; + } + + function incrementIndirectly() public { + x(); + } + + function deleteFunction() public { + // used to lead to an ICE during IR + delete x; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// x() -> 0 +// y() -> 0 +// increment() -> +// y() -> 1 +// set() -> +// x() -> 0xb7ac6c1bf268409abee9be742a25c518cfd13729d09de08a0000000000000000 +// increment() -> +// y() -> 2 +// incrementIndirectly() -> +// y() -> 3 +// deleteFunction() -> +// increment() -> +// y() -> 4 +// incrementIndirectly() -> +// y() -> 4 diff --git a/test/libsolidity/semanticTests/immutable/multi_creation.sol b/test/libsolidity/semanticTests/immutable/multi_creation.sol index aa8a1ad8a860..68b5a6211f8e 100644 --- a/test/libsolidity/semanticTests/immutable/multi_creation.sol +++ b/test/libsolidity/semanticTests/immutable/multi_creation.sol @@ -25,6 +25,8 @@ contract C { return (a, (new A()).f(), (new B()).f()); } } +// ==== +// compileToEOF: false // ---- // f() -> 3, 7, 5 // gas irOptimized: 86796 diff --git a/test/libsolidity/semanticTests/immutable/multi_creation_eof.sol b/test/libsolidity/semanticTests/immutable/multi_creation_eof.sol new file mode 100644 index 000000000000..9c376fc70ff8 --- /dev/null +++ b/test/libsolidity/semanticTests/immutable/multi_creation_eof.sol @@ -0,0 +1,40 @@ +contract A { + uint immutable a; + constructor() { + a = 7; + } + function f() public view returns (uint) { return a; } +} +contract B { + uint immutable a; + constructor() { + a = 5; + } + function f() public view returns (uint) { return a; } +} +contract C { + uint immutable a; + uint public x; + uint public y; + constructor() { + a = 3; + x = (new A{salt: hex"00"}()).f(); + y = (new B{salt: hex"00"}()).f(); + } + function f() public returns (uint256, uint, uint) { + return (a, (new A{salt: hex"01"}()).f(), (new B{salt: hex"01"}()).f()); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 3, 7, 5 +// gas irOptimized: 86796 +// gas irOptimized code: 37200 +// gas legacy: 87728 +// gas legacy code: 60800 +// gas legacyOptimized: 86771 +// gas legacyOptimized code: 37200 +// x() -> 7 +// y() -> 5 diff --git a/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol b/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol index 0865ed2876ad..25c31c95f1bb 100644 --- a/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol +++ b/test/libsolidity/semanticTests/inheritance/address_overload_resolution.sol @@ -18,6 +18,8 @@ contract D { return (new C()).transfer(5); } } +// ==== +// compileToEOF: false // ---- // f() -> 1 // gas irOptimized: 77051 diff --git a/test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol b/test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol new file mode 100644 index 000000000000..726aba9911a9 --- /dev/null +++ b/test/libsolidity/semanticTests/inheritance/address_overload_resolution_eof.sol @@ -0,0 +1,32 @@ +contract C { + function balance() public returns (uint256) { + return 1; + } + + function transfer(uint256 amount) public returns (uint256) { + return amount; + } +} + + +contract D { + function f() public returns (uint256) { + return (new C{salt: hex"00"}()).balance(); + } + + function g() public returns (uint256) { + return (new C{salt: hex"01"}()).transfer(5); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 1 +// gas irOptimized: 77051 +// gas legacy: 54480 +// gas legacy code: 57800 +// g() -> 5 +// gas irOptimized: 77106 +// gas legacy: 55016 +// gas legacy code: 57800 diff --git a/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol b/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol index b9697c5ae1d7..26aaed73d511 100644 --- a/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol +++ b/test/libsolidity/semanticTests/inheritance/member_notation_ctor.sol @@ -17,6 +17,8 @@ contract A { return d.getX(); } } +// ==== +// compileToEOF: false // ---- // g(int256): -1 -> -1 // gas legacy: 77876 diff --git a/test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol b/test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol new file mode 100644 index 000000000000..6937a6743db6 --- /dev/null +++ b/test/libsolidity/semanticTests/inheritance/member_notation_ctor_eof.sol @@ -0,0 +1,29 @@ +==== Source: A ==== +contract C { + int private x; + constructor (int p) public { x = p; } + function getX() public returns (int) { return x; } +} +==== Source: B ==== +import "A" as M; + +contract D is M.C { + constructor (int p) M.C(p) public {} +} + +contract A { + function g(int p) public returns (int) { + D d = new D{salt: bytes32(uint256(p))}(p); + return d.getX(); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// g(int256): -1 -> -1 +// gas legacy: 77878 +// gas legacy code: 24200 +// g(int256): 10 -> 10 +// gas legacy: 77506 +// gas legacy code: 24200 diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol index ae891df5dc4f..4b46ac74b02b 100644 --- a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address.sol @@ -12,6 +12,8 @@ contract C { return this.testFunction.address; } } +// ==== +// compileToEOF: false // ---- // testYul() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e // testSol() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e diff --git a/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol new file mode 100644 index 000000000000..c1823c6f4c30 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/external_function_pointer_address_eof.sol @@ -0,0 +1,20 @@ +contract C { + function testFunction() external {} + + function testYul() public returns (address adr) { + function() external fp = this.testFunction; + + assembly { + adr := fp.address + } + } + function testSol() public returns (address) { + return this.testFunction.address; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testYul() -> 0xb546c6ff998ecadc80f48650a3d77fd361aebb4e +// testSol() -> 0xb546c6ff998ecadc80f48650a3d77fd361aebb4e diff --git a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol index e43fc475740a..540dbeb163d6 100644 --- a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol +++ b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls.sol @@ -63,6 +63,7 @@ contract C { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // testDelegateCall() -> true // testCall() -> true diff --git a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol new file mode 100644 index 000000000000..dcd7ab5ca68b --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_low_level_calls_eof.sol @@ -0,0 +1,78 @@ +contract D { + function addOne() external { + assembly { + let x := tload(0) + tstore(0, add(x, 1)) + } + } + function get() external returns (uint x) { + assembly { + x := tload(0) + } + } +} + +contract C { + function set(uint x) external { + assembly { + tstore(0, x) + } + } + + function get() external view returns (uint x) { + assembly { + x := tload(0) + } + } + + function testDelegateCall() external returns (bool) { + this.set(5); + D d = new D{salt: hex"00"}(); + // Caller contract is the owner of the transient storage + (bool success, ) = address(d).delegatecall(abi.encodeCall(d.addOne, ())); + require(success); + require(this.get() == 6); + return true; + } + + function testCall() external returns (bool) { + this.set(5); + D d = new D{salt: hex"01"}(); + // Callee/Target contract is the owner of the transient storage + (bool success, ) = address(d).call(abi.encodeCall(d.addOne, ())); + require(success); + require(d.get() == 1); + return true; + } + + function tloadAllowedStaticCall() external returns (bool) { + this.set(5); + D d = new D{salt: hex"02"}(); + (bool success, bytes memory result) = address(d).staticcall(abi.encodeCall(d.get, ())); + require(success); + require(abi.decode(result, (uint)) == 0); + return true; + } + + function tstoreNotAllowedStaticCall() external returns (bool) { + D d = new D{salt: hex"03"}(); + (bool success, ) = address(d).staticcall(abi.encodeCall(d.addOne, ())); + require(!success); + return true; + } +} +// ==== +// EVMVersion: >=cancun +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testDelegateCall() -> true +// testCall() -> true +// tloadAllowedStaticCall() -> true +// tstoreNotAllowedStaticCall() -> true +// gas irOptimized: 98419720 +// gas irOptimized code: 19000 +// gas legacy: 98409086 +// gas legacy code: 30000 +// gas legacyOptimized: 98420962 +// gas legacyOptimized code: 17800 diff --git a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol index c2066ef8be1e..928a2a07bb69 100644 --- a/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol +++ b/test/libsolidity/semanticTests/inlineAssembly/transient_storage_selfdestruct.sol @@ -38,6 +38,7 @@ contract D { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor() -> // gas irOptimized: 127596 diff --git a/test/libsolidity/semanticTests/interface_inheritance_conversions.sol b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol index 62e1e4fba6dc..9a7ac401cbd6 100644 --- a/test/libsolidity/semanticTests/interface_inheritance_conversions.sol +++ b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol @@ -32,6 +32,8 @@ contract C { return (sb.parentFun(), sb.subBFun()); } } +// ==== +// compileToEOF: false // ---- // convertParent() -> 1 // gas irOptimized: 85524 diff --git a/test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol b/test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol new file mode 100644 index 000000000000..8a4c7d47e3f9 --- /dev/null +++ b/test/libsolidity/semanticTests/interface_inheritance_conversions_eof.sol @@ -0,0 +1,47 @@ +interface Parent { + function parentFun() external returns (uint256); +} + +interface SubA is Parent { + function subAFun() external returns (uint256); +} + +interface SubB is Parent { + function subBFun() external returns (uint256); +} + +contract Impl is SubA, SubB { + function parentFun() override external returns (uint256) { return 1; } + function subAFun() override external returns (uint256) { return 2; } + function subBFun() override external returns (uint256) { return 3; } +} + +contract C { + function convertParent() public returns (uint256) { + Parent p = new Impl(); + return p.parentFun(); + } + + function convertSubA() public returns (uint256, uint256) { + bytes32 s = 0x0000000000000000000000000000000000000000000000000000000000000001; + SubA sa = new Impl{salt: s}(); + return (sa.parentFun(), sa.subAFun()); + } + + function convertSubB() public returns (uint256, uint256) { + bytes32 s = 0x0000000000000000000000000000000000000000000000000000000000000002; + SubB sb = new Impl{salt: s}(); + return (sb.parentFun(), sb.subBFun()); + } +} +// ==== +// compileToEOF: false +// ---- +// convertParent() -> 1 +// gas irOptimized: 85524 +// convertSubA() -> 1, 2 +// gas irOptimized: 86155 +// gas legacy: 99047 +// convertSubB() -> 1, 3 +// gas irOptimized: 86098 +// gas legacy: 98981 diff --git a/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol index 1240a8251427..b99b63d14320 100644 --- a/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol +++ b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract.sol @@ -14,6 +14,8 @@ contract ClientReceipt { return other.getAddress(); } } +// ==== +// compileToEOF: false // ---- // constructor(), 2000 wei -> // gas irOptimized: 114353 diff --git a/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol new file mode 100644 index 000000000000..39b48fa66347 --- /dev/null +++ b/test/libsolidity/semanticTests/isoltestTesting/balance_other_contract_eof.sol @@ -0,0 +1,33 @@ +contract Other { + constructor() payable { + } + function getAddress() public returns (address) { + return address(this); + } +} +contract ClientReceipt { + Other other; + constructor() payable { + other = new Other{value:500}(); + } + function getAddress() public returns (address) { + return other.getAddress(); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// constructor(), 2000 wei -> +// gas irOptimized: 114353 +// gas irOptimized code: 58800 +// gas legacy: 118618 +// gas legacy code: 111400 +// gas legacyOptimized: 114067 +// gas legacyOptimized code: 59800 +// balance -> 1500 +// gas irOptimized: 191881 +// gas legacy: 235167 +// gas legacyOptimized: 180756 +// getAddress() -> 0xbeeb053868faf7a26a382b708a61fad0cfab5a48 +// balance: 0xbeeb053868faf7a26a382b708a61fad0cfab5a48 -> 500 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol index ec41099eecbb..231c0c531536 100644 --- a/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call.sol @@ -50,6 +50,8 @@ contract C { return -x; } } +// ==== +// compileToEOF: false // ---- // testMul(int32,int32): 42, 10 -> 420 // gas irOptimized: 102563 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol new file mode 100644 index 000000000000..bf27cebb3b73 --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_pure_external_call_eof.sol @@ -0,0 +1,68 @@ +type Int32 is int32; +using {add as +, unsub as -} for Int32 global; + +function add(Int32 x, Int32 y) pure returns (Int32) { + return loadAdder().mul(x, y); +} + +function unsub(Int32 x) pure returns (Int32) { + return loadAdder().inc(x); +} + +interface IAdder { + function mul(Int32, Int32) external pure returns (Int32); + function inc(Int32) external pure returns (Int32); +} + +contract Adder is IAdder { + function mul(Int32 x, Int32 y) external pure override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) * Int32.unwrap(y)); + } + + function inc(Int32 x) external pure override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) + 1); + } +} + +function storeAdder(IAdder adder) pure { + assembly { + // This test would also work without assembly if we could hard-code an address here. + mstore(0, adder) + } +} + +function loadAdder() pure returns (IAdder adder) { + assembly { + adder := mload(0) + } +} + +contract C { + function testMul(Int32 x, Int32 y) public returns (Int32) { + storeAdder(new Adder{salt: hex"00"}()); + + return x + y; + } + + function testInc(Int32 x) public returns (Int32) { + storeAdder(new Adder{salt: hex"01"}()); + + return -x; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testMul(int32,int32): 42, 10 -> 420 +// gas irOptimized: 102563 +// gas legacy: 56981 +// gas legacy code: 127000 +// gas legacyOptimized: 55163 +// gas legacyOptimized code: 68400 +// testInc(int32): 42 -> 43 +// gas irOptimized: 102386 +// gas legacy: 56239 +// gas legacy code: 127000 +// gas legacyOptimized: 54851 +// gas legacyOptimized code: 68400 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol index 7e70eb091bf6..ebd20f32b2ae 100644 --- a/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call.sol @@ -56,6 +56,8 @@ contract C { return -x; } } +// ==== +// compileToEOF: false // ---- // testMul(int32,int32): 42, 10 -> 420 // gas irOptimized: 102563 diff --git a/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol new file mode 100644 index 000000000000..bc78fb261a7e --- /dev/null +++ b/test/libsolidity/semanticTests/operators/userDefined/operator_making_view_external_call_eof.sol @@ -0,0 +1,74 @@ +type Int32 is int32; +using {add as +, unsub as -} for Int32 global; + +function add(Int32 x, Int32 y) pure returns (Int32) { + return loadAdder().mul(x, y); +} + +function unsub(Int32 x) pure returns (Int32) { + return loadAdder().inc(x); +} + +interface IAdderPure { + function mul(Int32, Int32) external pure returns (Int32); + function inc(Int32) external pure returns (Int32); +} + +interface IAdderView { + function mul(Int32, Int32) external view returns (Int32); + function inc(Int32) external view returns (Int32); +} + +contract Adder is IAdderView { + function mul(Int32 x, Int32 y) external view override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) * Int32.unwrap(y)); + } + + function inc(Int32 x) external view override returns (Int32) { + return Int32.wrap(Int32.unwrap(x) + 1); + } +} + +function storeAdder(IAdderView adder) pure { + assembly { + // This test would also work without assembly if we could hard-code an address here. + mstore(0, adder) + } +} + +function loadAdder() pure returns (IAdderPure adder) { + assembly { + // The adder we stored is view but we cheat by using a modified version with pure functions + adder := mload(0) + } +} + +contract C { + function testMul(Int32 x, Int32 y) public returns (Int32) { + storeAdder(new Adder{salt: hex"00"}()); + + return x + y; + } + + function testInc(Int32 x) public returns (Int32) { + storeAdder(new Adder{salt: hex"01"}()); + + return -x; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// testMul(int32,int32): 42, 10 -> 420 +// gas irOptimized: 102563 +// gas legacy: 56981 +// gas legacy code: 127000 +// gas legacyOptimized: 55163 +// gas legacyOptimized code: 68400 +// testInc(int32): 42 -> 43 +// gas irOptimized: 102386 +// gas legacy: 56239 +// gas legacy code: 127000 +// gas legacyOptimized: 54851 +// gas legacyOptimized code: 68400 diff --git a/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol b/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol index 110b1e50c724..66e5553fede4 100644 --- a/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol +++ b/test/libsolidity/semanticTests/revertStrings/called_contract_has_code.sol @@ -8,5 +8,6 @@ contract C { // ==== // EVMVersion: >=byzantium // revertStrings: debug +// compileToEOF: false // ---- // g() -> FAILURE, hex"08c379a0", 0x20, 37, "Target contract does not contain", " code" diff --git a/test/libsolidity/semanticTests/reverts/revert_return_area.sol b/test/libsolidity/semanticTests/reverts/revert_return_area.sol index 8ab4ca22722c..9aaa4f1b4a4a 100644 --- a/test/libsolidity/semanticTests/reverts/revert_return_area.sol +++ b/test/libsolidity/semanticTests/reverts/revert_return_area.sol @@ -14,5 +14,6 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // f() -> 0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol b/test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol new file mode 100644 index 000000000000..8d8e2b3279dd --- /dev/null +++ b/test/libsolidity/semanticTests/reverts/revert_return_area_eof.sol @@ -0,0 +1,21 @@ +contract C { + fallback() external { + revert("abc"); + } + + function f() public returns (uint s, uint r) { + address x = address(this); + assembly { + mstore(0, 7) + s := extcall(x, 0, 0, 0) + returndatacopy(0, 0, 32) + r := mload(0) + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0x01, 0x08c379a000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/salted_create/prediction_example.sol b/test/libsolidity/semanticTests/salted_create/prediction_example.sol index 7d0e3c8b0dbc..89bac53e9d41 100644 --- a/test/libsolidity/semanticTests/salted_create/prediction_example.sol +++ b/test/libsolidity/semanticTests/salted_create/prediction_example.sol @@ -5,14 +5,33 @@ contract D { } } +// TODO: this is horrible and hopefully avoided at the spec level +function adjustContractCodeForArgSize(bytes memory x, uint16 argSize) +{ + assembly { + let memPos := add(x, 32) + if eq(shr(232, mload(memPos)), 0xef0001) { + let numCodeSections := shr(240, mload(add(memPos, 7))) + let dataSectionSizeOffset := add(memPos, add(10, mul(numCodeSections, 2))) + let tmp := mload(dataSectionSizeOffset) + let dataSectionSize := shr(240, tmp) + dataSectionSize := add(dataSectionSize, argSize) + if gt(dataSectionSize, 0xFFFF) { revert(0,0) } + mstore(dataSectionSizeOffset, or(shr(16, shl(16, tmp)), shl(240, dataSectionSize))) + } + } +} + contract C { function createDSalted(bytes32 salt, uint arg) public { + bytes memory creationCode = type(D).creationCode; + adjustContractCodeForArgSize(creationCode, 32); address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( bytes1(0xff), address(this), salt, keccak256(abi.encodePacked( - type(D).creationCode, + creationCode, arg )) ))))); @@ -24,6 +43,7 @@ contract C { // ==== // EVMVersion: >=constantinople // compileViaYul: also +// compileToEOF: false // ---- // createDSalted(bytes32,uint256): 42, 64 -> // gas legacy: 78573 diff --git a/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol b/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol index 25d1935770d1..f92c5c3e2094 100644 --- a/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol +++ b/test/libsolidity/semanticTests/salted_create/salted_create_with_value.sol @@ -19,6 +19,7 @@ contract A { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // f(), 10 ether -> 3007, 3008, 3009 // gas irOptimized: 187022 diff --git a/test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol b/test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol new file mode 100644 index 000000000000..c7e28e6884d5 --- /dev/null +++ b/test/libsolidity/semanticTests/salted_create/salted_create_with_value_eof.sol @@ -0,0 +1,31 @@ +contract B +{ + uint x; + function getBalance() public view returns (uint) { + return address(this).balance * 1000 + x; + } + constructor(uint _x) payable { + x = _x; + } +} + +contract A { + function f() public payable returns (uint, uint, uint) { + B x = new B{salt: "abc0", value: 3}(7); + B y = new B{value: 3, salt: "abc1"}(8); + B z = new B{salt: "abc2", value: 3}(9); + return (x.getBalance(), y.getBalance(), z.getBalance()); + } +} +// ==== +// EVMVersion: >=constantinople +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f(), 10 ether -> 3007, 3008, 3009 +// gas irOptimized: 187022 +// gas irOptimized code: 67200 +// gas legacy: 190863 +// gas legacy code: 190200 +// gas legacyOptimized: 187258 +// gas legacyOptimized code: 92400 diff --git a/test/libsolidity/semanticTests/shanghai/evmone_support.sol b/test/libsolidity/semanticTests/shanghai/evmone_support.sol index 359dc516ffcd..b2fa6a69eaa0 100644 --- a/test/libsolidity/semanticTests/shanghai/evmone_support.sol +++ b/test/libsolidity/semanticTests/shanghai/evmone_support.sol @@ -26,6 +26,7 @@ contract Test { // ==== // compileViaYul: also // EVMVersion: >=shanghai +// compileToEOF: false // ---- // bytecode() -> 0x20, 4, 0x60205ff300000000000000000000000000000000000000000000000000000000 // isPush0Supported() -> true diff --git a/test/libsolidity/semanticTests/state/gasleft.sol b/test/libsolidity/semanticTests/state/gasleft.sol index 6ce623ce81ea..c6e29504fbeb 100644 --- a/test/libsolidity/semanticTests/state/gasleft.sol +++ b/test/libsolidity/semanticTests/state/gasleft.sol @@ -3,6 +3,8 @@ contract C { return gasleft() > 0; } } +// ==== +// compileToEOF: false // ---- // f() -> true // f() -> true diff --git a/test/libsolidity/semanticTests/tryCatch/create.sol b/test/libsolidity/semanticTests/tryCatch/create.sol index 43d0f22f6506..399bdc645cc4 100644 --- a/test/libsolidity/semanticTests/tryCatch/create.sol +++ b/test/libsolidity/semanticTests/tryCatch/create.sol @@ -27,6 +27,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileToEOF: false // ---- // f() -> 0, 0, 96, 13, "test message." // g() -> 0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0, 96, 7, "success" diff --git a/test/libsolidity/semanticTests/tryCatch/create_eof.sol b/test/libsolidity/semanticTests/tryCatch/create_eof.sol new file mode 100644 index 000000000000..9735fbcfe101 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/create_eof.sol @@ -0,0 +1,34 @@ +contract Reverts { + constructor(uint) { revert("test message."); } +} +contract Succeeds { + constructor(uint) { } +} + +contract C { + function f() public returns (Reverts x, uint, string memory txt) { + uint i = 3; + try new Reverts(i) returns (Reverts r) { + x = r; + txt = "success"; + } catch Error(string memory s) { + txt = s; + } + } + function g() public returns (Succeeds x, uint, string memory txt) { + uint i = 8; + try new Succeeds(i) returns (Succeeds r) { + x = r; + txt = "success"; + } catch Error(string memory s) { + txt = s; + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0, 0, 96, 13, "test message." +// g() -> 0xD1416F618F37EB929020DF731D1A78F392766843, 0, 96, 7, "success" diff --git a/test/libsolidity/semanticTests/tryCatch/return_function.sol b/test/libsolidity/semanticTests/tryCatch/return_function.sol index 6aac30fe4617..deb45122ae22 100644 --- a/test/libsolidity/semanticTests/tryCatch/return_function.sol +++ b/test/libsolidity/semanticTests/tryCatch/return_function.sol @@ -13,5 +13,7 @@ contract C { } function fun() public pure {} } +// ==== +// compileToEOF: false // ---- // f() -> 0x1, 0xc06afe3a8444fc0004668591e8306bfb9968e79e946644cd0000000000000000, 9 diff --git a/test/libsolidity/semanticTests/tryCatch/return_function_eof.sol b/test/libsolidity/semanticTests/tryCatch/return_function_eof.sol new file mode 100644 index 000000000000..397942e499a6 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/return_function_eof.sol @@ -0,0 +1,20 @@ +contract C { + function g() public returns (uint a, function() external h, uint b) { + a = 1; + h = this.fun; + b = 9; + } + function f() public returns (uint, function() external, uint) { + // Note that the function type uses two stack slots. + try this.g() returns (uint a, function() external h, uint b) { + return (a, h, b); + } catch { + } + } + function fun() public pure {} +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0x1, 0x67831474d284bf49471795e23b524e80a6b386a0946644cd0000000000000000, 9 diff --git a/test/libsolidity/semanticTests/various/address_code.sol b/test/libsolidity/semanticTests/various/address_code.sol index 549512eb3e44..fa8eff9c3819 100644 --- a/test/libsolidity/semanticTests/various/address_code.sol +++ b/test/libsolidity/semanticTests/various/address_code.sol @@ -12,6 +12,8 @@ contract C { function g() public view returns (uint) { return address(0).code.length; } function h() public view returns (uint) { return address(1).code.length; } } +// ==== +// compileToEOF: false // ---- // constructor() -> // gas irOptimized: 70760 diff --git a/test/libsolidity/semanticTests/various/address_code_complex.sol b/test/libsolidity/semanticTests/various/address_code_complex.sol index f2d21c9069b3..c9e185890c93 100644 --- a/test/libsolidity/semanticTests/various/address_code_complex.sol +++ b/test/libsolidity/semanticTests/various/address_code_complex.sol @@ -12,6 +12,8 @@ contract C { function f() public returns (bytes memory) { return address(new A()).code; } function g() public returns (uint) { return address(new A()).code.length; } } +// ==== +// compileToEOF: false // ---- // f() -> 0x20, 0x20, 0x48aa5566000000 // g() -> 0x20 diff --git a/test/libsolidity/semanticTests/various/code_access_content.sol b/test/libsolidity/semanticTests/various/code_access_content.sol index 22ceff337f89..b11807661479 100644 --- a/test/libsolidity/semanticTests/various/code_access_content.sol +++ b/test/libsolidity/semanticTests/various/code_access_content.sol @@ -36,6 +36,8 @@ contract C { return true; } } +// ==== +// compileToEOF: false // ---- // testRuntime() -> true // gas legacy: 76575 diff --git a/test/libsolidity/semanticTests/various/code_access_create.sol b/test/libsolidity/semanticTests/various/code_access_create.sol index f5f0b8644423..e17446a10dbf 100644 --- a/test/libsolidity/semanticTests/various/code_access_create.sol +++ b/test/libsolidity/semanticTests/various/code_access_create.sol @@ -21,6 +21,8 @@ contract C { return d.f(); } } +// ==== +// compileToEOF: false // ---- // test() -> 7 // gas legacy: 76647 diff --git a/test/libsolidity/semanticTests/various/code_access_padding.sol b/test/libsolidity/semanticTests/various/code_access_padding.sol index 831d4bde4ab4..ebb6b3a06528 100644 --- a/test/libsolidity/semanticTests/various/code_access_padding.sol +++ b/test/libsolidity/semanticTests/various/code_access_padding.sol @@ -14,5 +14,7 @@ contract C { } } } +// ==== +// compileToEOF: false // ---- // diff() -> 0 # This checks that the allocation function pads to multiples of 32 bytes # diff --git a/test/libsolidity/semanticTests/various/code_access_runtime.sol b/test/libsolidity/semanticTests/various/code_access_runtime.sol index 10d7c1852e95..6406acd4c41c 100644 --- a/test/libsolidity/semanticTests/various/code_access_runtime.sol +++ b/test/libsolidity/semanticTests/various/code_access_runtime.sol @@ -21,6 +21,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // test() -> 42 // gas legacy: 76034 diff --git a/test/libsolidity/semanticTests/various/code_length.sol b/test/libsolidity/semanticTests/various/code_length.sol index 844a0f65706f..5f331724be44 100644 --- a/test/libsolidity/semanticTests/various/code_length.sol +++ b/test/libsolidity/semanticTests/various/code_length.sol @@ -57,6 +57,8 @@ contract C { } } +// ==== +// compileToEOF: false // ---- // constructor() // gas legacy: 66989 diff --git a/test/libsolidity/semanticTests/various/code_length_contract_member.sol b/test/libsolidity/semanticTests/various/code_length_contract_member.sol index ff883139a46e..385e4b500171 100644 --- a/test/libsolidity/semanticTests/various/code_length_contract_member.sol +++ b/test/libsolidity/semanticTests/various/code_length_contract_member.sol @@ -11,5 +11,7 @@ contract C { return (s.code.length, s.another.length, address(this).code.length > 50); } } +// ==== +// compileToEOF: false // ---- // f() -> 0x20, 0x20, true diff --git a/test/libsolidity/semanticTests/various/codehash.sol b/test/libsolidity/semanticTests/various/codehash.sol index fa7dab9dab9f..bf373362ba72 100644 --- a/test/libsolidity/semanticTests/various/codehash.sol +++ b/test/libsolidity/semanticTests/various/codehash.sol @@ -13,6 +13,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // f() -> 0x0 // g() -> 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 diff --git a/test/libsolidity/semanticTests/various/codehash_assembly.sol b/test/libsolidity/semanticTests/various/codehash_assembly.sol index fe2210fa107a..f0a73142fe93 100644 --- a/test/libsolidity/semanticTests/various/codehash_assembly.sol +++ b/test/libsolidity/semanticTests/various/codehash_assembly.sol @@ -17,6 +17,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // f() -> 0 // g() -> 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 diff --git a/test/libsolidity/semanticTests/various/create_calldata.sol b/test/libsolidity/semanticTests/various/create_calldata.sol index a5f61d2ddd67..a9d2f640e3fd 100644 --- a/test/libsolidity/semanticTests/various/create_calldata.sol +++ b/test/libsolidity/semanticTests/various/create_calldata.sol @@ -6,6 +6,8 @@ contract C { assert(msg.data.length == 0); } } +// ==== +// compileToEOF: false // ---- // constructor(): 42 -> // gas irOptimized: 68239 diff --git a/test/libsolidity/semanticTests/various/create_random.sol b/test/libsolidity/semanticTests/various/create_random.sol index 676ec32a89ae..601cea718fc4 100644 --- a/test/libsolidity/semanticTests/various/create_random.sol +++ b/test/libsolidity/semanticTests/various/create_random.sol @@ -5,8 +5,8 @@ contract C { function testRunner() external returns (address a1, address a2) { assembly { - // This is `return(0, 1)`. We are using a simplified/fixed initcode to avoid - // instability due to metadata changes. + // This is `return(0, 1)`. We are using a simplified/fixed initcode to avoid + // instability due to metadata changes. let initcode := hex"60016000f3" mstore(0, initcode) @@ -23,7 +23,7 @@ contract C { function calculateCreate(address from, uint256 nonce) private pure returns (address) { assert(nonce <= 127); bytes memory data = - bytes.concat(hex"d694", bytes20(uint160(from)), nonce == 0 ? bytes1(hex"80") : bytes1(uint8(nonce))); + bytes.concat(hex"d694", bytes20(uint160(from)), nonce == 0 ? bytes1(hex"80") : bytes1(uint8(nonce))); return address(uint160(uint256(keccak256(data)))); // Take the lower 160-bits } @@ -33,6 +33,7 @@ contract C { } // ==== // EVMVersion: >=constantinople +// compileToEOF: false // ---- // addr() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e // testRunner() -> 0x137aa4dfc0911524504fcd4d98501f179bc13b4a, 0x2c1c30623ddd93e0b765a6caaca0c859eeb0644d diff --git a/test/libsolidity/semanticTests/various/gasleft_decrease.sol b/test/libsolidity/semanticTests/various/gasleft_decrease.sol index ab7302743f64..bffca20b3dd4 100644 --- a/test/libsolidity/semanticTests/various/gasleft_decrease.sol +++ b/test/libsolidity/semanticTests/various/gasleft_decrease.sol @@ -14,6 +14,8 @@ contract C { return true; } } +// ==== +// compileToEOF: false // ---- // f() -> true // g() -> true diff --git a/test/libsolidity/semanticTests/various/many_subassemblies.sol b/test/libsolidity/semanticTests/various/many_subassemblies.sol index b270c7006694..a4e9d96bdd9e 100644 --- a/test/libsolidity/semanticTests/various/many_subassemblies.sol +++ b/test/libsolidity/semanticTests/various/many_subassemblies.sol @@ -28,6 +28,8 @@ contract D { new C10(); } } +// ==== +// compileToEOF: false // ---- // run() -> // gas irOptimized: 374934 diff --git a/test/libsolidity/semanticTests/various/many_subassemblies_eof.sol b/test/libsolidity/semanticTests/various/many_subassemblies_eof.sol new file mode 100644 index 000000000000..95c797d0230f --- /dev/null +++ b/test/libsolidity/semanticTests/various/many_subassemblies_eof.sol @@ -0,0 +1,41 @@ +contract C0 {} +contract C1 {} +contract C2 {} +contract C3 {} +contract C4 {} +contract C5 {} +contract C6 {} +contract C7 {} +contract C8 {} +contract C9 {} +contract C10 {} + +contract D { + function run() public { + // This is primarily meant to test assembly import via --import-asm-json. + // The exported JSON will fail the reimport unless the subassembly indices are parsed + // correctly - as hex numbers. + new C0{salt: hex"00"}(); + new C1{salt: hex"01"}(); + new C2{salt: hex"02"}(); + new C3{salt: hex"03"}(); + new C4{salt: hex"04"}(); + new C5{salt: hex"05"}(); + new C6{salt: hex"06"}(); + new C7{salt: hex"07"}(); + new C8{salt: hex"08"}(); + new C9{salt: hex"09"}(); + new C10{salt: hex"0a"}(); + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// run() -> +// gas irOptimized: 374934 +// gas irOptimized code: 6600 +// gas legacy: 375119 +// gas legacy code: 17600 +// gas legacyOptimized: 375119 +// gas legacyOptimized code: 17600 diff --git a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol index 4cb5c8c713bf..da413b0becd3 100644 --- a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol +++ b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun.sol @@ -62,6 +62,7 @@ contract D { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 67028 diff --git a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol index a582649196c3..407cf54150f9 100644 --- a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol +++ b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_multiple_beneficiaries.sol @@ -33,6 +33,7 @@ contract D { } // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor(), 2 ether -> // gas irOptimized: 108104 diff --git a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol index dd550d31a0cf..1e5483deb814 100644 --- a/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol +++ b/test/libsolidity/semanticTests/various/selfdestruct_post_cancun_redeploy.sol @@ -80,6 +80,7 @@ contract D { // ==== // EVMVersion: >=cancun +// compileToEOF: false // ---- // constructor(), 1 ether -> // gas irOptimized: 132974 diff --git a/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol b/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol index 843376baa553..03793fcd23d1 100644 --- a/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol +++ b/test/libsolidity/semanticTests/viaYul/conversion/function_cast.sol @@ -15,7 +15,10 @@ contract C { b = this.f; } } +// ==== +// compileToEOF: false // ---- // f(uint256): 2 -> 4 // h(uint256): 2 -> 5 // t() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79eb3de648b0000000000000000, 0xc06afe3a8444fc0004668591e8306bfb9968e79eb3de648b0000000000000000 + diff --git a/test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol b/test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol new file mode 100644 index 000000000000..391334123782 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/conversion/function_cast_eof.sol @@ -0,0 +1,25 @@ +contract C { + function f(uint x) public pure returns (uint) { + return 2 * x; + } + function g() public view returns (function (uint) external returns (uint)) { + return this.f; + } + function h(uint x) public returns (uint) { + return this.g()(x) + 1; + } + function t() external view returns ( + function(uint) external returns (uint) a, + function(uint) external view returns (uint) b) { + a = this.f; + b = this.f; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f(uint256): 2 -> 4 +// h(uint256): 2 -> 5 +// t() -> 0x86e73fe93c23a38c4bdba9e4b9cc0ddcc0fe293eb3de648b0000000000000000, 0x86e73fe93c23a38c4bdba9e4b9cc0ddcc0fe293eb3de648b0000000000000000 + diff --git a/test/libsolidity/semanticTests/viaYul/function_address.sol b/test/libsolidity/semanticTests/viaYul/function_address.sol index 0c1c58dd821d..cffb34dd9136 100644 --- a/test/libsolidity/semanticTests/viaYul/function_address.sol +++ b/test/libsolidity/semanticTests/viaYul/function_address.sol @@ -9,6 +9,8 @@ contract C { return a.address; } } +// ==== +// compileToEOF: false // ---- // f() -> 0xc06afe3a8444fc0004668591e8306bfb9968e79e // g() -> true diff --git a/test/libsolidity/semanticTests/viaYul/function_address_eof.sol b/test/libsolidity/semanticTests/viaYul/function_address_eof.sol new file mode 100644 index 000000000000..c3ea05b7febc --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/function_address_eof.sol @@ -0,0 +1,18 @@ +contract C { + function f() external returns (address) { + return this.f.address; + } + function g() external returns (bool) { + return this.f.address == address(this); + } + function h(function() external a) public returns (address) { + return a.address; + } +} +// ==== +// compileToEOF: true +// EVMVersion: >=prague +// ---- +// f() -> 0x1a7b7ed5ae36cd8c4f6da702d8409d6cf9bd1f6d +// g() -> true +// h(function): left(0x1122334400112233445566778899AABBCCDDEEFF42424242) -> 0x1122334400112233445566778899AABBCCDDEEFF