/*
	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

#include <libsolidity/formal/SMTEncoder.h>

#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/formal/SymbolicState.h>
#include <libsolidity/formal/SymbolicTypes.h>

#include <libsolidity/analysis/ConstantEvaluator.h>

#include <libyul/AST.h>
#include <libyul/optimiser/Semantics.h>

#include <libsmtutil/SMTPortfolio.h>
#include <libsmtutil/Helpers.h>

#include <liblangutil/CharStreamProvider.h>

#include <range/v3/view.hpp>

#include <limits>
#include <deque>

using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::langutil;
using namespace solidity::frontend;

SMTEncoder::SMTEncoder(
	smt::EncodingContext& _context,
	ModelCheckerSettings const& _settings,
	UniqueErrorReporter& _errorReporter,
	langutil::CharStreamProvider const& _charStreamProvider
):
	m_errorReporter(_errorReporter),
	m_context(_context),
	m_settings(_settings),
	m_charStreamProvider(_charStreamProvider)
{
}

void SMTEncoder::resetSourceAnalysis()
{
	m_freeFunctions.clear();
}

bool SMTEncoder::visit(ContractDefinition const& _contract)
{
	solAssert(m_currentContract, "");

	for (auto const& node: _contract.subNodes())
		if (
			!dynamic_pointer_cast<FunctionDefinition>(node) &&
			!dynamic_pointer_cast<VariableDeclaration>(node)
		)
			node->accept(*this);

	for (auto const& base: _contract.annotation().linearizedBaseContracts)
	{
		// Look for all the constructor invocations bottom up.
		if (auto const& constructor =  base->constructor())
			for (auto const& invocation: constructor->modifiers())
			{
				auto refDecl = invocation->name().annotation().referencedDeclaration;
				if (auto const& baseContract = dynamic_cast<ContractDefinition const*>(refDecl))
				{
					solAssert(!m_baseConstructorCalls.count(baseContract), "");
					m_baseConstructorCalls[baseContract] = invocation.get();
				}
			}
	}
	// Functions are visited first since they might be used
	// for state variable initialization which is part of
	// the constructor.
	// Constructors are visited as part of the constructor
	// hierarchy inlining.
	for (auto const* function: contractFunctionsWithoutVirtual(_contract) + allFreeFunctions())
		if (!function->isConstructor())
			function->accept(*this);

	// Constructors need to be handled by the engines separately.

	return false;
}

void SMTEncoder::endVisit(ContractDefinition const& _contract)
{
	m_context.resetAllVariables();

	m_baseConstructorCalls.clear();

	solAssert(m_currentContract == &_contract, "");
	m_currentContract = nullptr;

	if (m_callStack.empty())
		m_context.popSolver();
}

void SMTEncoder::endVisit(VariableDeclaration const& _varDecl)
{
	// State variables are handled by the constructor.
	if (_varDecl.isLocalVariable() &&_varDecl.value())
		assignment(_varDecl, *_varDecl.value());
}

bool SMTEncoder::visit(ModifierDefinition const&)
{
	return false;
}

bool SMTEncoder::visit(FunctionDefinition const& _function)
{
	solAssert(m_currentContract, "");

	m_modifierDepthStack.push_back(-1);

	initializeLocalVariables(_function);

	_function.parameterList().accept(*this);
	if (_function.returnParameterList())
		_function.returnParameterList()->accept(*this);

	visitFunctionOrModifier();

	return false;
}

void SMTEncoder::visitFunctionOrModifier()
{
	solAssert(!m_callStack.empty(), "");
	solAssert(!m_modifierDepthStack.empty(), "");

	++m_modifierDepthStack.back();
	FunctionDefinition const& function = dynamic_cast<FunctionDefinition const&>(*m_callStack.back().first);

	if (m_modifierDepthStack.back() == static_cast<int>(function.modifiers().size()))
	{
		if (function.isImplemented())
		{
			pushInlineFrame(function);
			function.body().accept(*this);
			popInlineFrame(function);
		}
	}
	else
	{
		solAssert(m_modifierDepthStack.back() < static_cast<int>(function.modifiers().size()), "");
		ASTPointer<ModifierInvocation> const& modifierInvocation =
			function.modifiers()[static_cast<size_t>(m_modifierDepthStack.back())];
		solAssert(modifierInvocation, "");
		auto refDecl = modifierInvocation->name().annotation().referencedDeclaration;
		if (dynamic_cast<ContractDefinition const*>(refDecl))
			visitFunctionOrModifier();
		else if (auto modifier = resolveModifierInvocation(*modifierInvocation, m_currentContract))
		{
			m_scopes.push_back(modifier);
			inlineModifierInvocation(modifierInvocation.get(), modifier);
			solAssert(m_scopes.back() == modifier, "");
			m_scopes.pop_back();
		}
		else
			solAssert(false, "");
	}

	--m_modifierDepthStack.back();
}

void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation, CallableDeclaration const* _definition)
{
	solAssert(_invocation, "");
	_invocation->accept(*this);

	vector<smtutil::Expression> args;
	if (auto const* arguments = _invocation->arguments())
	{
		auto const& modifierParams = _definition->parameters();
		solAssert(modifierParams.size() == arguments->size(), "");
		for (unsigned i = 0; i < arguments->size(); ++i)
			args.push_back(expr(*arguments->at(i), modifierParams.at(i)->type()));
	}

	initializeFunctionCallParameters(*_definition, args);

	pushCallStack({_definition, _invocation});
	pushInlineFrame(*_definition);
	if (auto modifier = dynamic_cast<ModifierDefinition const*>(_definition))
	{
		if (modifier->isImplemented())
			modifier->body().accept(*this);
		popCallStack();
	}
	else if (auto function = dynamic_cast<FunctionDefinition const*>(_definition))
	{
		if (function->isImplemented())
			function->accept(*this);
		// Functions are popped from the callstack in endVisit(FunctionDefinition)
	}
	popInlineFrame(*_definition);
}

void SMTEncoder::inlineConstructorHierarchy(ContractDefinition const& _contract)
{
	auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts;
	auto it = find(begin(hierarchy), end(hierarchy), &_contract);
	solAssert(it != end(hierarchy), "");

	auto nextBase = it + 1;
	// Initialize the base contracts here as long as their constructors are implicit,
	// stop when the first explicit constructor is found.
	while (nextBase != end(hierarchy))
	{
		if (auto baseConstructor = (*nextBase)->constructor())
		{
			createLocalVariables(*baseConstructor);
			// If any subcontract explicitly called baseConstructor, use those arguments.
			if (m_baseConstructorCalls.count(*nextBase))
				inlineModifierInvocation(m_baseConstructorCalls.at(*nextBase), baseConstructor);
			else if (baseConstructor->isImplemented())
			{
				// The first constructor found is handled like a function
				// and its pushed into the callstack there.
				// This if avoids duplication in the callstack.
				if (!m_callStack.empty())
					pushCallStack({baseConstructor, nullptr});
				baseConstructor->accept(*this);
				// popped by endVisit(FunctionDefinition)
			}
			break;
		}
		else
		{
			initializeStateVariables(**nextBase);
			++nextBase;
		}
	}

	initializeStateVariables(_contract);
}

bool SMTEncoder::visit(PlaceholderStatement const&)
{
	solAssert(!m_callStack.empty(), "");
	auto lastCall = popCallStack();
	visitFunctionOrModifier();
	pushCallStack(lastCall);
	return true;
}

void SMTEncoder::endVisit(FunctionDefinition const&)
{
	solAssert(m_currentContract, "");

	popCallStack();
	solAssert(m_modifierDepthStack.back() == -1, "");
	m_modifierDepthStack.pop_back();
	if (m_callStack.empty())
		m_context.popSolver();
}

bool SMTEncoder::visit(Block const& _block)
{
	if (_block.unchecked())
	{
		solAssert(m_checked, "");
		m_checked = false;
	}
	return true;
}

void SMTEncoder::endVisit(Block const& _block)
{
	if (_block.unchecked())
	{
		solAssert(!m_checked, "");
		m_checked = true;
	}
}

bool SMTEncoder::visit(InlineAssembly const& _inlineAsm)
{
	/// This is very similar to `yul::Assignments`, except I need to collect `Identifier`s and not just names as `YulString`s.
	struct AssignedExternalsCollector: public yul::ASTWalker
	{
		AssignedExternalsCollector(InlineAssembly const& _inlineAsm): externalReferences(_inlineAsm.annotation().externalReferences)
		{
			this->operator()(_inlineAsm.operations());
		}

		map<yul::Identifier const*, InlineAssemblyAnnotation::ExternalIdentifierInfo> const& externalReferences;
		set<VariableDeclaration const*> assignedVars;

		using yul::ASTWalker::operator();
		void operator()(yul::Assignment const& _assignment)
		{
			auto const& vars = _assignment.variableNames;
			for (auto const& identifier: vars)
				if (auto externalInfo = valueOrNullptr(externalReferences, &identifier))
					if (auto varDecl = dynamic_cast<VariableDeclaration const*>(externalInfo->declaration))
						assignedVars.insert(varDecl);
		}
	};

	yul::SideEffectsCollector sideEffectsCollector(_inlineAsm.dialect(), _inlineAsm.operations());
	if (sideEffectsCollector.invalidatesMemory())
		resetMemoryVariables();
	if (sideEffectsCollector.invalidatesStorage())
		resetStorageVariables();

	auto assignedVars = AssignedExternalsCollector(_inlineAsm).assignedVars;
	for (auto const* var: assignedVars)
	{
		solAssert(var, "");
		solAssert(var->isLocalVariable(), "Non-local variable assigned in inlined assembly");
		m_context.resetVariable(*var);
	}

	m_errorReporter.warning(
		7737_error,
		_inlineAsm.location(),
		"Inline assembly may cause SMTChecker to produce spurious warnings (false positives)."
	);
	return false;
}

void SMTEncoder::pushInlineFrame(CallableDeclaration const&)
{
	pushPathCondition(currentPathConditions());
}

void SMTEncoder::popInlineFrame(CallableDeclaration const&)
{
	popPathCondition();
}

void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
{
	if (auto init = _varDecl.initialValue())
		expressionToTupleAssignment(_varDecl.declarations(), *init);
}

bool SMTEncoder::visit(Assignment const& _assignment)
{
	// RHS must be visited before LHS; as opposed to what Assignment::accept does
	_assignment.rightHandSide().accept(*this);
	_assignment.leftHandSide().accept(*this);
	return false;
}

void SMTEncoder::endVisit(Assignment const& _assignment)
{
	createExpr(_assignment);

	Token op = _assignment.assignmentOperator();
	solAssert(TokenTraits::isAssignmentOp(op), "");

	if (!isSupportedType(*_assignment.annotation().type))
	{
		// Give it a new index anyway to keep the SSA scheme sound.

		Expression const* base = &_assignment.leftHandSide();
		if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(base))
			base = leftmostBase(*indexAccess);

		if (auto varDecl = identifierToVariable(*base))
			m_context.newValue(*varDecl);
	}
	else
	{
		if (dynamic_cast<TupleType const*>(_assignment.rightHandSide().annotation().type))
			tupleAssignment(_assignment.leftHandSide(), _assignment.rightHandSide());
		else
		{
			auto const& type = _assignment.annotation().type;
			auto rightHandSide = op == Token::Assign ?
				expr(_assignment.rightHandSide(), type) :
				compoundAssignment(_assignment);
			defineExpr(_assignment, rightHandSide);
			assignment(
				_assignment.leftHandSide(),
				expr(_assignment, type),
				type
			);
		}
	}
}

void SMTEncoder::endVisit(TupleExpression const& _tuple)
{
	if (_tuple.annotation().type->category() == Type::Category::Function)
		return;

	if (_tuple.annotation().type->category() == Type::Category::TypeType)
		return;

	createExpr(_tuple);

	if (_tuple.isInlineArray())
	{
		// Add constraints for the length and values as it is known.
		auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_tuple));
		solAssert(symbArray, "");

		addArrayLiteralAssertions(*symbArray, applyMap(_tuple.components(), [&](auto const& c) { return expr(*c); }));
	}
	else
	{
		auto values = applyMap(_tuple.components(), [this](auto const& component) -> optional<smtutil::Expression> {
			if (component)
			{
				if (!m_context.knownExpression(*component))
						createExpr(*component);
				return expr(*component);
			}
			return {};
		});
		defineExpr(_tuple, values);
	}
}

void SMTEncoder::endVisit(UnaryOperation const& _op)
{
	/// We need to shortcut here due to potentially unknown
	/// rational number sizes.
	if (_op.annotation().type->category() == Type::Category::RationalNumber)
		return;

	if (TokenTraits::isBitOp(_op.getOperator()))
		return bitwiseNotOperation(_op);

	createExpr(_op);

	auto const* subExpr = innermostTuple(_op.subExpression());
	auto type = _op.annotation().type;
	switch (_op.getOperator())
	{
	case Token::Not: // !
	{
		solAssert(smt::isBool(*type), "");
		defineExpr(_op, !expr(*subExpr));
		break;
	}
	case Token::Inc: // ++ (pre- or postfix)
	case Token::Dec: // -- (pre- or postfix)
	{
		solAssert(smt::isInteger(*type) || smt::isFixedPoint(*type), "");
		solAssert(subExpr->annotation().willBeWrittenTo, "");
		auto innerValue = expr(*subExpr);
		auto newValue = arithmeticOperation(
			_op.getOperator() == Token::Inc ? Token::Add : Token::Sub,
			innerValue,
			smtutil::Expression(size_t(1)),
			_op.annotation().type,
			_op
		).first;
		defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
		assignment(*subExpr, newValue);
		break;
	}
	case Token::Sub: // -
	{
		defineExpr(_op, 0 - expr(*subExpr));
		break;
	}
	case Token::Delete:
	{
		if (auto decl = identifierToVariable(*subExpr))
		{
			m_context.newValue(*decl);
			m_context.setZeroValue(*decl);
		}
		else
		{
			solAssert(m_context.knownExpression(*subExpr), "");
			auto const& symbVar = m_context.expression(*subExpr);
			symbVar->increaseIndex();
			m_context.setZeroValue(*symbVar);
			if (
				dynamic_cast<IndexAccess const*>(subExpr) ||
				dynamic_cast<MemberAccess const*>(subExpr)
			)
				indexOrMemberAssignment(*subExpr, symbVar->currentValue());
			// Empty push added a zero value anyway, so no need to delete extra.
			else if (!isEmptyPush(*subExpr))
				solAssert(false, "");
		}
		break;
	}
	default:
		solAssert(false, "");
	}
}

bool SMTEncoder::visit(UnaryOperation const& _op)
{
	return !shortcutRationalNumber(_op);
}

bool SMTEncoder::visit(BinaryOperation const& _op)
{
	if (shortcutRationalNumber(_op))
		return false;
	if (TokenTraits::isBooleanOp(_op.getOperator()))
	{
		booleanOperation(_op);
		return false;
	}
	return true;
}

void SMTEncoder::endVisit(BinaryOperation const& _op)
{
	/// If _op is const evaluated the RationalNumber shortcut was taken.
	if (isConstant(_op))
		return;
	if (TokenTraits::isBooleanOp(_op.getOperator()))
		return;

	createExpr(_op);

	if (TokenTraits::isArithmeticOp(_op.getOperator()))
		arithmeticOperation(_op);
	else if (TokenTraits::isCompareOp(_op.getOperator()))
		compareOperation(_op);
	else if (TokenTraits::isBitOp(_op.getOperator()) || TokenTraits::isShiftOp(_op.getOperator()))
		bitwiseOperation(_op);
	else
		solAssert(false, "");
}

bool SMTEncoder::visit(Conditional const& _op)
{
	_op.condition().accept(*this);

	auto indicesEndTrue = visitBranch(&_op.trueExpression(), expr(_op.condition())).first;

	auto indicesEndFalse = visitBranch(&_op.falseExpression(), !expr(_op.condition())).first;

	mergeVariables(expr(_op.condition()), indicesEndTrue, indicesEndFalse);

	defineExpr(_op, smtutil::Expression::ite(
		expr(_op.condition()),
		expr(_op.trueExpression(), _op.annotation().type),
		expr(_op.falseExpression(), _op.annotation().type)
	));

	return false;
}

bool SMTEncoder::visit(FunctionCall const& _funCall)
{
	auto functionCallKind = *_funCall.annotation().kind;
	if (functionCallKind != FunctionCallKind::FunctionCall)
		return true;

	FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
	// We do not want to visit the TypeTypes in the second argument of `abi.decode`.
	// Those types are checked/used in SymbolicState::buildABIFunctions.
	if (funType.kind() == FunctionType::Kind::ABIDecode)
	{
		if (auto arg = _funCall.arguments().front())
			arg->accept(*this);
		return false;
	}
	// We do not really need to visit the expression in a wrap/unwrap no-op call,
	// so we just ignore the function call expression to avoid "unsupported" warnings.
	else if (
		funType.kind() == FunctionType::Kind::Wrap ||
		funType.kind() == FunctionType::Kind::Unwrap
	)
	{
		if (auto arg = _funCall.arguments().front())
			arg->accept(*this);
		return false;
	}
	return true;
}

void SMTEncoder::endVisit(FunctionCall const& _funCall)
{
	auto functionCallKind = *_funCall.annotation().kind;

	createExpr(_funCall);
	if (functionCallKind == FunctionCallKind::StructConstructorCall)
	{
		visitStructConstructorCall(_funCall);
		return;
	}

	if (functionCallKind == FunctionCallKind::TypeConversion)
	{
		visitTypeConversion(_funCall);
		return;
	}

	FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);

	std::vector<ASTPointer<Expression const>> const args = _funCall.arguments();
	switch (funType.kind())
	{
	case FunctionType::Kind::Assert:
		visitAssert(_funCall);
		break;
	case FunctionType::Kind::Require:
		visitRequire(_funCall);
		break;
	case FunctionType::Kind::Revert:
		// Revert is a special case of require and equals to `require(false)`
		addPathImpliedExpression(smtutil::Expression(false));
		break;
	case FunctionType::Kind::GasLeft:
		visitGasLeft(_funCall);
		break;
	case FunctionType::Kind::External:
		if (isPublicGetter(_funCall.expression()))
			visitPublicGetter(_funCall);
		break;
	case FunctionType::Kind::ABIDecode:
	case FunctionType::Kind::ABIEncode:
	case FunctionType::Kind::ABIEncodePacked:
	case FunctionType::Kind::ABIEncodeWithSelector:
	case FunctionType::Kind::ABIEncodeWithSignature:
		visitABIFunction(_funCall);
		break;
	case FunctionType::Kind::Internal:
	case FunctionType::Kind::BareStaticCall:
	case FunctionType::Kind::BareCall:
		break;
	case FunctionType::Kind::KECCAK256:
	case FunctionType::Kind::ECRecover:
	case FunctionType::Kind::SHA256:
	case FunctionType::Kind::RIPEMD160:
		visitCryptoFunction(_funCall);
		break;
	case FunctionType::Kind::BlockHash:
		defineExpr(_funCall, state().blockhash(expr(*_funCall.arguments().at(0))));
		break;
	case FunctionType::Kind::AddMod:
	case FunctionType::Kind::MulMod:
		visitAddMulMod(_funCall);
		break;
	case FunctionType::Kind::Unwrap:
	case FunctionType::Kind::Wrap:
		visitWrapUnwrap(_funCall);
		break;
	case FunctionType::Kind::Send:
	case FunctionType::Kind::Transfer:
	{
		auto const& memberAccess = dynamic_cast<MemberAccess const&>(_funCall.expression());
		auto const& address = memberAccess.expression();
		auto const& value = args.front();
		solAssert(value, "");

		smtutil::Expression thisBalance = state().balance();
		setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_context);

		state().transfer(state().thisAddress(), expr(address), expr(*value));
		break;
	}
	case FunctionType::Kind::ArrayPush:
		arrayPush(_funCall);
		break;
	case FunctionType::Kind::ArrayPop:
		arrayPop(_funCall);
		break;
	case FunctionType::Kind::Event:
	case FunctionType::Kind::Error:
		// This can be safely ignored.
		break;
	case FunctionType::Kind::ObjectCreation:
		visitObjectCreation(_funCall);
		return;
	case FunctionType::Kind::DelegateCall:
	case FunctionType::Kind::BareCallCode:
	case FunctionType::Kind::BareDelegateCall:
	case FunctionType::Kind::Creation:
	default:
		m_errorReporter.warning(
			4588_error,
			_funCall.location(),
			"Assertion checker does not yet implement this type of function call."
		);
	}
}

bool SMTEncoder::visit(ModifierInvocation const& _node)
{
	if (auto const* args = _node.arguments())
		for (auto const& arg: *args)
			if (arg)
				arg->accept(*this);
	return false;
}

void SMTEncoder::initContract(ContractDefinition const& _contract)
{
	solAssert(m_currentContract == nullptr, "");
	m_currentContract = &_contract;

	m_context.reset();
	m_context.pushSolver();
	createStateVariables(_contract);
	clearIndices(m_currentContract, nullptr);
	m_variableUsage.setCurrentContract(_contract);
	m_checked = true;
}

void SMTEncoder::initFunction(FunctionDefinition const& _function)
{
	solAssert(m_callStack.empty(), "");
	solAssert(m_currentContract, "");
	m_context.pushSolver();
	m_pathConditions.clear();
	pushCallStack({&_function, nullptr});
	m_uninterpretedTerms.clear();
	createStateVariables(*m_currentContract);
	createLocalVariables(_function);
	m_arrayAssignmentHappened = false;
	clearIndices(m_currentContract, &_function);
	m_checked = true;
}

void SMTEncoder::visitAssert(FunctionCall const& _funCall)
{
	auto const& args = _funCall.arguments();
	solAssert(args.size() == 1, "");
	solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
}

void SMTEncoder::visitRequire(FunctionCall const& _funCall)
{
	auto const& args = _funCall.arguments();
	solAssert(args.size() >= 1, "");
	solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
	addPathImpliedExpression(expr(*args.front()));
}

void SMTEncoder::visitABIFunction(FunctionCall const& _funCall)
{
	auto symbFunction = state().abiFunction(&_funCall);
	auto const& [name, inTypes, outTypes] = state().abiFunctionTypes(&_funCall);

	auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
	auto kind = funType.kind();
	auto const& args = _funCall.sortedArguments();
	auto argsActualLength = kind == FunctionType::Kind::ABIDecode ? 1 : args.size();

	solAssert(inTypes.size() == argsActualLength, "");
	if (argsActualLength == 0)
	{
		defineExpr(_funCall, smt::zeroValue(TypeProvider::bytesMemory()));
		return;
	}
	vector<smtutil::Expression> symbArgs;
	for (unsigned i = 0; i < argsActualLength; ++i)
		if (args.at(i))
			symbArgs.emplace_back(expr(*args.at(i), inTypes.at(i)));

	optional<smtutil::Expression> arg;
	if (inTypes.size() == 1)
		arg = expr(*args.at(0), inTypes.at(0));
	else
	{
		auto inputSort = dynamic_cast<smtutil::ArraySort&>(*symbFunction.sort).domain;
		arg = smtutil::Expression::tuple_constructor(
			smtutil::Expression(make_shared<smtutil::SortSort>(inputSort), ""),
			symbArgs
		);
	}

	auto out = smtutil::Expression::select(symbFunction, *arg);
	if (outTypes.size() == 1)
		defineExpr(_funCall, out);
	else
	{
		auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_funCall));
		solAssert(symbTuple, "");
		solAssert(symbTuple->components().size() == outTypes.size(), "");
		solAssert(out.sort->kind == smtutil::Kind::Tuple, "");

		symbTuple->increaseIndex();
		for (unsigned i = 0; i < symbTuple->components().size(); ++i)
			m_context.addAssertion(symbTuple->component(i) == smtutil::Expression::tuple_get(out, i));
	}
}

void SMTEncoder::visitCryptoFunction(FunctionCall const& _funCall)
{
	auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
	auto kind = funType.kind();
	auto arg0 = expr(*_funCall.arguments().at(0));
	optional<smtutil::Expression> result;
	if (kind == FunctionType::Kind::KECCAK256)
		result = smtutil::Expression::select(state().cryptoFunction("keccak256"), arg0);
	else if (kind == FunctionType::Kind::SHA256)
		result = smtutil::Expression::select(state().cryptoFunction("sha256"), arg0);
	else if (kind == FunctionType::Kind::RIPEMD160)
		result = smtutil::Expression::select(state().cryptoFunction("ripemd160"), arg0);
	else if (kind == FunctionType::Kind::ECRecover)
	{
		auto e = state().cryptoFunction("ecrecover");
		auto arg0 = expr(*_funCall.arguments().at(0));
		auto arg1 = expr(*_funCall.arguments().at(1));
		auto arg2 = expr(*_funCall.arguments().at(2));
		auto arg3 = expr(*_funCall.arguments().at(3));
		auto inputSort = dynamic_cast<smtutil::ArraySort&>(*e.sort).domain;
		auto ecrecoverInput = smtutil::Expression::tuple_constructor(
			smtutil::Expression(make_shared<smtutil::SortSort>(inputSort), ""),
			{arg0, arg1, arg2, arg3}
		);
		result = smtutil::Expression::select(e, ecrecoverInput);
	}
	else
		solAssert(false, "");

	defineExpr(_funCall, *result);
}

void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
{
	string gasLeft = "gasleft";
	// We increase the variable index since gasleft changes
	// inside a tx.
	defineGlobalVariable(gasLeft, _funCall, true);
	auto const& symbolicVar = m_context.globalSymbol(gasLeft);
	unsigned index = symbolicVar->index();
	// We set the current value to unknown anyway to add type constraints.
	m_context.setUnknownValue(*symbolicVar);
	if (index > 0)
		m_context.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1));
}

void SMTEncoder::visitAddMulMod(FunctionCall const& _funCall)
{
	auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
	auto kind = funType.kind();
	solAssert(kind == FunctionType::Kind::AddMod || kind == FunctionType::Kind::MulMod, "");
	auto const& args = _funCall.arguments();
	solAssert(args.at(0) && args.at(1) && args.at(2), "");
	auto x = expr(*args.at(0));
	auto y = expr(*args.at(1));
	auto k = expr(*args.at(2));
	auto const& intType = dynamic_cast<IntegerType const&>(*_funCall.annotation().type);

	if (kind == FunctionType::Kind::AddMod)
		defineExpr(_funCall, divModWithSlacks(x + y, k, intType).second);
	else
		defineExpr(_funCall, divModWithSlacks(x * y, k, intType).second);
}

void SMTEncoder::visitWrapUnwrap(FunctionCall const& _funCall)
{
	auto const& args = _funCall.arguments();
	solAssert(args.size() == 1, "");
	defineExpr(_funCall, expr(*args.front()));
}

void SMTEncoder::visitObjectCreation(FunctionCall const& _funCall)
{
	auto const& args = _funCall.arguments();
	solAssert(args.size() >= 1, "");
	auto argType = args.front()->annotation().type->category();
	solAssert(argType == Type::Category::Integer || argType == Type::Category::RationalNumber, "");

	smtutil::Expression arraySize = expr(*args.front());
	setSymbolicUnknownValue(arraySize, TypeProvider::uint256(), m_context);

	auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_funCall));
	solAssert(symbArray, "");
	smt::setSymbolicZeroValue(*symbArray, m_context);
	auto zeroElements = symbArray->elements();
	symbArray->increaseIndex();
	m_context.addAssertion(symbArray->length() == arraySize);
	m_context.addAssertion(symbArray->elements() == zeroElements);
}

void SMTEncoder::endVisit(Identifier const& _identifier)
{
	if (auto decl = identifierToVariable(_identifier))
	{
		if (decl->isConstant())
			defineExpr(_identifier, constantExpr(_identifier, *decl));
		else
			defineExpr(_identifier, currentValue(*decl));
	}
	else if (_identifier.annotation().type->category() == Type::Category::Function)
		visitFunctionIdentifier(_identifier);
	else if (_identifier.name() == "now")
		defineGlobalVariable(_identifier.name(), _identifier);
	else if (_identifier.name() == "this")
	{
		defineExpr(_identifier, state().thisAddress());
		m_uninterpretedTerms.insert(&_identifier);
	}
	// Ignore type identifiers
	else if (dynamic_cast<TypeType const*>(_identifier.annotation().type))
		return;
	// Ignore module identifiers
	else if (dynamic_cast<ModuleType const*>(_identifier.annotation().type))
		return;
	// Ignore user defined value type identifiers
	else if (dynamic_cast<UserDefinedValueType const*>(_identifier.annotation().type))
		return;
	// Ignore the builtin abi, it is handled in FunctionCall.
	// TODO: ignore MagicType in general (abi, block, msg, tx, type)
	else if (auto magicType = dynamic_cast<MagicType const*>(_identifier.annotation().type); magicType && magicType->kind() == MagicType::Kind::ABI)
	{
		solAssert(_identifier.name() == "abi", "");
		return;
	}
	else
		createExpr(_identifier);
}

void SMTEncoder::endVisit(ElementaryTypeNameExpression const& _typeName)
{
	auto const& typeType = dynamic_cast<TypeType const&>(*_typeName.annotation().type);
	auto result = smt::newSymbolicVariable(
		*TypeProvider::uint256(),
		typeType.actualType()->toString(false),
		m_context
	);
	solAssert(!result.first && result.second, "");
	m_context.createExpression(_typeName, result.second);
}

namespace // helpers for SMTEncoder::visitPublicGetter
{

bool isReturnedFromStructGetter(Type const* _type)
{
	// So far it seems that only Mappings and ordinary Arrays are not returned.
	auto category = _type->category();
	if (category == Type::Category::Mapping)
		return false;
	if (category == Type::Category::Array)
		return dynamic_cast<ArrayType const&>(*_type).isByteArray();
	// default
	return true;
}

vector<string> structGetterReturnedMembers(StructType const& _structType)
{
	vector<string> returnedMembers;
	for (auto const& member: _structType.nativeMembers(nullptr))
		if (isReturnedFromStructGetter(member.type))
			returnedMembers.push_back(member.name);
	return returnedMembers;
}

}

void SMTEncoder::visitPublicGetter(FunctionCall const& _funCall)
{
	MemberAccess const& access = dynamic_cast<MemberAccess const&>(_funCall.expression());
	auto var = dynamic_cast<VariableDeclaration const*>(access.annotation().referencedDeclaration);
	solAssert(var, "");
	solAssert(m_context.knownExpression(_funCall), "");
	auto paramExpectedTypes = replaceUserTypes(FunctionType(*var).parameterTypes());
	auto actualArguments = _funCall.arguments();
	solAssert(actualArguments.size() == paramExpectedTypes.size(), "");
	deque<smtutil::Expression> symbArguments;
	for (unsigned i = 0; i < paramExpectedTypes.size(); ++i)
		symbArguments.push_back(expr(*actualArguments[i], paramExpectedTypes[i]));

	// See FunctionType::FunctionType(VariableDeclaration const& _varDecl)
	// to understand the return types of public getters.
	Type const* type = var->type();
	smtutil::Expression currentExpr = currentValue(*var);
	while (true)
	{
		if (
			type->isValueType() ||
			(type->category() == Type::Category::Array && dynamic_cast<ArrayType const&>(*type).isByteArray())
		)
		{
			solAssert(symbArguments.empty(), "");
			defineExpr(_funCall, currentExpr);
			return;
		}
		switch (type->category())
		{
			case Type::Category::Array:
			case Type::Category::Mapping:
			{
				solAssert(!symbArguments.empty(), "");
				// For nested arrays/mappings, each argument in the call is an index to the next layer.
				// We mirror this with `select` after unpacking the SMT-LIB array expression.
				currentExpr = smtutil::Expression::select(smtutil::Expression::tuple_get(currentExpr, 0), symbArguments.front());
				symbArguments.pop_front();
				if (auto arrayType = dynamic_cast<ArrayType const*>(type))
					type = arrayType->baseType();
				else if (auto mappingType = dynamic_cast<MappingType const*>(type))
					type = mappingType->valueType();
				else
					solAssert(false, "");
				break;
			}
			case Type::Category::Struct:
			{
				solAssert(symbArguments.empty(), "");
				smt::SymbolicStructVariable structVar(dynamic_cast<StructType const*>(type), "struct_temp_" + to_string(_funCall.id()), m_context);
				m_context.addAssertion(structVar.currentValue() == currentExpr);
				auto returnedMembers = structGetterReturnedMembers(dynamic_cast<StructType const&>(*structVar.type()));
				solAssert(!returnedMembers.empty(), "");
				auto returnedValues = applyMap(returnedMembers, [&](string const& memberName) -> optional<smtutil::Expression> { return structVar.member(memberName); });
				defineExpr(_funCall, returnedValues);
				return;
			}
			default:
			{
				// Unsupported cases, do nothing and the getter will be abstracted.
				return;
			}
		}
	}
}

bool SMTEncoder::shouldAnalyze(ContractDefinition const& _contract) const
{
	if (!_contract.canBeDeployed())
		return false;

	return m_settings.contracts.isDefault() ||
		m_settings.contracts.has(_contract.sourceUnitName(), _contract.name());
}

void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)
{
	solAssert(*_funCall.annotation().kind == FunctionCallKind::TypeConversion, "");
	solAssert(_funCall.arguments().size() == 1, "");

	auto argument = _funCall.arguments().front();
	auto const argType = argument->annotation().type;
	auto const funCallType = _funCall.annotation().type;

	auto symbArg = expr(*argument, funCallType);

	if (smt::isStringLiteral(*argType) && smt::isFixedBytes(*funCallType))
	{
		defineExpr(_funCall, symbArg);
		return;
	}

	ArrayType const* arrayType = dynamic_cast<ArrayType const*>(argType);
	if (auto sliceType = dynamic_cast<ArraySliceType const*>(argType))
		arrayType = &sliceType->arrayType();

	if (arrayType && arrayType->isByteArray() && smt::isFixedBytes(*funCallType))
	{
		auto array = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(*argument));
		bytesToFixedBytesAssertions(*array, _funCall);
		return;
	}

	// TODO Simplify this whole thing for 0.8.0 where weird casts are disallowed.

	unsigned argSize = argType->storageBytes();
	unsigned castSize = funCallType->storageBytes();
	bool castIsSigned = smt::isNumber(*funCallType) && smt::isSigned(funCallType);
	bool argIsSigned = smt::isNumber(*argType) && smt::isSigned(argType);
	optional<smtutil::Expression> symbMin;
	optional<smtutil::Expression> symbMax;
	if (smt::isNumber(*funCallType))
	{
		symbMin = smt::minValue(funCallType);
		symbMax = smt::maxValue(funCallType);
	}
	if (argSize == castSize)
	{
		// If sizes are the same, it's possible that the signs are different.
		if (smt::isNumber(*funCallType) && smt::isNumber(*argType))
		{
			// castIsSigned && !argIsSigned => might overflow if arg > castType.max
			// !castIsSigned && argIsSigned => might underflow if arg < castType.min
			// !castIsSigned && !argIsSigned => ok
			// castIsSigned && argIsSigned => ok

			if (castIsSigned && !argIsSigned)
			{
				auto wrap = smtutil::Expression::ite(
					symbArg > *symbMax,
					symbArg - (*symbMax - *symbMin + 1),
					symbArg
				);
				defineExpr(_funCall, wrap);
			}
			else if (!castIsSigned && argIsSigned)
			{
				auto wrap = smtutil::Expression::ite(
					symbArg < *symbMin,
					symbArg + (*symbMax + 1),
					symbArg
				);
				defineExpr(_funCall, wrap);
			}
			else
				defineExpr(_funCall, symbArg);
		}
		else
			defineExpr(_funCall, symbArg);
	}
	else if (castSize > argSize)
	{
		solAssert(smt::isNumber(*funCallType), "");
		// RationalNumbers have size 32.
		solAssert(argType->category() != Type::Category::RationalNumber, "");

		// castIsSigned && !argIsSigned => ok
		// castIsSigned && argIsSigned => ok
		// !castIsSigned && !argIsSigned => ok except for FixedBytesType, need to adjust padding
		// !castIsSigned && argIsSigned => might underflow if arg < castType.min

		if (!castIsSigned && argIsSigned)
		{
			auto wrap = smtutil::Expression::ite(
				symbArg < *symbMin,
				symbArg + (*symbMax + 1),
				symbArg
			);
			defineExpr(_funCall, wrap);
		}
		else if (!castIsSigned && !argIsSigned)
		{
			if (auto const* fixedCast = dynamic_cast<FixedBytesType const*>(funCallType))
			{
				auto const* fixedArg = dynamic_cast<FixedBytesType const*>(argType);
				solAssert(fixedArg, "");
				auto diff = fixedCast->numBytes() - fixedArg->numBytes();
				solAssert(diff > 0, "");
				auto bvSize = fixedCast->numBytes() * 8;
				defineExpr(
					_funCall,
					smtutil::Expression::bv2int(smtutil::Expression::int2bv(symbArg, bvSize) << smtutil::Expression::int2bv(diff * 8, bvSize))
				);
			}
			else
				defineExpr(_funCall, symbArg);
		}
		else
			defineExpr(_funCall, symbArg);
	}
	else // castSize < argSize
	{
		solAssert(smt::isNumber(*funCallType), "");

		RationalNumberType const* rationalType = isConstant(*argument);
		if (rationalType)
		{
			// The TypeChecker guarantees that a constant fits in the cast size.
			defineExpr(_funCall, symbArg);
			return;
		}

		auto const* fixedCast = dynamic_cast<FixedBytesType const*>(funCallType);
		auto const* fixedArg = dynamic_cast<FixedBytesType const*>(argType);
		if (fixedCast && fixedArg)
		{
			createExpr(_funCall);
			auto diff = argSize - castSize;
			solAssert(fixedArg->numBytes() - fixedCast->numBytes() == diff, "");

			auto argValueBV = smtutil::Expression::int2bv(symbArg, argSize * 8);
			auto shr = smtutil::Expression::int2bv(diff * 8, argSize * 8);
			solAssert(!castIsSigned, "");
			defineExpr(_funCall, smtutil::Expression::bv2int(argValueBV >> shr));
		}
		else
		{
			auto argValueBV = smtutil::Expression::int2bv(symbArg, castSize * 8);
			defineExpr(_funCall, smtutil::Expression::bv2int(argValueBV, castIsSigned));
		}
	}
}

void SMTEncoder::visitFunctionIdentifier(Identifier const& _identifier)
{
	auto const& fType = dynamic_cast<FunctionType const&>(*_identifier.annotation().type);
	if (replaceUserTypes(fType.returnParameterTypes()).size() == 1)
	{
		defineGlobalVariable(fType.identifier(), _identifier);
		m_context.createExpression(_identifier, m_context.globalSymbol(fType.identifier()));
	}
}

void SMTEncoder::visitStructConstructorCall(FunctionCall const& _funCall)
{
	solAssert(*_funCall.annotation().kind == FunctionCallKind::StructConstructorCall, "");
	if (smt::isNonRecursiveStruct(*_funCall.annotation().type))
	{
		auto& structSymbolicVar = dynamic_cast<smt::SymbolicStructVariable&>(*m_context.expression(_funCall));
		auto structType = dynamic_cast<StructType const*>(structSymbolicVar.type());
		solAssert(structType, "");
		auto const& structMembers = structType->structDefinition().members();
		solAssert(structMembers.size() == _funCall.sortedArguments().size(), "");
		auto args = _funCall.sortedArguments();
		structSymbolicVar.assignAllMembers(applyMap(
			ranges::views::zip(args, structMembers),
			[this] (auto const& argMemberPair) { return expr(*argMemberPair.first, argMemberPair.second->type()); }
		));
	}

}

void SMTEncoder::endVisit(Literal const& _literal)
{
	solAssert(_literal.annotation().type, "Expected type for AST node");
	Type const& type = *_literal.annotation().type;
	if (smt::isNumber(type))
		defineExpr(_literal, smtutil::Expression(type.literalValue(&_literal)));
	else if (smt::isBool(type))
		defineExpr(_literal, smtutil::Expression(_literal.token() == Token::TrueLiteral ? true : false));
	else if (smt::isStringLiteral(type))
	{
		createExpr(_literal);

		// Add constraints for the length and values as it is known.
		auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_literal));
		solAssert(symbArray, "");

		addArrayLiteralAssertions(
			*symbArray,
			applyMap(_literal.value(), [](unsigned char c) { return smtutil::Expression{size_t(c)}; })
		);
	}
	else
		solAssert(false, "");
}

void SMTEncoder::addArrayLiteralAssertions(
	smt::SymbolicArrayVariable& _symArray,
	vector<smtutil::Expression> const& _elementValues
)
{
	m_context.addAssertion(_symArray.length() == _elementValues.size());
	for (size_t i = 0; i < _elementValues.size(); i++)
		m_context.addAssertion(smtutil::Expression::select(_symArray.elements(), i) == _elementValues[i]);
}

void SMTEncoder::bytesToFixedBytesAssertions(
	smt::SymbolicArrayVariable& _symArray,
	Expression const& _fixedBytes
)
{
	auto const& fixed = dynamic_cast<FixedBytesType const&>(*_fixedBytes.annotation().type);
	auto intType = TypeProvider::uint256();
	string suffix = to_string(_fixedBytes.id()) + "_" + to_string(m_context.newUniqueId());
	smt::SymbolicIntVariable k(intType, intType, "k_" + suffix, m_context);
	m_context.addAssertion(k.currentValue() == 0);
	size_t n = fixed.numBytes();
	for (size_t i = 0; i < n; i++)
	{
		auto kPrev = k.currentValue();
		m_context.addAssertion((smtutil::Expression::select(_symArray.elements(), i) * (u256(1) << ((n - i - 1) * 8))) + kPrev == k.increaseIndex());
	}
	m_context.addAssertion(expr(_fixedBytes) == k.currentValue());
}

void SMTEncoder::endVisit(Return const& _return)
{
	if (_return.expression() && m_context.knownExpression(*_return.expression()))
	{
		auto returnParams = m_callStack.back().first->returnParameters();
		if (returnParams.size() > 1)
		{
			auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*_return.expression()));
			solAssert(symbTuple, "");
			solAssert(symbTuple->components().size() == returnParams.size(), "");

			auto const* tupleType = dynamic_cast<TupleType const*>(_return.expression()->annotation().type);
			solAssert(tupleType, "");
			auto const& types = tupleType->components();
			solAssert(types.size() == returnParams.size(), "");

			for (unsigned i = 0; i < returnParams.size(); ++i)
				assignment(*returnParams.at(i), symbTuple->component(i, types.at(i), returnParams.at(i)->type()));
		}
		else if (returnParams.size() == 1)
			assignment(*returnParams.front(), expr(*_return.expression(), returnParams.front()->type()));
	}
}

bool SMTEncoder::visit(MemberAccess const& _memberAccess)
{
	auto const& accessType = _memberAccess.annotation().type;
	if (accessType->category() == Type::Category::Function)
		return true;

	createExpr(_memberAccess);

	Expression const* memberExpr = innermostTuple(_memberAccess.expression());

	auto const& exprType = memberExpr->annotation().type;
	solAssert(exprType, "");
	if (exprType->category() == Type::Category::Magic)
	{
		if (auto const* identifier = dynamic_cast<Identifier const*>(memberExpr))
		{
			auto const& name = identifier->name();
			solAssert(name == "block" || name == "msg" || name == "tx", "");
			defineExpr(_memberAccess, state().txMember(name + "." + _memberAccess.memberName()));
		}
		else if (auto magicType = dynamic_cast<MagicType const*>(exprType); magicType->kind() == MagicType::Kind::MetaType)
		{
			auto const& memberName = _memberAccess.memberName();
			if (memberName == "min" || memberName == "max")
			{
				if (IntegerType const* integerType = dynamic_cast<IntegerType const*>(magicType->typeArgument()))
					defineExpr(_memberAccess, memberName == "min" ? integerType->minValue() : integerType->maxValue());
				else if (EnumType const* enumType = dynamic_cast<EnumType const*>(magicType->typeArgument()))
					defineExpr(_memberAccess, memberName == "min" ? enumType->minValue() : enumType->maxValue());
			}
			else if (memberName == "interfaceId")
			{
				ContractDefinition const& contract = dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition();
				defineExpr(_memberAccess, contract.interfaceId());
			}
			else
				// NOTE: supporting name, creationCode, runtimeCode would be easy enough, but the bytes/string they return are not
				//       at all usable in the SMT checker currently
				m_errorReporter.warning(
					7507_error,
					_memberAccess.location(),
					"Assertion checker does not yet support this expression."
				);
		}
		else
			solUnimplementedAssert(false, "");

		return false;
	}
	else if (smt::isNonRecursiveStruct(*exprType))
	{
		memberExpr->accept(*this);
		auto const& symbStruct = dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.expression(*memberExpr));
		defineExpr(_memberAccess, symbStruct->member(_memberAccess.memberName()));
		return false;
	}
	else if (exprType->category() == Type::Category::TypeType)
	{
		auto const* decl = expressionToDeclaration(*memberExpr);
		if (dynamic_cast<EnumDefinition const*>(decl))
		{
			auto enumType = dynamic_cast<EnumType const*>(accessType);
			solAssert(enumType, "");
			defineExpr(_memberAccess, enumType->memberValue(_memberAccess.memberName()));

			return false;
		}
		else if (dynamic_cast<ContractDefinition const*>(decl))
		{
			if (auto const* var = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
			{
				defineExpr(_memberAccess, currentValue(*var));
				return false;
			}
		}
	}
	else if (exprType->category() == Type::Category::Address)
	{
		memberExpr->accept(*this);
		if (_memberAccess.memberName() == "balance")
		{
			defineExpr(_memberAccess, state().balance(expr(*memberExpr)));
			setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_context);
			m_uninterpretedTerms.insert(&_memberAccess);
			return false;
		}
	}
	else if (exprType->category() == Type::Category::Array)
	{
		memberExpr->accept(*this);
		if (_memberAccess.memberName() == "length")
		{
			auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(*memberExpr));
			solAssert(symbArray, "");
			defineExpr(_memberAccess, symbArray->length());
			m_uninterpretedTerms.insert(&_memberAccess);
			setSymbolicUnknownValue(
				expr(_memberAccess),
				_memberAccess.annotation().type,
				m_context
			);
		}
		return false;
	}
	else if (
		auto const* functionType = dynamic_cast<FunctionType const*>(exprType);
		functionType &&
		_memberAccess.memberName() == "selector" &&
		functionType->hasDeclaration()
	)
	{
		defineExpr(_memberAccess, functionType->externalIdentifier());
		return false;
	}
	else if (exprType->category() == Type::Category::Module)
	{
		if (auto const* var = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
		{
			solAssert(var->isConstant(), "");
			defineExpr(_memberAccess, constantExpr(_memberAccess, *var));
			return false;
		}
	}
	else
		m_errorReporter.warning(
			7650_error,
			_memberAccess.location(),
			"Assertion checker does not yet support this expression."
		);

	return true;
}

void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
{
	createExpr(_indexAccess);

	if (_indexAccess.annotation().type->category() == Type::Category::TypeType)
		return;

	makeOutOfBoundsVerificationTarget(_indexAccess);

	if (auto const* type = dynamic_cast<FixedBytesType const*>(_indexAccess.baseExpression().annotation().type))
	{
		smtutil::Expression base = expr(_indexAccess.baseExpression());

		if (type->numBytes() == 1)
			defineExpr(_indexAccess, base);
		else
		{
			auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_indexAccess.baseExpression().annotation().type);
			solAssert(!isSigned, "");
			solAssert(bvSize >= 16, "");
			solAssert(bvSize % 8 == 0, "");

			smtutil::Expression idx = expr(*_indexAccess.indexExpression());

			auto bvBase = smtutil::Expression::int2bv(base, bvSize);
			auto bvShl = smtutil::Expression::int2bv(idx * 8, bvSize);
			auto bvShr = smtutil::Expression::int2bv(bvSize - 8, bvSize);
			auto result = (bvBase << bvShl) >> bvShr;

			auto anyValue = expr(_indexAccess);
			m_context.expression(_indexAccess)->increaseIndex();
			unsigned numBytes = bvSize / 8;
			auto withBound = smtutil::Expression::ite(
				idx < numBytes,
				smtutil::Expression::bv2int(result, false),
				anyValue
			);
			defineExpr(_indexAccess, withBound);
		}
		return;
	}

	shared_ptr<smt::SymbolicVariable> array;
	if (auto const* id = dynamic_cast<Identifier const*>(&_indexAccess.baseExpression()))
	{
		auto varDecl = identifierToVariable(*id);
		solAssert(varDecl, "");
		array = m_context.variable(*varDecl);

		if (varDecl && varDecl->isConstant())
			m_context.addAssertion(currentValue(*varDecl) == expr(*id));
	}
	else
	{
		solAssert(m_context.knownExpression(_indexAccess.baseExpression()), "");
		array = m_context.expression(_indexAccess.baseExpression());
	}

	auto arrayVar = dynamic_pointer_cast<smt::SymbolicArrayVariable>(array);
	solAssert(arrayVar, "");

	Type const* baseType = _indexAccess.baseExpression().annotation().type;
	defineExpr(_indexAccess, smtutil::Expression::select(
		arrayVar->elements(),
		expr(*_indexAccess.indexExpression(), keyType(baseType))
	));
	setSymbolicUnknownValue(
		expr(_indexAccess),
		_indexAccess.annotation().type,
		m_context
	);
	m_uninterpretedTerms.insert(&_indexAccess);
}

void SMTEncoder::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
	createExpr(_indexRangeAccess);
	/// The actual slice is created by CHC which also assigns the length.
}

void SMTEncoder::arrayAssignment()
{
	m_arrayAssignmentHappened = true;
}

void SMTEncoder::indexOrMemberAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide)
{
	auto toStore = _rightHandSide;
	auto const* lastExpr = &_expr;
	while (true)
	{
		if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(lastExpr))
		{
			auto const& base = indexAccess->baseExpression();
			if (dynamic_cast<Identifier const*>(&base))
				base.accept(*this);

			Type const* baseType = base.annotation().type;
			auto indexExpr = expr(*indexAccess->indexExpression(), keyType(baseType));
			auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(base));
			solAssert(symbArray, "");
			toStore = smtutil::Expression::tuple_constructor(
				smtutil::Expression(make_shared<smtutil::SortSort>(smt::smtSort(*baseType)), baseType->toString(true)),
				{smtutil::Expression::store(symbArray->elements(), indexExpr, toStore), symbArray->length()}
			);
			defineExpr(*indexAccess, smtutil::Expression::select(
				symbArray->elements(),
				indexExpr
			));
			lastExpr = &indexAccess->baseExpression();
		}
		else if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(lastExpr))
		{
			auto const& base = memberAccess->expression();
			if (dynamic_cast<Identifier const*>(&base))
				base.accept(*this);

			if (
				auto const* structType = dynamic_cast<StructType const*>(base.annotation().type);
				structType && structType->recursive()
			)
			{
				m_errorReporter.warning(
					4375_error,
					memberAccess->location(),
					"Assertion checker does not support recursive structs."
				);
				return;
			}
			if (auto varDecl = identifierToVariable(*memberAccess))
			{
				if (varDecl->hasReferenceOrMappingType())
					resetReferences(*varDecl);

				assignment(*varDecl, toStore);
				break;
			}

			auto symbStruct = dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.expression(base));
			solAssert(symbStruct, "");
			symbStruct->assignMember(memberAccess->memberName(), toStore);
			toStore = symbStruct->currentValue();
			defineExpr(*memberAccess, symbStruct->member(memberAccess->memberName()));
			lastExpr = &memberAccess->expression();
		}
		else if (auto const& id = dynamic_cast<Identifier const*>(lastExpr))
		{
			auto varDecl = identifierToVariable(*id);
			solAssert(varDecl, "");

			if (varDecl->hasReferenceOrMappingType())
				resetReferences(*varDecl);

			assignment(*varDecl, toStore);
			defineExpr(*id, currentValue(*varDecl));
			break;
		}
		else
		{
			auto type = lastExpr->annotation().type;
			if (
				dynamic_cast<ReferenceType const*>(type) ||
				dynamic_cast<MappingType const*>(type)
			)
				resetReferences(type);

			assignment(*m_context.expression(*lastExpr), toStore);
			break;
		}
	}
}

void SMTEncoder::arrayPush(FunctionCall const& _funCall)
{
	auto memberAccess = dynamic_cast<MemberAccess const*>(&_funCall.expression());
	solAssert(memberAccess, "");
	auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
	solAssert(symbArray, "");
	auto oldLength = symbArray->length();
	m_context.addAssertion(oldLength >= 0);
	// Real world assumption: the array length is assumed to not overflow.
	// This assertion guarantees that both the current and updated lengths have the above property.
	m_context.addAssertion(oldLength + 1 < (smt::maxValue(*TypeProvider::uint256()) - 1));

	auto const& arguments = _funCall.arguments();
	auto arrayType = dynamic_cast<ArrayType const*>(symbArray->type());
	solAssert(arrayType, "");
	auto elementType = arrayType->baseType();
	smtutil::Expression element = arguments.empty() ?
		smt::zeroValue(elementType) :
		expr(*arguments.front(), elementType);
	smtutil::Expression store = smtutil::Expression::store(
		symbArray->elements(),
		oldLength,
		element
	);
	symbArray->increaseIndex();
	m_context.addAssertion(symbArray->elements() == store);
	m_context.addAssertion(symbArray->length() == oldLength + 1);

	if (arguments.empty())
		defineExpr(_funCall, element);

	assignment(memberAccess->expression(), symbArray->currentValue());
}

void SMTEncoder::arrayPop(FunctionCall const& _funCall)
{
	auto memberAccess = dynamic_cast<MemberAccess const*>(cleanExpression(_funCall.expression()));
	solAssert(memberAccess, "");
	auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
	solAssert(symbArray, "");

	makeArrayPopVerificationTarget(_funCall);

	auto oldElements = symbArray->elements();
	auto oldLength = symbArray->length();

	symbArray->increaseIndex();
	m_context.addAssertion(symbArray->elements() == oldElements);
	auto newLength = smtutil::Expression::ite(
		oldLength > 0,
		oldLength - 1,
		0
	);
	m_context.addAssertion(symbArray->length() == newLength);

	assignment(memberAccess->expression(), symbArray->currentValue());
}

void SMTEncoder::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex)
{
	if (!m_context.knownGlobalSymbol(_name))
	{
		bool abstract = m_context.createGlobalSymbol(_name, _expr);
		if (abstract)
			m_errorReporter.warning(
				1695_error,
				_expr.location(),
				"Assertion checker does not yet support this global variable."
			);
	}
	else if (_increaseIndex)
		m_context.globalSymbol(_name)->increaseIndex();
	// The default behavior is not to increase the index since
	// most of the global values stay the same throughout a tx.
	if (isSupportedType(*_expr.annotation().type))
		defineExpr(_expr, m_context.globalSymbol(_name)->currentValue());
}

bool SMTEncoder::shortcutRationalNumber(Expression const& _expr)
{
	RationalNumberType const* rationalType = isConstant(_expr);
	if (!rationalType)
		return false;

	if (rationalType->isNegative())
		defineExpr(_expr, smtutil::Expression(u2s(rationalType->literalValue(nullptr))));
	else
		defineExpr(_expr, smtutil::Expression(rationalType->literalValue(nullptr)));
	return true;
}

void SMTEncoder::arithmeticOperation(BinaryOperation const& _op)
{
	auto type = _op.annotation().commonType;
	solAssert(type, "");
	solAssert(type->category() == Type::Category::Integer || type->category() == Type::Category::FixedPoint, "");
	switch (_op.getOperator())
	{
	case Token::Add:
	case Token::Sub:
	case Token::Mul:
	case Token::Div:
	case Token::Mod:
	{
		auto values = arithmeticOperation(
			_op.getOperator(),
			expr(_op.leftExpression()),
			expr(_op.rightExpression()),
			_op.annotation().commonType,
			_op
		);
		defineExpr(_op, values.first);
		break;
	}
	default:
		m_errorReporter.warning(
			5188_error,
			_op.location(),
			"Assertion checker does not yet implement this operator."
		);
	}
}

pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
	Token _op,
	smtutil::Expression const& _left,
	smtutil::Expression const& _right,
	Type const* _commonType,
	Expression const& _operation
)
{
	static set<Token> validOperators{
		Token::Add,
		Token::Sub,
		Token::Mul,
		Token::Div,
		Token::Mod
	};
	solAssert(validOperators.count(_op), "");
	solAssert(_commonType, "");
	solAssert(
		_commonType->category() == Type::Category::Integer || _commonType->category() == Type::Category::FixedPoint,
		""
	);

	IntegerType const* intType = nullptr;
	if (auto type = dynamic_cast<IntegerType const*>(_commonType))
		intType = type;
	else
		intType = TypeProvider::uint256();

	auto valueUnbounded = [&]() -> smtutil::Expression {
		switch (_op)
		{
		case Token::Add: return _left + _right;
		case Token::Sub: return _left - _right;
		case Token::Mul: return _left * _right;
		case Token::Div: return divModWithSlacks(_left, _right, *intType).first;
		case Token::Mod: return divModWithSlacks(_left, _right, *intType).second;
		default: solAssert(false, "");
		}
	}();

	if (m_checked)
		return {valueUnbounded, valueUnbounded};

	if (_op == Token::Div || _op == Token::Mod)
	{
		// mod and unsigned division never underflow/overflow
		if (_op == Token::Mod || !intType->isSigned())
			return {valueUnbounded, valueUnbounded};

		// The only case where division overflows is
		// - type is signed
		// - LHS is type.min
		// - RHS is -1
		// the result is then -(type.min), which wraps back to type.min
		smtutil::Expression maxLeft = _left == smt::minValue(*intType);
		smtutil::Expression minusOneRight = _right == numeric_limits<size_t >::max();
		smtutil::Expression wrap = smtutil::Expression::ite(maxLeft && minusOneRight, smt::minValue(*intType), valueUnbounded);
		return {wrap, valueUnbounded};
	}

	auto symbMin = smt::minValue(*intType);
	auto symbMax = smt::maxValue(*intType);

	smtutil::Expression intValueRange = (0 - symbMin) + symbMax + 1;
	string suffix = to_string(_operation.id()) + "_" + to_string(m_context.newUniqueId());
	smt::SymbolicIntVariable k(intType, intType, "k_" + suffix, m_context);
	smt::SymbolicIntVariable m(intType, intType, "m_" + suffix, m_context);

	// To wrap around valueUnbounded in case of overflow or underflow, we replace it with a k, given:
	// 1. k + m * intValueRange = valueUnbounded
	// 2. k is in range of the desired integer type
	auto wrap = k.currentValue();
	m_context.addAssertion(valueUnbounded == (k.currentValue() + intValueRange * m.currentValue()));
	m_context.addAssertion(k.currentValue() >= symbMin);
	m_context.addAssertion(k.currentValue() <= symbMax);

	// TODO this could be refined:
	// for unsigned types it's enough to check only the upper bound.
	auto value = smtutil::Expression::ite(
		valueUnbounded > symbMax,
		wrap,
		smtutil::Expression::ite(
			valueUnbounded < symbMin,
			wrap,
			valueUnbounded
		)
	);

	return {value, valueUnbounded};
}

smtutil::Expression SMTEncoder::bitwiseOperation(
	Token _op,
	smtutil::Expression const& _left,
	smtutil::Expression const& _right,
	Type const* _commonType
)
{
	static set<Token> validOperators{
		Token::BitAnd,
		Token::BitOr,
		Token::BitXor,
		Token::SHL,
		Token::SHR,
		Token::SAR
	};
	solAssert(validOperators.count(_op), "");
	solAssert(_commonType, "");

	auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_commonType);

	auto bvLeft = smtutil::Expression::int2bv(_left, bvSize);
	auto bvRight = smtutil::Expression::int2bv(_right, bvSize);

	optional<smtutil::Expression> result;
	switch (_op)
	{
		case Token::BitAnd:
			result = bvLeft & bvRight;
			break;
		case Token::BitOr:
			result = bvLeft | bvRight;
			break;
		case Token::BitXor:
			result = bvLeft ^ bvRight;
			break;
		case Token::SHL:
			result = bvLeft << bvRight;
			break;
		case Token::SHR:
			result = bvLeft >> bvRight;
			break;
		case Token::SAR:
			result = isSigned ?
				smtutil::Expression::ashr(bvLeft, bvRight) :
				bvLeft >> bvRight;
			break;
		default:
			solAssert(false, "");
	}

	solAssert(result.has_value(), "");
	return smtutil::Expression::bv2int(*result, isSigned);
}

void SMTEncoder::compareOperation(BinaryOperation const& _op)
{
	auto commonType = _op.annotation().commonType;
	solAssert(commonType, "");

	if (isSupportedType(*commonType))
	{
		smtutil::Expression left(expr(_op.leftExpression(), commonType));
		smtutil::Expression right(expr(_op.rightExpression(), commonType));
		Token op = _op.getOperator();
		shared_ptr<smtutil::Expression> value;
		if (smt::isNumber(*commonType))
		{
			value = make_shared<smtutil::Expression>(
				op == Token::Equal ? (left == right) :
				op == Token::NotEqual ? (left != right) :
				op == Token::LessThan ? (left < right) :
				op == Token::LessThanOrEqual ? (left <= right) :
				op == Token::GreaterThan ? (left > right) :
				/*op == Token::GreaterThanOrEqual*/ (left >= right)
			);
		}
		else // Bool
		{
			solUnimplementedAssert(smt::isBool(*commonType), "Operation not yet supported");
			value = make_shared<smtutil::Expression>(
				op == Token::Equal ? (left == right) :
				/*op == Token::NotEqual*/ (left != right)
			);
		}
		// TODO: check that other values for op are not possible.
		defineExpr(_op, *value);
	}
	else
		m_errorReporter.warning(
			7229_error,
			_op.location(),
			"Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for comparisons"
		);
}

