diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index c186ad1a0f1b..b9666889cfeb 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -379,7 +379,7 @@ void GlobalContext::addValidateMultiSignMethod() { parameterNames, returnParameterNames, FunctionType::Kind::ValidateMultiSign, - StateMutability::Pure, + StateMutability::View, nullptr) )); } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 5c8c53613482..0ce058809cdd 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1999,6 +1999,38 @@ void TypeChecker::typeCheckFunctionCall( "\"staticcall\" is not supported by the VM version." ); + static std::set const tronStaticCallKinds = { + FunctionType::Kind::ValidateMultiSign, + FunctionType::Kind::BatchValidateSign, + FunctionType::Kind::VerifyBurnProof, + FunctionType::Kind::VerifyTransferProof, + FunctionType::Kind::VerifyMintProof, + FunctionType::Kind::PedersenHash, + FunctionType::Kind::RewardBalance, + FunctionType::Kind::IsSrCandidate, + FunctionType::Kind::VoteCount, + FunctionType::Kind::UsedVoteCount, + FunctionType::Kind::ReceivedVoteCount, + FunctionType::Kind::TotalVoteCount, + FunctionType::Kind::GetChainParameter, + FunctionType::Kind::AvailableUnfreezeV2Size, + FunctionType::Kind::UnfreezableBalanceV2, + FunctionType::Kind::ExpireUnfreezeBalanceV2, + FunctionType::Kind::DelegatableResource, + FunctionType::Kind::ResourceV2, + FunctionType::Kind::CheckUnDelegateResource, + FunctionType::Kind::ResourceUsage, + FunctionType::Kind::TotalResource, + FunctionType::Kind::TotalDelegatedResource, + FunctionType::Kind::TotalAcquiredResource, + }; + if (!m_evmVersion.hasStaticCall() && tronStaticCallKinds.count(_functionType->kind())) + m_errorReporter.typeError( + 9137_error, + _functionCall.location(), + "This TRON builtin requires a Byzantium-compatible VM." + ); + // Perform standard function call type checking typeCheckFunctionGeneralChecks(_functionCall, _functionType); } @@ -3290,6 +3322,12 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) { if (magicType->kind() == MagicType::Kind::ABI) annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::Chain && !m_evmVersion.hasStaticCall()) + m_errorReporter.typeError( + 9137_error, + _memberAccess.location(), + "This TRON builtin requires a Byzantium-compatible VM." + ); else if (magicType->kind() == MagicType::Kind::MetaType && ( memberName == "creationCode" || memberName == "runtimeCode" )) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 46086f8f5d62..13fb93c3463b 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -3875,7 +3875,6 @@ bool FunctionType::isPure() const return m_kind == Kind::KECCAK256 || m_kind == Kind::ECRecover || - m_kind == Kind::ValidateMultiSign || m_kind == Kind::BatchValidateSign || m_kind == Kind::VerifyBurnProof || m_kind == Kind::VerifyTransferProof || diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 768895a31f7c..92db9d9589d9 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1646,6 +1646,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case FunctionType::Kind::Freeze: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1657,6 +1658,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::Unfreeze: { + solAssert(arguments.size() == 1 && function.parameterTypes().size() == 1, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i) { @@ -1669,6 +1671,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::FreezeExpireTime: { + solAssert(arguments.size() == 1 && function.parameterTypes().size() == 1, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i) { @@ -1679,6 +1682,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::Vote: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i) { @@ -1692,11 +1696,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::WithdrawReward: { + solAssert(arguments.empty() && function.parameterTypes().empty(), ""); m_context << Instruction::NATIVEWITHDRAWREWARD; break; } case FunctionType::Kind::FreezeBalanceV2: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1708,6 +1714,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::UnfreezeBalanceV2: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1719,16 +1726,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::CancelAllUnfreezeV2: { + solAssert(arguments.empty() && function.parameterTypes().empty(), ""); m_context << Instruction::NATIVECANCELALLUNFREEZEV2; break; } case FunctionType::Kind::WithdrawExpireUnfreeze: { + solAssert(arguments.empty() && function.parameterTypes().empty(), ""); m_context << Instruction::NATIVEWITHDRAWEXPIREUNFREEZE; break; } case FunctionType::Kind::DelegateResource: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -1740,6 +1750,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::UnDelegateResource: { + solAssert(arguments.size() == 2 && function.parameterTypes().size() == 2, ""); _functionCall.expression().accept(*this); for (unsigned i = 0; i < arguments.size(); ++i){ acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); @@ -3319,6 +3330,7 @@ void ExpressionCompiler::appendExternalFunctionCall( switch v case 0 { v := 0x60 } default { + if mod(returndatasize(), 0x20) { revert(0, 0) } v := mload(0x40) mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f)))) mstore(v, div(returndatasize(), 0x20)) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 9eb3f379378c..862ff7c9f592 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1658,7 +1658,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) std::string tokenId{expressionAsType(*arguments[1], *(parameterTypes[1]))}; Whiskers templ(R"( if iszero(lt(0xf4240, )) { revert(0, 0) } - if iszero(gt(exp(2, 63), )) { revert(0, 0) } + if iszero(gt(0x8000000000000000, )) { revert(0, 0) } let := 0 if iszero() { := } let := calltoken(,
, , , 0, 0, 0, 0) @@ -1682,7 +1682,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) std::string tokenId{expressionAsType(*arguments[0], *(parameterTypes[0]))}; Whiskers templ(R"( if iszero(lt(0xf4240, )) { revert(0, 0) } - if iszero(gt(exp(2, 63), )) { revert(0, 0) } + if iszero(gt(0x8000000000000000, )) { revert(0, 0) } let := tokenbalance(,
) )"); templ("address", address); @@ -2004,30 +2004,41 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) let := staticcall(gas(),
, , sub(, ), , ) if iszero() { () } + + if lt(returndatasize(), 0x20) { + mstore(, 0) + returndatacopy(, 0, returndatasize()) + } + - + + if mod(returndatasize(), 0x20) { revert(0, 0) } let := add(returndatasize(), 0x40) returndatacopy(add(, 0x40), 0, returndatasize()) mstore(, 0x20) mstore(add(, 0x20), div(returndatasize(), 0x20)) - + let := returndatasize() returndatacopy(, 0, ) - + let := - + if gt(, returndatasize()) { := returndatasize() } - + // update freeMemoryPointer according to dynamic return size (, ) - let := (, add(, )) + + let := mload() + + let := (, add(, )) + )"); templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); templ("pos", m_context.newYulVariable()); @@ -2040,12 +2051,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) if (returnInfo.dynamicReturnSize) solAssert(m_context.evmVersion().supportsReturndata()); - templ("supportsReturnData", m_context.evmVersion().supportsReturndata()); + bool const isLegacyCompatibleMultisig = + functionType->kind() == FunctionType::Kind::ValidateMultiSign || + functionType->kind() == FunctionType::Kind::BatchValidateSign; + templ("isLegacyCompatibleMultisig", isLegacyCompatibleMultisig); + templ("strictReturnSize", m_context.evmVersion().supportsReturndata() && !isLegacyCompatibleMultisig); templ("returnDataSizeVar", m_context.newYulVariable()); templ("staticReturndataSize", std::to_string(returnInfo.estimatedReturnSize)); templ("isReturndataSizeDynamic", returnInfo.dynamicReturnSize); - templ("isMintProof",functionType->kind() == FunctionType::Kind::VerifyMintProof ||functionType->kind() == FunctionType::Kind::VerifyTransferProof); + templ("isProof", functionType->kind() == FunctionType::Kind::VerifyMintProof || functionType->kind() == FunctionType::Kind::VerifyTransferProof); templ("finalizeAllocation", m_utils.finalizeAllocationFunction()); templ("retVars", IRVariable(_functionCall).commaSeparatedList()); templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true)); diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 95268032af0e..ad4120839a92 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -726,16 +726,66 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) " with the CHC engine." ); break; + case FunctionType::Kind::TransferToken: + case FunctionType::Kind::Freeze: + case FunctionType::Kind::Unfreeze: + case FunctionType::Kind::Vote: + case FunctionType::Kind::WithdrawReward: + case FunctionType::Kind::FreezeBalanceV2: + case FunctionType::Kind::UnfreezeBalanceV2: + case FunctionType::Kind::CancelAllUnfreezeV2: + case FunctionType::Kind::WithdrawExpireUnfreeze: + case FunctionType::Kind::DelegateResource: + case FunctionType::Kind::UnDelegateResource: + // These operations mutate TRON account or resource state. Since that state is + // not modeled explicitly, conservatively invalidate the symbolic blockchain + // state so balances and other observable state cannot remain falsely stable. + state().newState(); + m_unsupportedErrors.warning( + 4588_error, + _funCall.location(), + "Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively." + ); + break; + case FunctionType::Kind::TokenBalance: + case FunctionType::Kind::FreezeExpireTime: + case FunctionType::Kind::ValidateMultiSign: + case FunctionType::Kind::BatchValidateSign: + case FunctionType::Kind::VerifyBurnProof: + case FunctionType::Kind::VerifyTransferProof: + case FunctionType::Kind::VerifyMintProof: + case FunctionType::Kind::PedersenHash: + case FunctionType::Kind::RewardBalance: + case FunctionType::Kind::IsSrCandidate: + case FunctionType::Kind::VoteCount: + case FunctionType::Kind::UsedVoteCount: + case FunctionType::Kind::ReceivedVoteCount: + case FunctionType::Kind::TotalVoteCount: + case FunctionType::Kind::GetChainParameter: + case FunctionType::Kind::AvailableUnfreezeV2Size: + case FunctionType::Kind::UnfreezableBalanceV2: + case FunctionType::Kind::ExpireUnfreezeBalanceV2: + case FunctionType::Kind::DelegatableResource: + case FunctionType::Kind::ResourceV2: + case FunctionType::Kind::CheckUnDelegateResource: + case FunctionType::Kind::ResourceUsage: + case FunctionType::Kind::TotalResource: + case FunctionType::Kind::TotalDelegatedResource: + case FunctionType::Kind::TotalAcquiredResource: + // Keep unsupported TRON queries and precompiles unconstrained, while still + // applying the range/shape constraints of their Solidity return types. + if (!funType.returnParameterTypes().empty()) + setSymbolicUnknownValue(*m_context.expression(_funCall), m_context); + m_unsupportedErrors.warning( + 4588_error, + _funCall.location(), + "Assertion checker does not yet implement this type of function call." + ); + break; case FunctionType::Kind::DelegateCall: case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: default: - // The TRON-specific builtins (freeze/unfreeze, vote, the various V2 calls, - // validatemultisign/batchvalidatesign, the zk-proof verifiers, pedersenhash, ...) - // are not modeled here: we only emit the warning below. Their state side effects - // are not havoc'd in this branch; soundness for state mutation across unmodeled - // calls is instead handled by the engine-level reset logic (CHC::unknownFunctionCall - // / makeOutsideFunctionCall and BMC's resetStateVariables paths). m_unsupportedErrors.warning( 4588_error, _funCall.location(), diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 5360a2778677..60533522e36b 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -598,6 +598,42 @@ BOOST_AUTO_TEST_CASE(basic_compilation) ); } +BOOST_AUTO_TEST_CASE(tron_builtin_via_ir_codegen_guards) +{ + Json input = createLanguageAndSourcesSection("Solidity", {{"A.sol", R"( +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.30; +contract C { + function validate(address account, bytes32 content, bytes[] memory signatures) public view returns (bool) { + return validatemultisign(account, 0, content, signatures); + } + function transfer(address payable target, uint256 value, trcToken tokenId) public { + target.transferToken(value, tokenId); + } + function mint( + bytes32[9] memory output, + bytes32[2] memory bindingSignature, + uint64 value, + bytes32 signHash, + bytes32[33] memory frontier, + uint256 leafCount + ) public pure returns (bytes32[] memory) { + return verifyMintProof(output, bindingSignature, value, signHash, frontier, leafCount); + } +} +)"}}); + input["settings"]["viaIR"] = true; + input["settings"]["outputSelection"]["*"]["*"] = Json::array({"ir"}); + + Json const output = compile(input.dump()); + BOOST_REQUIRE(containsAtMostWarnings(output)); + std::string const ir = output["contracts"]["A.sol"]["C"]["ir"].get(); + BOOST_TEST(ir.find("if iszero(gt(0x8000000000000000") != std::string::npos); + BOOST_TEST(ir.find("exp(2, 63)") == std::string::npos); + BOOST_TEST(ir.find("if lt(returndatasize(), 0x20)") != std::string::npos); + BOOST_TEST(ir.find("if mod(returndatasize(), 0x20) { revert(0, 0) }") != std::string::npos); +} + BOOST_AUTO_TEST_CASE(compilation_error) { char const* input = R"( diff --git a/test/libsolidity/smtCheckerTests/tron/state_mutation_havocs_balance.sol b/test/libsolidity/smtCheckerTests/tron/state_mutation_havocs_balance.sol new file mode 100644 index 000000000000..e6ef09cdc08a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/tron/state_mutation_havocs_balance.sol @@ -0,0 +1,13 @@ +contract C { + function f(uint256 amount) public { + uint256 balanceBefore = address(this).balance; + payable(address(this)).freeze(amount, 0); + assert(address(this).balance == balanceBefore); + } +} +// ==== +// SMTEngine: all +// SMTIgnoreCex: yes +// ---- +// Warning 4588: (116-156): Assertion checker does not yet implement this type of function call. Its state effects are modeled conservatively. +// Warning 6328: (166-212): CHC: Assertion violation happens here. diff --git a/test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol b/test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol new file mode 100644 index 000000000000..34b9a3b1587f --- /dev/null +++ b/test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol @@ -0,0 +1,11 @@ +contract C { + function f() public view returns (bool, uint64) { + bool valid = validatemultisign(address(0), 0, bytes32(0), new bytes[](0)); + return (valid, chain.totalNetLimit); + } +} +// ==== +// EVMVersion: