Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libsolidity/analysis/GlobalContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ void GlobalContext::addValidateMultiSignMethod() {
parameterNames,
returnParameterNames,
FunctionType::Kind::ValidateMultiSign,
StateMutability::Pure,
StateMutability::View,
nullptr)
));
}
Expand Down
38 changes: 38 additions & 0 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1999,6 +1999,38 @@ void TypeChecker::typeCheckFunctionCall(
"\"staticcall\" is not supported by the VM version."
);

static std::set<FunctionType::Kind> 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);
}
Expand Down Expand Up @@ -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"
))
Expand Down
1 change: 0 additions & 1 deletion libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand Down
12 changes: 12 additions & 0 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand All @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand All @@ -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]);
Expand Down Expand Up @@ -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))
Expand Down
35 changes: 25 additions & 10 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, <tokenId>)) { revert(0, 0) }
if iszero(gt(exp(2, 63), <tokenId>)) { revert(0, 0) }
if iszero(gt(0x8000000000000000, <tokenId>)) { revert(0, 0) }
let <gas> := 0
if iszero(<tokenValue>) { <gas> := <callStipend> }
let <success> := calltoken(<gas>, <address>, <tokenValue>, <tokenId>, 0, 0, 0, 0)
Expand All @@ -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, <tokenId>)) { revert(0, 0) }
if iszero(gt(exp(2, 63), <tokenId>)) { revert(0, 0) }
if iszero(gt(0x8000000000000000, <tokenId>)) { revert(0, 0) }
let <result> := tokenbalance(<tokenId>, <address>)
)");
templ("address", address);
Expand Down Expand Up @@ -2004,30 +2004,41 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
let <success> := staticcall(gas(), <address>, <pos>, sub(<end>, <pos>), <pos>, <staticReturndataSize>)

if iszero(<success>) { <forwardingRevert>() }
<?isLegacyCompatibleMultisig>
if lt(returndatasize(), 0x20) {
mstore(<pos>, 0)
returndatacopy(<pos>, 0, returndatasize())
}
</isLegacyCompatibleMultisig>

<?isReturndataSizeDynamic>
<?isMintProof>
<?isProof>
if mod(returndatasize(), 0x20) { revert(0, 0) }
let <returnDataSizeVar> := add(returndatasize(), 0x40)
returndatacopy(add(<pos>, 0x40), 0, returndatasize())
mstore(<pos>, 0x20)
mstore(add(<pos>, 0x20), div(returndatasize(), 0x20))
<!isMintProof>
<!isProof>
let <returnDataSizeVar> := returndatasize()
returndatacopy(<pos>, 0, <returnDataSizeVar>)
</isMintProof>
</isProof>
<!isReturndataSizeDynamic>
let <returnDataSizeVar> := <staticReturndataSize>
<?supportsReturnData>
<?strictReturnSize>
if gt(<returnDataSizeVar>, returndatasize()) {
<returnDataSizeVar> := returndatasize()
}
</supportsReturnData>
</strictReturnSize>
</isReturndataSizeDynamic>

// update freeMemoryPointer according to dynamic return size
<finalizeAllocation>(<pos>, <returnDataSizeVar>)

let <retVars> := <abiDecode>(<pos>, add(<pos>, <returnDataSizeVar>))
<?isLegacyCompatibleMultisig>
let <retVars> := mload(<pos>)
<!isLegacyCompatibleMultisig>
let <retVars> := <abiDecode>(<pos>, add(<pos>, <returnDataSizeVar>))
</isLegacyCompatibleMultisig>
)");
templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
templ("pos", m_context.newYulVariable());
Expand All @@ -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));
Expand Down
62 changes: 56 additions & 6 deletions libsolidity/formal/SMTEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
36 changes: 36 additions & 0 deletions test/libsolidity/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>();
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"(
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions test/libsolidity/syntaxTests/tron/precompiles_before_byzantium.sol
Original file line number Diff line number Diff line change
@@ -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: <byzantium
// ----
// TypeError 9137: (88-148): This TRON builtin requires a Byzantium-compatible VM.
// TypeError 9137: (173-192): This TRON builtin requires a Byzantium-compatible VM.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
contract C {
function f() public pure returns (bool) {
return validatemultisign(address(0), 0, bytes32(0), new bytes[](0));
}
}
// ----
// TypeError 2527: (74-134): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view".