void SMTEncoder::booleanOperation(BinaryOperation const& _op)
{
	solAssert(_op.getOperator() == Token::And || _op.getOperator() == Token::Or, "");
	solAssert(_op.annotation().commonType, "");
	solAssert(_op.annotation().commonType->category() == Type::Category::Bool, "");
	// @TODO check that both of them are not constant
	_op.leftExpression().accept(*this);
	if (_op.getOperator() == Token::And)
	{
		auto indicesAfterSecond = visitBranch(&_op.rightExpression(), expr(_op.leftExpression())).first;
		mergeVariables(!expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond);
		defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression()));
	}
	else
	{
		auto indicesAfterSecond = visitBranch(&_op.rightExpression(), !expr(_op.leftExpression())).first;
		mergeVariables(expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond);
		defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression()));
	}
}

void SMTEncoder::bitwiseOperation(BinaryOperation const& _op)
{
	auto op = _op.getOperator();
	solAssert(TokenTraits::isBitOp(op) || TokenTraits::isShiftOp(op), "");
	auto commonType = _op.annotation().commonType;
	solAssert(commonType, "");

	defineExpr(_op, bitwiseOperation(
		_op.getOperator(),
		expr(_op.leftExpression(), commonType),
		expr(_op.rightExpression(), commonType),
		commonType
	));
}

