/*
	This file is part of solidity.

	solidity is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	solidity is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
 * @author Christian <c@ethdev.com>
 * @date 2014
 * Tests for the Solidity optimizer.
 */

#include <test/Metadata.h>
#include <test/libsolidity/SolidityExecutionFramework.h>

#include <libevmasm/Instruction.h>
#include <libevmasm/Disassemble.h>

#include <boost/test/unit_test.hpp>

#include <chrono>
#include <string>
#include <tuple>
#include <memory>
#include <limits>

using namespace std;
using namespace solidity::util;
using namespace solidity::evmasm;
using namespace solidity::test;

namespace solidity::frontend::test
{

class OptimizerTestFramework: public SolidityExecutionFramework
{
public:
	bytes const& compileAndRunWithOptimizer(
		std::string const& _sourceCode,
		u256 const& _value = 0,
		std::string const& _contractName = "",
		bool const _optimize = true,
		unsigned const _optimizeRuns = 200
	)
	{
		OptimiserSettings previousSettings = std::move(m_optimiserSettings);
		// This uses "none" / "full" while most other test frameworks use
		// "minimal" / "standard".
		m_optimiserSettings = _optimize ? OptimiserSettings::full() : OptimiserSettings::none();
		m_optimiserSettings.expectedExecutionsPerDeployment = _optimizeRuns;
		bytes const& ret = compileAndRun(_sourceCode, _value, _contractName);
		m_optimiserSettings = std::move(previousSettings);
		return ret;
	}

	/// Compiles the source code with and without optimizing.
	void compileBothVersions(
		std::string const& _sourceCode,
		u256 const& _value = 0,
		std::string const& _contractName = "",
		unsigned const _optimizeRuns = 200
	)
	{
		m_nonOptimizedBytecode = compileAndRunWithOptimizer("pragma solidity >=0.0;\n" + _sourceCode, _value, _contractName, false, _optimizeRuns);
		m_nonOptimizedContract = m_contractAddress;
		m_optimizedBytecode = compileAndRunWithOptimizer("pragma solidity >=0.0;\n" + _sourceCode, _value, _contractName, true, _optimizeRuns);
		size_t nonOptimizedSize = numInstructions(m_nonOptimizedBytecode);
		size_t optimizedSize = numInstructions(m_optimizedBytecode);
		BOOST_CHECK_MESSAGE(
			_optimizeRuns < 50 || optimizedSize < nonOptimizedSize,
			string("Optimizer did not reduce bytecode size. Non-optimized size: ") +
			to_string(nonOptimizedSize) + " - optimized size: " +
			to_string(optimizedSize)
		);
		m_optimizedContract = m_contractAddress;
	}

	template <class... Args>
	void compareVersions(std::string _sig, Args const&... _arguments)
	{
		m_contractAddress = m_nonOptimizedContract;
		bytes nonOptimizedOutput = callContractFunction(_sig, _arguments...);
		m_gasUsedNonOptimized = m_gasUsed;
		m_contractAddress = m_optimizedContract;
		bytes optimizedOutput = callContractFunction(_sig, _arguments...);
		m_gasUsedOptimized = m_gasUsed;
		BOOST_CHECK_MESSAGE(!optimizedOutput.empty(), "No optimized output for " + _sig);
		BOOST_CHECK_MESSAGE(!nonOptimizedOutput.empty(), "No un-optimized output for " + _sig);
		BOOST_CHECK_MESSAGE(nonOptimizedOutput == optimizedOutput, "Computed values do not match."
							"\nNon-Optimized: " + util::toHex(nonOptimizedOutput) +
							"\nOptimized:     " + util::toHex(optimizedOutput));
	}