void SMTEncoder::bitwiseNotOperation(UnaryOperation const& _op)
{
	solAssert(_op.getOperator() == Token::BitNot, "");

	auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_op.annotation().type);

	auto bvOperand = smtutil::Expression::int2bv(expr(_op.subExpression(), _op.annotation().type), bvSize);
	defineExpr(_op, smtutil::Expression::bv2int(~bvOperand, isSigned));
}

pair<smtutil::Expression, smtutil::Expression> SMTEncoder::divModWithSlacks(
	smtutil::Expression _left,
	smtutil::Expression _right,
	IntegerType const& _type
)
{
	if (m_settings.divModNoSlacks)
		return {_left / _right, _left % _right};

	IntegerType const* intType = &_type;
	string suffix = "div_mod_" + to_string(m_context.newUniqueId());
	smt::SymbolicIntVariable dSymb(intType, intType, "d_" + suffix, m_context);
	smt::SymbolicIntVariable rSymb(intType, intType, "r_" + suffix, m_context);
	auto d = dSymb.currentValue();
	auto r = rSymb.currentValue();

	// x / y = d and x % y = r iff d * y + r = x and
	// either x >= 0 and 0 <= r < abs(y) (or just 0 <= r < y for unsigned)
	// or     x < 0 and -abs(y) < r <= 0
	m_context.addAssertion(((d * _right) + r) == _left);
	if (_type.isSigned())
		m_context.addAssertion(
			(_left >= 0 && 0 <= r && (_right == 0 || r < smtutil::abs(_right))) ||
			(_left < 0 && ((_right == 0 || 0 - smtutil::abs(_right) < r) && r <= 0))
		);
	else // unsigned version
		m_context.addAssertion(0 <= r && (_right == 0 || r < _right));

	auto divResult = smtutil::Expression::ite(_right == 0, 0, d);
	auto modResult = smtutil::Expression::ite(_right == 0, 0, r);
	return {divResult, modResult};
}

void SMTEncoder::assignment(Expression const& _left, smtutil::Expression const& _right)
{
	assignment(_left, _right, _left.annotation().type);
}

void SMTEncoder::assignment(
	Expression const& _left,
	smtutil::Expression const& _right,
	Type const* _type
)
{
	solAssert(
		_left.annotation().type->category() != Type::Category::Tuple,
		"Tuple assignments should be handled by tupleAssignment."
	);

	Expression const* left = cleanExpression(_left);

	if (!isSupportedType(*_type))
	{
		// Give it a new index anyway to keep the SSA scheme sound.
		if (auto varDecl = identifierToVariable(*left))
			m_context.newValue(*varDecl);
	}
	else if (auto varDecl = identifierToVariable(*left))
	{
		if (varDecl->hasReferenceOrMappingType())
			resetReferences(*varDecl);
		assignment(*varDecl, _right);
	}
	else if (
		dynamic_cast<IndexAccess const*>(left) ||
		dynamic_cast<MemberAccess const*>(left)
	)
		indexOrMemberAssignment(*left, _right);
	else if (auto const* funCall = dynamic_cast<FunctionCall const*>(left))
	{
		if (auto funType = dynamic_cast<FunctionType const*>(funCall->expression().annotation().type))
		{
			if (funType->kind() == FunctionType::Kind::ArrayPush)
			{
				auto memberAccess = dynamic_cast<MemberAccess const*>(&funCall->expression());
				solAssert(memberAccess, "");
				auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
				solAssert(symbArray, "");

				auto oldLength = symbArray->length();
				auto store = smtutil::Expression::store(
					symbArray->elements(),
					symbArray->length() - 1,
					_right
				);
				symbArray->increaseIndex();
				m_context.addAssertion(symbArray->elements() == store);
				m_context.addAssertion(symbArray->length() == oldLength);
				assignment(memberAccess->expression(), symbArray->currentValue());
			}
			else if (funType->kind() == FunctionType::Kind::Internal)
			{
				for (auto type: replaceUserTypes(funType->returnParameterTypes()))
					if (type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type))
						resetReferences(type);
			}
		}
	}
	else
		solAssert(false, "");
}