	/// @returns the number of instructions in the given bytecode, not taking the metadata hash
	/// into account.
	size_t numInstructions(bytes const& _bytecode, std::optional<Instruction> _which = std::optional<Instruction>{})
	{
		bytes realCode = bytecodeSansMetadata(_bytecode);
		BOOST_REQUIRE_MESSAGE(!realCode.empty(), "Invalid or missing metadata in bytecode.");
		size_t instructions = 0;
		evmasm::eachInstruction(realCode, m_evmVersion, [&](Instruction _instr, u256 const&) {
			if (!_which || *_which == _instr)
				instructions++;
		});
		return instructions;
	}

protected:
	u256 m_gasUsedOptimized;
	u256 m_gasUsedNonOptimized;
	bytes m_nonOptimizedBytecode;
	bytes m_optimizedBytecode;
	h160 m_optimizedContract;
	h160 m_nonOptimizedContract;
};

BOOST_FIXTURE_TEST_SUITE(SolidityOptimizer, OptimizerTestFramework)

BOOST_AUTO_TEST_CASE(smoke_test)
{
	char const* sourceCode = R"(
		contract test {
			function f(uint a) public returns (uint b) {
				return a;
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256)", u256(7));
}

BOOST_AUTO_TEST_CASE(identities)
{
	char const* sourceCode = R"(
		contract test {
			function f(int a) public returns (int b) {
				return int(0) | (int(1) * (int(0) ^ (0 + a)));
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(int256)", u256(0x12334664));
}

BOOST_AUTO_TEST_CASE(unused_expressions)
{
	char const* sourceCode = R"(
		contract test {
			uint data;
			function f() public returns (uint a, uint b) {
				10 + 20;
				data;
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f()");
}

BOOST_AUTO_TEST_CASE(constant_folding_both_sides)
{
	// if constants involving the same associative and commutative operator are applied from both
	// sides, the operator should be applied only once, because the expression compiler pushes
	// literals as late as possible
	char const* sourceCode = R"(
		contract test {
			function f(uint x) public returns (uint y) {
				return 98 ^ (7 * ((1 | (x | 1000)) * 40) ^ 102);
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256)", 7);
}

BOOST_AUTO_TEST_CASE(storage_access)
{
	char const* sourceCode = R"(
		contract test {
			uint8[40] data;
			function f(uint x) public returns (uint y) {
				data[2] = data[7] = uint8(x);
				data[4] = data[2] * 10 + data[3];
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256)", 7);
}

BOOST_AUTO_TEST_CASE(array_copy)
{
	char const* sourceCode = R"(
		contract test {
			bytes2[] data1;
			bytes5[] data2;
			function f(uint x) public returns (uint l, uint y) {
				for (uint i = 0; i < msg.data.length; i++)
					data1.push();
				for (uint i = 0; i < msg.data.length; ++i)
					data1[i] = msg.data[i];
				data2 = data1;
				l = data2.length;
				y = uint(uint40(data2[x]));
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256)", 0);
	compareVersions("f(uint256)", 10);
	compareVersions("f(uint256)", 35);
}

BOOST_AUTO_TEST_CASE(function_calls)
{
	char const* sourceCode = R"(
		contract test {
			function f1(uint x) public returns (uint) { unchecked { return x*x; } }
			function f(uint x) public returns (uint) { unchecked { return f1(7+x) - this.f1(x**9); } }
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256)", 0);
	compareVersions("f(uint256)", 10);
	compareVersions("f(uint256)", 36);
}

BOOST_AUTO_TEST_CASE(storage_write_in_loops)
{
	char const* sourceCode = R"(
		contract test {
			uint d;
			function f(uint a) public returns (uint r) {
				uint x = d;
				for (uint i = 1; i < a * a; i++) {
					r = d;
					d = i;
				}

			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256)", 0);
	compareVersions("f(uint256)", 10);
	compareVersions("f(uint256)", 36);
}

// Test disabled with https://github.com/ethereum/solidity/pull/762
// Information in joining branches is not retained anymore.
BOOST_AUTO_TEST_CASE(retain_information_in_branches)
{
	// This tests that the optimizer knows that we already have "z == keccak256(abi.encodePacked(y))" inside both branches.
	char const* sourceCode = R"(
		contract c {
			bytes32 d;
			uint a;
			function f(uint x, bytes32 y) public returns (uint r_a, bytes32 r_d) {
				bytes32 z = keccak256(abi.encodePacked(y));
				if (x > 8) {
					z = keccak256(abi.encodePacked(y));
					a = x;
				} else {
					z = keccak256(abi.encodePacked(y));
					a = x;
				}
				r_a = a;
				r_d = d;
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256,bytes32)", 0, "abc");
	compareVersions("f(uint256,bytes32)", 8, "def");
	compareVersions("f(uint256,bytes32)", 10, "ghi");

	bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "c", true);
	size_t numSHA3s = 0;
	eachInstruction(optimizedBytecode, m_evmVersion, [&](Instruction _instr, u256 const&) {
		if (_instr == Instruction::KECCAK256)
			numSHA3s++;
	});
// TEST DISABLED - OPTIMIZER IS NOT EFFECTIVE ON THIS ONE ANYMORE
//	BOOST_CHECK_EQUAL(1, numSHA3s);
}

BOOST_AUTO_TEST_CASE(store_tags_as_unions)
{
	// This calls the same function from two sources and both calls have a certain Keccak-256 on
	// the stack at the same position.
	// Without storing tags as unions, the return from the shared function would not know where to
	// jump and thus all jumpdests are forced to clear their state and we do not know about the
	// sha3 anymore.
	// Note that, for now, this only works if the functions have the same number of return
	// parameters since otherwise, the return jump addresses are at different stack positions
	// which triggers the "unknown jump target" situation.
	char const* sourceCode = R"(
		contract test {
			bytes32 data;
			function f(uint x, bytes32 y) external returns (uint r_a, bytes32 r_d) {
				r_d = keccak256(abi.encodePacked(y));
				shared(y);
				r_d = keccak256(abi.encodePacked(y));
				r_a = 5;
			}
			function g(uint x, bytes32 y) external returns (uint r_a, bytes32 r_d) {
				r_d = keccak256(abi.encodePacked(y));
				shared(y);
				r_d = bytes32(uint(keccak256(abi.encodePacked(y))) + 2);
				r_a = 7;
			}
			function shared(bytes32 y) internal {
				data = keccak256(abi.encodePacked(y));
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256,bytes32)", 7, "abc");

	bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "test", true);
	size_t numSHA3s = 0;
	eachInstruction(optimizedBytecode, m_evmVersion, [&](Instruction _instr, u256 const&) {
		if (_instr == Instruction::KECCAK256)
			numSHA3s++;
	});
// TEST DISABLED UNTIL 93693404 IS IMPLEMENTED
//	BOOST_CHECK_EQUAL(2, numSHA3s);
}

BOOST_AUTO_TEST_CASE(incorrect_storage_access_bug)
{
	// This bug appeared because a Keccak-256 operation with too low sequence number was used,
	// resulting in memory not being rewritten before the Keccak-256. The fix was to
	// take the max of the min sequence numbers when merging the states.
	char const* sourceCode = R"(
		contract C
		{
			mapping(uint => uint) data;
			function f() public returns (uint)
			{
				if (data[block.timestamp] == 0)
					data[type(uint).max - 6] = 5;
				return data[block.timestamp];
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f()");
}

BOOST_AUTO_TEST_CASE(sequence_number_for_calls)
{
	// This is a test for a bug that was present because we did not increment the sequence
	// number for CALLs - CALLs can read and write from memory (and DELEGATECALLs can do the same
	// to storage), so the sequence number should be incremented.
	char const* sourceCode = R"(
		contract test {
			function f(string memory a, string memory b) public returns (bool) { return sha256(bytes(a)) == sha256(bytes(b)); }
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(string,string)", 0x40, 0x80, 3, "abc", 3, "def");
}

BOOST_AUTO_TEST_CASE(computing_constants)
{
	char const* sourceCode = R"(
		contract C {
			uint m_a;
			uint m_b;
			uint m_c;
			uint m_d;
			constructor() {
				set();
			}
			function set() public returns (uint) {
				m_a = 0x77abc0000000000000000000000000000000000000000000000000000000001;
				m_b = 0x817416927846239487123469187231298734162934871263941234127518276;
				g();
				return 1;
			}
			function g() public {
				m_b = 0x817416927846239487123469187231298734162934871263941234127518276;
				m_c = 0x817416927846239487123469187231298734162934871263941234127518276;
				h();
			}
			function h() public {
				m_d = 0xff05694900000000000000000000000000000000000000000000000000000000;
			}
			function get() public returns (uint ra, uint rb, uint rc, uint rd) {
				ra = m_a;
				rb = m_b;
				rc = m_c;
				rd = m_d;
			}
		}
	)";
	compileBothVersions(sourceCode, 0, "C", 1);
	compareVersions("get()");
	compareVersions("set()");
	compareVersions("get()");

	bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "C", true, 1);
	bytes complicatedConstant = toBigEndian(u256("0x817416927846239487123469187231298734162934871263941234127518276"));
	unsigned occurrences = 0;
	for (auto iter = optimizedBytecode.cbegin(); iter < optimizedBytecode.cend(); ++occurrences)
	{
		iter = search(iter, optimizedBytecode.cend(), complicatedConstant.cbegin(), complicatedConstant.cend());
		if (iter < optimizedBytecode.cend())
			++iter;
	}
	BOOST_CHECK_EQUAL(2, occurrences);

	bytes constantWithZeros = toBigEndian(u256("0x77abc0000000000000000000000000000000000000000000000000000000001"));
	BOOST_CHECK(search(
		optimizedBytecode.cbegin(),
		optimizedBytecode.cend(),
		constantWithZeros.cbegin(),
		constantWithZeros.cend()
	) == optimizedBytecode.cend());
}


BOOST_AUTO_TEST_CASE(constant_optimization_early_exit)
{
	// This tests that the constant optimizer does not try to find the best representation
	// indefinitely but instead stops after some number of iterations.
	char const* sourceCode = R"(
	contract HexEncoding {
		function hexEncodeTest(address addr) public returns (bytes32 ret) {
			uint x = uint(uint160(addr)) / 2**32;

			// Nibble interleave
			x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
			x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
			x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
			x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
			x = (x | (x * 2** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
			x = (x | (x * 2** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;

			// Hex encode
			uint h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
			uint i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
			uint j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
			x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;

			// Store and load next batch
			assembly {
				mstore(0, x)
			}
			x = uint160(addr) * 2**96;

			// Nibble interleave
			x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
			x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
			x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
			x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
			x = (x | (x * 2** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
			x = (x | (x * 2** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;

			// Hex encode
			h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
			i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
			j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
			x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;

			// Store and hash
			assembly {
				mstore(32, x)
				ret := keccak256(0, 40)
			}
		}
	}
	)";
	auto start = std::chrono::steady_clock::now();
	compileBothVersions(sourceCode);
	double duration = std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
	// Since run time on an ASan build is not really realistic, we disable this test for those builds.
	size_t maxDuration = 20;
#if !defined(__SANITIZE_ADDRESS__) && defined(__has_feature)
#if __has_feature(address_sanitizer)
#define __SANITIZE_ADDRESS__ 1
#endif
#endif
#if __SANITIZE_ADDRESS__
	maxDuration = numeric_limits<size_t>::max();
	BOOST_TEST_MESSAGE("Disabled constant optimizer run time check for address sanitizer build.");
#endif
	BOOST_CHECK_MESSAGE(duration <= double(maxDuration), "Compilation of constants took longer than 20 seconds.");
	compareVersions("hexEncodeTest(address)", u256(0x123456789));
}

BOOST_AUTO_TEST_CASE(inconsistency)
{
	// This is a test of a bug in the optimizer.
	char const* sourceCode = R"(
		contract Inconsistency {
			struct Value {
				uint badnum;
				uint number;
			}

			struct Container {
				uint[] valueIndices;
				Value[] values;
			}

			Container[] containers;
			uint[] valueIndices;
			uint INDEX_ZERO = 0;
			uint  debug;

			// Called with params: containerIndex=0, valueIndex=0
			function levelIII(uint containerIndex, uint valueIndex) private {
				Container storage container = containers[containerIndex];
				Value storage value = container.values[valueIndex];
				debug = container.valueIndices[value.number];
			}
			function levelII() private {
				for (uint i = 0; i < valueIndices.length; i++) {
					levelIII(INDEX_ZERO, valueIndices[i]);
				}
			}

			function trigger() public returns (uint) {
				Container storage container = containers.push();

				container.values.push(Value({
					badnum: 9000,
					number: 0
				}));

				container.valueIndices.push();
				valueIndices.push();

				levelII();
				return debug;
			}

			function DoNotCallButDoNotDelete() public {
				levelII();
				levelIII(1, 2);
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("trigger()");
}

BOOST_AUTO_TEST_CASE(dead_code_elimination_across_assemblies)
{
	// This tests that a runtime-function that is stored in storage in the constructor
	// is not removed as part of dead code elimination.
	char const* sourceCode = R"(
		contract DCE {
			function () internal returns (uint) stored;
			constructor() {
				stored = f;
			}
			function f() internal returns (uint) { return 7; }
			function test() public returns (uint) { return stored(); }
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("test()");
}

BOOST_AUTO_TEST_CASE(invalid_state_at_control_flow_join)
{
	char const* sourceCode = R"(
		contract Test {
			uint256 public totalSupply = 100;
			function f() public returns (uint r) {
				if (false)
					r = totalSupply;
				totalSupply -= 10;
			}
			function test() public returns (uint) {
				f();
				return this.totalSupply();
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("test()");
}

BOOST_AUTO_TEST_CASE(init_empty_dynamic_arrays)
{
	// This is not so much an optimizer test, but rather a test
	// that allocating empty arrays is implemented efficiently.
	// In particular, initializing a dynamic memory array does
	// not use any memory.
	char const* sourceCode = R"(
		contract Test {
			function f() public pure returns (uint r) {
				uint[][] memory x = new uint[][](20000);
				return x.length;
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f()");
	BOOST_CHECK_LE(m_gasUsedNonOptimized, 1900000);
	BOOST_CHECK_LE(1600000, m_gasUsedNonOptimized);
}

BOOST_AUTO_TEST_CASE(optimise_multi_stores)
{
	char const* sourceCode = R"(
		contract Test {
			struct S { uint16 a; uint16 b; uint16[3] c; uint[] dyn; }
			uint padding;
			S[] s;
			function f() public returns (uint16, uint16, uint16[3] memory, uint) {
				uint16[3] memory c;
				c[0] = 7;
				c[1] = 8;
				c[2] = 9;
				s.push(S(1, 2, c, new uint[](4)));
				return (s[0].a, s[0].b, s[0].c, s[0].dyn[2]);
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f()");
	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::SSTORE), 8);
	BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::SSTORE), 7);
}

BOOST_AUTO_TEST_CASE(optimise_constant_to_codecopy)
{
	char const* sourceCode = R"(
		contract C {
			// We use the state variable so that the functions won't be deemed identical
			// and be optimised out to the same implementation.
			uint a;
			function f() public returns (uint) {
				a = 1;
				// This cannot be represented well with the `CalculateMethod`,
				// hence the decision will be between `LiteralMethod` and `CopyMethod`.
				return 0x1234123412431234123412412342112341234124312341234124;
			}
			function g() public returns (uint) {
				a = 2;
				return 0x1234123412431234123412412342112341234124312341234124;
			}
			function h() public returns (uint) {
				a = 3;
				return 0x1234123412431234123412412342112341234124312341234124;
			}
			function i() public returns (uint) {
				a = 4;
				return 0x1234123412431234123412412342112341234124312341234124;
			}
		}
	)";
	compileBothVersions(sourceCode, 0, "C", 50);
	compareVersions("f()");
	compareVersions("g()");
	compareVersions("h()");
	compareVersions("i()");
	// This is counting in the deployed code.
	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::CODECOPY), 0);
	BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::CODECOPY), 4);
}

BOOST_AUTO_TEST_CASE(byte_access)
{
	char const* sourceCode = R"(
		contract C
		{
			function f(bytes32 x) public returns (bytes1 r)
			{
				assembly { r := and(byte(x, 31), 0xff) }
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(bytes32)", u256("0x1223344556677889900112233445566778899001122334455667788990011223"));
}

BOOST_AUTO_TEST_CASE(shift_optimizer_bug)
{
	char const* sourceCode = R"(
		contract C
		{
			function f(uint x) public returns (uint)
			{
				return (x << 1) << type(uint).max;
			}
			function g(uint x) public returns (uint)
			{
				return (x >> 1) >> type(uint).max;
			}
		}
	)";
	compileBothVersions(sourceCode);
	compareVersions("f(uint256)", 7);
	compareVersions("g(uint256)", u256(-1));
}

BOOST_AUTO_TEST_CASE(avoid_double_cleanup)
{
	char const* sourceCode = R"(
		contract C {
			receive() external payable {
				abi.encodePacked(uint200(0));
			}
		}
	)";
	compileBothVersions(sourceCode, 0, "C", 50);
	// Check that there is no double AND instruction in the resulting code
	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::AND), 1);
}

BOOST_AUTO_TEST_SUITE_END()

} // end namespaces