void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _right)
{
	auto lTuple = dynamic_cast<TupleExpression const*>(innermostTuple(_left));
	solAssert(lTuple, "");
	Expression const* right = innermostTuple(_right);

	auto const& lComponents = lTuple->components();

	// If both sides are tuple expressions, we individually and potentially
	// recursively assign each pair of components.
	// This is because of potential type conversion.
	if (auto rTuple = dynamic_cast<TupleExpression const*>(right))
	{
		auto const& rComponents = rTuple->components();
		solAssert(lComponents.size() == rComponents.size(), "");
		for (unsigned i = 0; i < lComponents.size(); ++i)
		{
			if (!lComponents.at(i) || !rComponents.at(i))
				continue;
			auto const& lExpr = *lComponents.at(i);
			auto const& rExpr = *rComponents.at(i);
			if (lExpr.annotation().type->category() == Type::Category::Tuple)
				tupleAssignment(lExpr, rExpr);
			else
			{
				auto type = lExpr.annotation().type;
				assignment(lExpr, expr(rExpr, type), type);
			}
		}
	}
	else
	{
		auto rType = dynamic_cast<TupleType const*>(right->annotation().type);
		solAssert(rType, "");

		auto const& rComponents = rType->components();
		solAssert(lComponents.size() == rComponents.size(), "");

		auto symbRight = expr(*right);
		solAssert(symbRight.sort->kind == smtutil::Kind::Tuple, "");

		for (unsigned i = 0; i < lComponents.size(); ++i)
			if (auto component = lComponents.at(i); component && rComponents.at(i))
				assignment(*component, smtutil::Expression::tuple_get(symbRight, i), component->annotation().type);
	}
}

smtutil::Expression SMTEncoder::compoundAssignment(Assignment const& _assignment)
{
	static map<Token, Token> const compoundToArithmetic{
		{Token::AssignAdd, Token::Add},
		{Token::AssignSub, Token::Sub},
		{Token::AssignMul, Token::Mul},
		{Token::AssignDiv, Token::Div},
		{Token::AssignMod, Token::Mod}
	};
	static map<Token, Token> const compoundToBitwise{
		{Token::AssignBitAnd, Token::BitAnd},
		{Token::AssignBitOr, Token::BitOr},
		{Token::AssignBitXor, Token::BitXor},
		{Token::AssignShl, Token::SHL},
		{Token::AssignShr, Token::SHR},
		{Token::AssignSar, Token::SAR}
	};
	Token op = _assignment.assignmentOperator();
	solAssert(compoundToArithmetic.count(op) || compoundToBitwise.count(op), "");

	auto decl = identifierToVariable(_assignment.leftHandSide());

	if (compoundToBitwise.count(op))
		return bitwiseOperation(
			compoundToBitwise.at(op),
			decl ? currentValue(*decl) : expr(_assignment.leftHandSide(), _assignment.annotation().type),
			expr(_assignment.rightHandSide(), _assignment.annotation().type),
			_assignment.annotation().type
		);

	auto values = arithmeticOperation(
		compoundToArithmetic.at(op),
		decl ? currentValue(*decl) : expr(_assignment.leftHandSide(), _assignment.annotation().type),
		expr(_assignment.rightHandSide(), _assignment.annotation().type),
		_assignment.annotation().type,
		_assignment
	);
	return values.first;
}

void SMTEncoder::expressionToTupleAssignment(vector<shared_ptr<VariableDeclaration>> const& _variables, Expression const& _rhs)
{
	auto symbolicVar = m_context.expression(_rhs);
	if (_variables.size() > 1)
	{
		auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(symbolicVar);
		solAssert(symbTuple, "");
		auto const& symbComponents = symbTuple->components();
		solAssert(symbComponents.size() == _variables.size(), "");
		auto tupleType = dynamic_cast<TupleType const*>(_rhs.annotation().type);
		solAssert(tupleType, "");
		auto const& typeComponents = tupleType->components();
		solAssert(typeComponents.size() == symbComponents.size(), "");
		for (unsigned i = 0; i < symbComponents.size(); ++i)
		{
			auto param = _variables.at(i);
			if (param)
			{
				solAssert(m_context.knownVariable(*param), "");
				assignment(*param, symbTuple->component(i, typeComponents[i], param->type()));
			}
		}
	}
	else if (_variables.size() == 1)
	{
		auto const& var = *_variables.front();
		solAssert(m_context.knownVariable(var), "");
		assignment(var, _rhs);
	}
}

void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression const& _value)
{
	// In general, at this point, the SMT sorts of _variable and _value are the same,
	// even if there is implicit conversion.
	// This is a special case where the SMT sorts are different.
	// For now we are unaware of other cases where this happens, but if they do appear
	// we should extract this into an `implicitConversion` function.
	assignment(_variable, expr(_value, _variable.type()));
}

void SMTEncoder::assignment(VariableDeclaration const& _variable, smtutil::Expression const& _value)
{
	Type const* type = _variable.type();
	if (type->category() == Type::Category::Mapping)
		arrayAssignment();
	assignment(*m_context.variable(_variable), _value);
}

void SMTEncoder::assignment(smt::SymbolicVariable& _symVar, smtutil::Expression const& _value)
{
	m_context.addAssertion(_symVar.increaseIndex() == _value);
}

pair<SMTEncoder::VariableIndices, smtutil::Expression> SMTEncoder::visitBranch(
	ASTNode const* _statement,
	smtutil::Expression _condition
)
{
	return visitBranch(_statement, &_condition);
}

pair<SMTEncoder::VariableIndices, smtutil::Expression> SMTEncoder::visitBranch(
	ASTNode const* _statement,
	smtutil::Expression const* _condition
)
{
	auto indicesBeforeBranch = copyVariableIndices();
	if (_condition)
		pushPathCondition(*_condition);
	_statement->accept(*this);
	auto pathConditionOnExit = currentPathConditions();
	if (_condition)
		popPathCondition();
	auto indicesAfterBranch = copyVariableIndices();
	resetVariableIndices(indicesBeforeBranch);
	return {indicesAfterBranch, pathConditionOnExit};
}

void SMTEncoder::initializeFunctionCallParameters(CallableDeclaration const& _function, vector<smtutil::Expression> const& _callArgs)
{
	auto const& funParams = _function.parameters();
	solAssert(funParams.size() == _callArgs.size(), "");
	for (unsigned i = 0; i < funParams.size(); ++i)
		if (createVariable(*funParams[i]))
		{
			m_context.addAssertion(_callArgs[i] == m_context.newValue(*funParams[i]));
			if (funParams[i]->annotation().type->category() == Type::Category::Mapping)
				m_arrayAssignmentHappened = true;
		}

	vector<VariableDeclaration const*> localVars;
	if (auto const* fun = dynamic_cast<FunctionDefinition const*>(&_function))
		localVars = localVariablesIncludingModifiers(*fun, m_currentContract);
	else
		localVars = _function.localVariables();
	for (auto const& variable: localVars)
		if (createVariable(*variable))
		{
			m_context.newValue(*variable);
			m_context.setZeroValue(*variable);
		}

	if (_function.returnParameterList())
		for (auto const& retParam: _function.returnParameters())
			if (createVariable(*retParam))
			{
				m_context.newValue(*retParam);
				m_context.setZeroValue(*retParam);
			}
}

void SMTEncoder::createStateVariables(ContractDefinition const& _contract)
{
	for (auto var: stateVariablesIncludingInheritedAndPrivate(_contract))
		createVariable(*var);
}

void SMTEncoder::initializeStateVariables(ContractDefinition const& _contract)
{
	for (auto var: _contract.stateVariables())
	{
		solAssert(m_context.knownVariable(*var), "");
		m_context.setZeroValue(*var);
	}

	for (auto var: _contract.stateVariables())
		if (var->value())
		{
			var->value()->accept(*this);
			assignment(*var, *var->value());
		}
}

void SMTEncoder::createLocalVariables(FunctionDefinition const& _function)
{
	for (auto const& variable: localVariablesIncludingModifiers(_function, m_currentContract))
		createVariable(*variable);

	for (auto const& param: _function.parameters())
		createVariable(*param);

	if (_function.returnParameterList())
		for (auto const& retParam: _function.returnParameters())
			createVariable(*retParam);
}

void SMTEncoder::initializeLocalVariables(FunctionDefinition const& _function)
{
	for (auto const& variable: localVariablesIncludingModifiers(_function, m_currentContract))
	{
		solAssert(m_context.knownVariable(*variable), "");
		m_context.setZeroValue(*variable);
	}

	for (auto const& param: _function.parameters())
	{
		solAssert(m_context.knownVariable(*param), "");
		m_context.setUnknownValue(*param);
	}

	if (_function.returnParameterList())
		for (auto const& retParam: _function.returnParameters())
		{
			solAssert(m_context.knownVariable(*retParam), "");
			m_context.setZeroValue(*retParam);
		}
}

void SMTEncoder::resetStateVariables()
{
	m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.isStateVariable(); });
}

void SMTEncoder::resetMemoryVariables()
{
	m_context.resetVariables([&](VariableDeclaration const& _variable) {
		return _variable.referenceLocation() == VariableDeclaration::Location::Memory;
	});
}

void SMTEncoder::resetStorageVariables()
{
	m_context.resetVariables([&](VariableDeclaration const& _variable) {
		return _variable.referenceLocation() == VariableDeclaration::Location::Storage || _variable.isStateVariable();
	});
}

void SMTEncoder::resetBalances()
{
	state().newBalances();
}

void SMTEncoder::resetReferences(VariableDeclaration const& _varDecl)
{
	m_context.resetVariables([&](VariableDeclaration const& _var) {
		if (_var == _varDecl)
			return false;

		// If both are state variables no need to clear knowledge.
		if (_var.isStateVariable() && _varDecl.isStateVariable())
			return false;

		return sameTypeOrSubtype(_var.type(), _varDecl.type());
	});
}

void SMTEncoder::resetReferences(Type const* _type)
{
	m_context.resetVariables([&](VariableDeclaration const& _var) {
		return sameTypeOrSubtype(_var.type(), _type);
	});
}

bool SMTEncoder::sameTypeOrSubtype(Type const* _a, Type const* _b)
{
	Type const* prefix = _a;
	while (
		prefix->category() == Type::Category::Mapping ||
		prefix->category() == Type::Category::Array
	)
	{
		if (*typeWithoutPointer(_b) == *typeWithoutPointer(prefix))
			return true;
		if (prefix->category() == Type::Category::Mapping)
		{
			auto mapPrefix = dynamic_cast<MappingType const*>(prefix);
			solAssert(mapPrefix, "");
			prefix = mapPrefix->valueType();
		}
		else
		{
			auto arrayPrefix = dynamic_cast<ArrayType const*>(prefix);
			solAssert(arrayPrefix, "");
			prefix = arrayPrefix->baseType();
		}
	}
	return false;
}

bool SMTEncoder::isSupportedType(Type const& _type) const
{
	return smt::isSupportedType(*underlyingType(&_type));
}

Type const* SMTEncoder::typeWithoutPointer(Type const* _type)
{
	if (auto refType = dynamic_cast<ReferenceType const*>(_type))
		return TypeProvider::withLocationIfReference(refType->location(), _type);
	return _type;
}

void SMTEncoder::mergeVariables(smtutil::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse)
{
	for (auto const& entry: _indicesEndTrue)
	{
		VariableDeclaration const* var = entry.first;
		auto trueIndex = entry.second;
		if (_indicesEndFalse.count(var) && _indicesEndFalse.at(var) != trueIndex)
		{
			m_context.addAssertion(m_context.newValue(*var) == smtutil::Expression::ite(
				_condition,
				valueAtIndex(*var, trueIndex),
				valueAtIndex(*var, _indicesEndFalse.at(var)))
			);
		}
	}
}

smtutil::Expression SMTEncoder::currentValue(VariableDeclaration const& _decl) const
{
	solAssert(m_context.knownVariable(_decl), "");
	return m_context.variable(_decl)->currentValue();
}

smtutil::Expression SMTEncoder::valueAtIndex(VariableDeclaration const& _decl, unsigned _index) const
{
	solAssert(m_context.knownVariable(_decl), "");
	return m_context.variable(_decl)->valueAtIndex(_index);
}

bool SMTEncoder::createVariable(VariableDeclaration const& _varDecl)
{
	if (m_context.knownVariable(_varDecl))
		return true;
	bool abstract = m_context.createVariable(_varDecl);
	if (abstract)
	{
		m_errorReporter.warning(
			8115_error,
			_varDecl.location(),
			"Assertion checker does not yet support the type of this variable."
		);
		return false;
	}
	return true;
}

smtutil::Expression SMTEncoder::expr(Expression const& _e, Type const* _targetType)
{
	if (!m_context.knownExpression(_e))
	{
		m_errorReporter.warning(6031_error, _e.location(), "Internal error: Expression undefined for SMT solver." );
		createExpr(_e);
	}

	return m_context.expression(_e)->currentValue(underlyingType(_targetType));
}

void SMTEncoder::createExpr(Expression const& _e)
{
	bool abstract = m_context.createExpression(_e);
	if (abstract)
		m_errorReporter.warning(
			8364_error,
			_e.location(),
			"Assertion checker does not yet implement type " + _e.annotation().type->toString()
		);
}

void SMTEncoder::defineExpr(Expression const& _e, smtutil::Expression _value)
{
	auto type = _e.annotation().type;
	createExpr(_e);
	solAssert(_value.sort->kind != smtutil::Kind::Function, "Equality operator applied to type that is not fully supported");
	if (!smt::isInaccessibleDynamic(*type))
		m_context.addAssertion(expr(_e) == _value);

	if (m_checked && smt::isNumber(*type))
		m_context.addAssertion(smtutil::Expression::implies(
			currentPathConditions(),
			smt::symbolicUnknownConstraints(expr(_e), type)
		));
}

void SMTEncoder::defineExpr(Expression const& _e, vector<optional<smtutil::Expression>> const& _values)
{
	if (_values.size() == 1 && _values.front())
	{
		defineExpr(_e, *_values.front());
		return;
	}
	auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_e));
	solAssert(symbTuple, "");
	symbTuple->increaseIndex();
	auto const& symbComponents = symbTuple->components();
	solAssert(symbComponents.size() == _values.size(), "");
	auto tupleType = dynamic_cast<TupleType const*>(_e.annotation().type);
	solAssert(tupleType, "");
	solAssert(tupleType->components().size() == symbComponents.size(), "");
	for (unsigned i = 0; i < symbComponents.size(); ++i)
		if (_values[i] && !smt::isInaccessibleDynamic(*tupleType->components()[i]))
			m_context.addAssertion(symbTuple->component(i) == *_values[i]);
}

void SMTEncoder::popPathCondition()
{
	solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty.");
	m_pathConditions.pop_back();
}

void SMTEncoder::pushPathCondition(smtutil::Expression const& _e)
{
	m_pathConditions.push_back(currentPathConditions() && _e);
}

void SMTEncoder::setPathCondition(smtutil::Expression const& _e)
{
	if (m_pathConditions.empty())
		m_pathConditions.push_back(_e);
	else
		m_pathConditions.back() = _e;
}

smtutil::Expression SMTEncoder::currentPathConditions()
{
	if (m_pathConditions.empty())
		return smtutil::Expression(true);
	return m_pathConditions.back();
}

SecondarySourceLocation SMTEncoder::callStackMessage(vector<CallStackEntry> const& _callStack)
{
	SecondarySourceLocation callStackLocation;
	solAssert(!_callStack.empty(), "");
	callStackLocation.append("Callstack:", SourceLocation());
	for (auto const& call: _callStack | ranges::views::reverse)
		if (call.second)
			callStackLocation.append("", call.second->location());
	return callStackLocation;
}

pair<CallableDeclaration const*, ASTNode const*> SMTEncoder::popCallStack()
{
	solAssert(!m_callStack.empty(), "");
	auto lastCalled = m_callStack.back();
	m_callStack.pop_back();
	return lastCalled;
}

void SMTEncoder::pushCallStack(CallStackEntry _entry)
{
	m_callStack.push_back(_entry);
}

void SMTEncoder::addPathImpliedExpression(smtutil::Expression const& _e)
{
	m_context.addAssertion(smtutil::Expression::implies(currentPathConditions(), _e));
}

bool SMTEncoder::isRootFunction()
{
	return m_callStack.size() == 1;
}

bool SMTEncoder::visitedFunction(FunctionDefinition const* _funDef)
{
	for (auto const& call: m_callStack)
		if (call.first == _funDef)
			return true;
	return false;
}

ContractDefinition const* SMTEncoder::currentScopeContract()
{
	for (auto&& f: m_callStack | ranges::views::reverse | ranges::views::keys)
		if (auto fun = dynamic_cast<FunctionDefinition const*>(f))
			return fun->annotation().contract;
	return nullptr;
}

SMTEncoder::VariableIndices SMTEncoder::copyVariableIndices()
{
	VariableIndices indices;
	for (auto const& var: m_context.variables())
		indices.emplace(var.first, var.second->index());
	return indices;
}

void SMTEncoder::resetVariableIndices(VariableIndices const& _indices)
{
	for (auto const& var: _indices)
		m_context.variable(*var.first)->setIndex(var.second);
}

void SMTEncoder::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function)
{
	solAssert(_contract, "");
	for (auto var: stateVariablesIncludingInheritedAndPrivate(*_contract))
		m_context.variable(*var)->resetIndex();
	if (_function)
	{
		for (auto const& var: _function->parameters() + _function->returnParameters())
			m_context.variable(*var)->resetIndex();
		for (auto const& var: localVariablesIncludingModifiers(*_function, _contract))
			m_context.variable(*var)->resetIndex();
	}
	state().reset();
}

Expression const* SMTEncoder::leftmostBase(IndexAccess const& _indexAccess)
{
	Expression const* base = &_indexAccess.baseExpression();
	while (auto access = dynamic_cast<IndexAccess const*>(base))
		base = &access->baseExpression();
	return base;
}

Type const* SMTEncoder::keyType(Type const* _type)
{
	if (auto const* mappingType = dynamic_cast<MappingType const*>(_type))
		return mappingType->keyType();
	if (
		dynamic_cast<ArrayType const*>(_type) ||
		dynamic_cast<ArraySliceType const*>(_type)
	)
		return TypeProvider::uint256();
	else
		solAssert(false, "");
}

Expression const* SMTEncoder::innermostTuple(Expression const& _expr)
{
	auto const* tuple = dynamic_cast<TupleExpression const*>(&_expr);
	if (!tuple || tuple->isInlineArray())
		return &_expr;

	Expression const* expr = tuple;
	while (tuple && !tuple->isInlineArray() && tuple->components().size() == 1)
	{
		expr = tuple->components().front().get();
		tuple = dynamic_cast<TupleExpression const*>(expr);
	}
	solAssert(expr, "");
	return expr;
}

Type const* SMTEncoder::underlyingType(Type const* _type)
{
	if (auto userType = dynamic_cast<UserDefinedValueType const*>(_type))
		_type = &userType->underlyingType();
	return _type;
}

TypePointers SMTEncoder::replaceUserTypes(TypePointers const& _types)
{
	return applyMap(_types, [](auto _type) {
		if (auto userType = dynamic_cast<UserDefinedValueType const*>(_type))
			return &userType->underlyingType();
		return _type;
	});
}

pair<Expression const*, FunctionCallOptions const*> SMTEncoder::functionCallExpression(FunctionCall const& _funCall)
{
	Expression const* callExpr = &_funCall.expression();
	auto const* callOptions = dynamic_cast<FunctionCallOptions const*>(callExpr);
	if (callOptions)
		callExpr = &callOptions->expression();

	return {callExpr, callOptions};
}

Expression const* SMTEncoder::cleanExpression(Expression const& _expr)
{
	auto const* expr = &_expr;
	if (auto const* tuple = dynamic_cast<TupleExpression const*>(expr))
		return cleanExpression(*innermostTuple(*tuple));
	if (auto const* functionCall = dynamic_cast<FunctionCall const*>(expr))
		if (*functionCall->annotation().kind == FunctionCallKind::TypeConversion)
		{
			auto typeType = dynamic_cast<TypeType const*>(functionCall->expression().annotation().type);
			solAssert(typeType, "");
			if (auto const* arrayType = dynamic_cast<ArrayType const*>(typeType->actualType()))
				if (arrayType->isByteArray())
				{
					// this is a cast to `bytes`
					solAssert(functionCall->arguments().size() == 1, "");
					Expression const& arg = *functionCall->arguments()[0];
					if (
						auto const* argArrayType = dynamic_cast<ArrayType const*>(arg.annotation().type);
						argArrayType && argArrayType->isByteArray()
					)
						return cleanExpression(arg);
				}
		}
	solAssert(expr, "");
	return expr;
}

set<VariableDeclaration const*> SMTEncoder::touchedVariables(ASTNode const& _node)
{
	vector<CallableDeclaration const*> callStack;
	for (auto const& call: m_callStack)
		callStack.push_back(call.first);
	return m_variableUsage.touchedVariables(_node, callStack);
}

Declaration const* SMTEncoder::expressionToDeclaration(Expression const& _expr) const
{
	if (auto const* identifier = dynamic_cast<Identifier const*>(&_expr))
		return identifier->annotation().referencedDeclaration;
	if (auto const* outerMemberAccess = dynamic_cast<MemberAccess const*>(&_expr))
		return outerMemberAccess->annotation().referencedDeclaration;
	return nullptr;
}

VariableDeclaration const* SMTEncoder::identifierToVariable(Expression const& _expr) const
{
	// We do not use `expressionToDeclaration` here because we are not interested in
	// struct.field, for example.
	if (auto const* identifier = dynamic_cast<Identifier const*>(&_expr))
		if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
		{
			solAssert(m_context.knownVariable(*varDecl), "");
			return varDecl;
		}
	// But we are interested in "contract.var", because that is the same as just "var".
	if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(&_expr))
		if (dynamic_cast<ContractDefinition const*>(expressionToDeclaration(memberAccess->expression())))
			if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration))
			{
				solAssert(m_context.knownVariable(*varDecl), "");
				return varDecl;
			}

	return nullptr;
}

MemberAccess const* SMTEncoder::isEmptyPush(Expression const& _expr) const
{
	if (
		auto const* funCall = dynamic_cast<FunctionCall const*>(&_expr);
		funCall && funCall->arguments().empty()
	)
	{
		auto const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
		if (funType.kind() == FunctionType::Kind::ArrayPush)
			return &dynamic_cast<MemberAccess const&>(funCall->expression());
	}
	return nullptr;
}

bool SMTEncoder::isPublicGetter(Expression const& _expr) {
	if (!isTrustedExternalCall(&_expr))
		return false;
	auto varDecl = dynamic_cast<VariableDeclaration const*>(
		dynamic_cast<MemberAccess const&>(_expr).annotation().referencedDeclaration
	);
	return varDecl != nullptr;
}

bool SMTEncoder::isTrustedExternalCall(Expression const* _expr) {
	auto memberAccess = dynamic_cast<MemberAccess const*>(_expr);
	if (!memberAccess)
		return false;

	auto identifier = dynamic_cast<Identifier const*>(&memberAccess->expression());
	return identifier &&
		identifier->name() == "this" &&
		identifier->annotation().referencedDeclaration &&
		dynamic_cast<MagicVariableDeclaration const*>(identifier->annotation().referencedDeclaration)
	;
}

string SMTEncoder::extraComment()
{
	string extra;
	if (m_arrayAssignmentHappened)
		extra +=
			"\nNote that array aliasing is not supported,"
			" therefore all mapping information is erased after"
			" a mapping local variable/parameter is assigned.\n"
			"You can re-introduce information using require().";
	return extra;
}

FunctionDefinition const* SMTEncoder::functionCallToDefinition(
	FunctionCall const& _funCall,
	ContractDefinition const* _scopeContract,
	ContractDefinition const* _contextContract
)
{
	if (*_funCall.annotation().kind != FunctionCallKind::FunctionCall)
		return {};

	auto [calledExpr, callOptions] = functionCallExpression(_funCall);

	if (TupleExpression const* fun = dynamic_cast<TupleExpression const*>(calledExpr))
	{
		solAssert(fun->components().size() == 1, "");
		calledExpr = innermostTuple(*calledExpr);
	}

	auto resolveVirtual = [&](auto const* _ref) -> FunctionDefinition const* {
		VirtualLookup lookup = *_ref->annotation().requiredLookup;
		solAssert(_contextContract || lookup == VirtualLookup::Static, "No contract context provided for function lookup resolution!");
		auto funDef = dynamic_cast<FunctionDefinition const*>(_ref->annotation().referencedDeclaration);
		if (!funDef)
			return funDef;
		switch (lookup)
		{
		case VirtualLookup::Virtual:
			return &(funDef->resolveVirtual(*_contextContract));
		case VirtualLookup::Super:
		{
			solAssert(_scopeContract, "");
			auto super = _scopeContract->superContract(*_contextContract);
			solAssert(super, "Super contract not available.");
			return &funDef->resolveVirtual(*_contextContract, super);
		}
		case VirtualLookup::Static:
			return funDef;
		}
		solAssert(false, "");
	};

	if (Identifier const* fun = dynamic_cast<Identifier const*>(calledExpr))
		return resolveVirtual(fun);
	else if (MemberAccess const* fun = dynamic_cast<MemberAccess const*>(calledExpr))
		return resolveVirtual(fun);

	return {};
}

vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
{
	return fold(
		_contract.annotation().linearizedBaseContracts,
		vector<VariableDeclaration const*>{},
		[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
	);
}

vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function)
{
	return stateVariablesIncludingInheritedAndPrivate(dynamic_cast<ContractDefinition const&>(*_function.scope()));
}

vector<VariableDeclaration const*> SMTEncoder::localVariablesIncludingModifiers(FunctionDefinition const& _function, ContractDefinition const* _contract)
{
	return _function.localVariables() + tryCatchVariables(_function) + modifiersVariables(_function, _contract);
}

vector<VariableDeclaration const*> SMTEncoder::tryCatchVariables(FunctionDefinition const& _function)
{
	struct TryCatchVarsVisitor : public ASTConstVisitor
	{
		bool visit(TryCatchClause const& _catchClause) override
		{
			if (_catchClause.parameters())
			{
				auto const& params = _catchClause.parameters()->parameters();
				for (auto param: params)
					vars.push_back(param.get());
			}

			return true;
		}

		vector<VariableDeclaration const*> vars;
	} tryCatchVarsVisitor;
	_function.accept(tryCatchVarsVisitor);
	return tryCatchVarsVisitor.vars;
}

vector<VariableDeclaration const*> SMTEncoder::modifiersVariables(FunctionDefinition const& _function, ContractDefinition const* _contract)
{
	struct BlockVars: ASTConstVisitor
	{
		BlockVars(Block const& _block) { _block.accept(*this); }
		void endVisit(VariableDeclaration const& _var) { vars.push_back(&_var); }
		vector<VariableDeclaration const*> vars;
	};

	vector<VariableDeclaration const*> vars;
	set<ModifierDefinition const*> visited;
	for (auto invok: _function.modifiers())
	{
		if (!invok)
			continue;
		auto const* modifier = resolveModifierInvocation(*invok, _contract);
		if (!modifier || visited.count(modifier))
			continue;

		visited.insert(modifier);
		if (modifier->isImplemented())
		{
			vars += applyMap(modifier->parameters(), [](auto _var) { return _var.get(); });
			vars += BlockVars(modifier->body()).vars;
		}
	}
	return vars;
}

ModifierDefinition const* SMTEncoder::resolveModifierInvocation(ModifierInvocation const& _invocation, ContractDefinition const* _contract)
{
	auto const* modifier = dynamic_cast<ModifierDefinition const*>(_invocation.name().annotation().referencedDeclaration);
	if (modifier)
	{
		VirtualLookup lookup = *_invocation.name().annotation().requiredLookup;
		solAssert(lookup == VirtualLookup::Virtual || lookup == VirtualLookup::Static, "");
		solAssert(_contract || lookup == VirtualLookup::Static, "No contract context provided for modifier lookup resolution!");
		if (lookup == VirtualLookup::Virtual)
			modifier = &modifier->resolveVirtual(*_contract);
	}
	return modifier;
}

set<FunctionDefinition const*, ASTNode::CompareByID> const& SMTEncoder::contractFunctions(ContractDefinition const& _contract)
{
	if (!m_contractFunctions.count(&_contract))
	{
		auto const& functions = _contract.definedFunctions();
		set<FunctionDefinition const*, ASTNode::CompareByID> resolvedFunctions(begin(functions), end(functions));
		for (auto const* base: _contract.annotation().linearizedBaseContracts)
		{
			if (base == &_contract)
				continue;
			for (auto const* baseFunction: base->definedFunctions())
			{
				if (baseFunction->isConstructor()) // We don't want to include constructors of parent contracts
					continue;
				bool overridden = false;
				for (auto const* function: resolvedFunctions)
					if (
						function->name() == baseFunction->name() &&
						function->kind() == baseFunction->kind() &&
						FunctionType(*function).asExternallyCallableFunction(false)->
							hasEqualParameterTypes(*FunctionType(*baseFunction).asExternallyCallableFunction(false))
						)
					{
						overridden = true;
						break;
					}
				if (!overridden)
					resolvedFunctions.insert(baseFunction);
			}
		}
		m_contractFunctions.emplace(&_contract, move(resolvedFunctions));
	}
	return m_contractFunctions.at(&_contract);
}

set<FunctionDefinition const*, ASTNode::CompareByID> const& SMTEncoder::contractFunctionsWithoutVirtual(ContractDefinition const& _contract)
{
	if (!m_contractFunctionsWithoutVirtual.count(&_contract))
	{
		auto allFunctions = contractFunctions(_contract);
		for (auto const* base: _contract.annotation().linearizedBaseContracts)
			for (auto const* baseFun: base->definedFunctions())
				allFunctions.insert(baseFun);

		m_contractFunctionsWithoutVirtual.emplace(&_contract, move(allFunctions));

	}
	return m_contractFunctionsWithoutVirtual.at(&_contract);
}

SourceUnit const* SMTEncoder::sourceUnitContaining(Scopable const& _scopable)
{
	for (auto const* s = &_scopable; s; s = dynamic_cast<Scopable const*>(s->scope()))
		if (auto const* source = dynamic_cast<SourceUnit const*>(s->scope()))
			return source;
	solAssert(false, "");
}

map<ContractDefinition const*, vector<ASTPointer<frontend::Expression>>> SMTEncoder::baseArguments(ContractDefinition const& _contract)
{
	map<ContractDefinition const*, vector<ASTPointer<Expression>>> baseArgs;

	for (auto contract: _contract.annotation().linearizedBaseContracts)
	{
		/// Collect base contracts and potential constructor arguments.
		for (auto specifier: contract->baseContracts())
		{
			solAssert(specifier, "");
			auto const& base = dynamic_cast<ContractDefinition const&>(*specifier->name().annotation().referencedDeclaration);
			if (auto args = specifier->arguments())
				baseArgs[&base] = *args;
		}
		/// Collect base constructor arguments given as constructor modifiers.
		if (auto constructor = contract->constructor())
			for (auto mod: constructor->modifiers())
			{
				auto decl = mod->name().annotation().referencedDeclaration;
				if (auto base = dynamic_cast<ContractDefinition const*>(decl))
				{
					solAssert(!baseArgs.count(base), "");
					if (auto args = mod->arguments())
						baseArgs[base] = *args;
				}
			}
	}

	return baseArgs;
}

RationalNumberType const* SMTEncoder::isConstant(Expression const& _expr)
{
	if (auto type = dynamic_cast<RationalNumberType const*>(_expr.annotation().type))
		return type;

	// _expr may not be constant evaluable.
	// In that case we ignore any warnings emitted by the constant evaluator,
	// as it will return nullptr in case of failure.
	ErrorList l;
	ErrorReporter e(l);
	if (auto t = ConstantEvaluator::evaluate(e, _expr))
		return TypeProvider::rationalNumber(t->value);

	return nullptr;
}

set<FunctionCall const*> SMTEncoder::collectABICalls(ASTNode const* _node)
{
	struct ABIFunctions: public ASTConstVisitor
	{
		ABIFunctions(ASTNode const* _node) { _node->accept(*this); }
		void endVisit(FunctionCall const& _funCall)
		{
			if (*_funCall.annotation().kind == FunctionCallKind::FunctionCall)
				switch (dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type).kind())
				{
				case FunctionType::Kind::ABIEncode:
				case FunctionType::Kind::ABIEncodePacked:
				case FunctionType::Kind::ABIEncodeWithSelector:
				case FunctionType::Kind::ABIEncodeWithSignature:
				case FunctionType::Kind::ABIDecode:
					abiCalls.insert(&_funCall);
					break;
				default: {}
				}
		}

		set<FunctionCall const*> abiCalls;
	};

	return ABIFunctions(_node).abiCalls;
}

set<SourceUnit const*, ASTNode::CompareByID> SMTEncoder::sourceDependencies(SourceUnit const& _source)
{
	set<SourceUnit const*, ASTNode::CompareByID> sources;
	sources.insert(&_source);
	for (auto const& source: _source.referencedSourceUnits(true))
		sources.insert(source);
	return sources;
}

void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall, ContractDefinition const* _contextContract)
{
	auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), _contextContract);
	if (!funDef)
		return;

	auto const& returnParams = funDef->returnParameters();
	for (auto param: returnParams)
		createVariable(*param);
	auto returnValues = applyMap(returnParams, [this](auto const& param) -> optional<smtutil::Expression> {
		solAssert(param && m_context.knownVariable(*param), "");
		return currentValue(*param);
	});
	defineExpr(_funCall, returnValues);
}

vector<smtutil::Expression> SMTEncoder::symbolicArguments(FunctionCall const& _funCall, ContractDefinition const* _contextContract)
{
	auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), _contextContract);
	solAssert(funDef, "");

	vector<smtutil::Expression> args;
	Expression const* calledExpr = &_funCall.expression();
	auto funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type);
	solAssert(funType, "");

	vector<ASTPointer<Expression const>> arguments = _funCall.sortedArguments();
	auto functionParams = funDef->parameters();
	unsigned firstParam = 0;
	if (funType->bound())
	{
		calledExpr = innermostTuple(*calledExpr);
		auto const& boundFunction = dynamic_cast<MemberAccess const*>(calledExpr);
		solAssert(boundFunction, "");
		args.push_back(expr(boundFunction->expression(), functionParams.front()->type()));
		firstParam = 1;
	}

	solAssert((arguments.size() + firstParam) == functionParams.size(), "");
	for (unsigned i = 0; i < arguments.size(); ++i)
		args.push_back(expr(*arguments.at(i), functionParams.at(i + firstParam)->type()));

	return args;
}

smtutil::Expression SMTEncoder::constantExpr(Expression const& _expr, VariableDeclaration const& _var)
{
	if (RationalNumberType const* rationalType = isConstant(_expr))
	{
		if (rationalType->isNegative())
			return smtutil::Expression(u2s(rationalType->literalValue(nullptr)));
		else
			return smtutil::Expression(rationalType->literalValue(nullptr));
	}
	else
	{
		solAssert(_var.value(), "");
		_var.value()->accept(*this);
		return expr(*_var.value(), _expr.annotation().type);
	}
	solAssert(false, "");
}

void SMTEncoder::collectFreeFunctions(set<SourceUnit const*, ASTNode::CompareByID> const& _sources)
{
	for (auto source: _sources)
		for (auto node: source->nodes())
			if (auto function = dynamic_cast<FunctionDefinition const*>(node.get()))
				m_freeFunctions.insert(function);
			else if (
				auto contract = dynamic_cast<ContractDefinition const*>(node.get());
				contract && contract->isLibrary()
			)
			{
				for (auto function: contract->definedFunctions())
					if (!function->isPublic())
						m_freeFunctions.insert(function);
			}
}

void SMTEncoder::createFreeConstants(set<SourceUnit const*, ASTNode::CompareByID> const& _sources)
{
	for (auto source: _sources)
		for (auto node: source->nodes())
			if (auto var = dynamic_cast<VariableDeclaration const*>(node.get()))
				createVariable(*var);
			else if (
				auto contract = dynamic_cast<ContractDefinition const*>(node.get());
				contract && contract->isLibrary()
			)
				for (auto var: contract->stateVariables())
				{
					solAssert(var->isConstant(), "");
					createVariable(*var);
				}
}

smt::SymbolicState& SMTEncoder::state()
{
	return m_context.state();
}
