Skip to content

Commit 4f87193

Browse files
authored
Merge pull request #12205 from ethereum/optimizeextocedsizecheck
Skip extcodesize check if return data is expected.
2 parents b6d69ee + 080c724 commit 4f87193

File tree

41 files changed

+315
-198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+315
-198
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Language Features:
55

66

77
Compiler Features:
8+
* Code Generator: Skip existence check for external contract if return data is expected. In this case, the ABI decoder will revert if the contract does not exist.
89
* Commandline Interface: Accept nested brackets in step sequences passed to ``--yul-optimizations``.
910
* Commandline Interface: Add ``--debug-info`` option for selecting how much extra debug information should be included in the produced EVM assembly and Yul code.
1011
* Commandline Interface: Use different colors when printing errors, warnings and infos.

docs/control-structures.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,19 @@ otherwise, the ``value`` option would not be available.
114114
Due to the fact that the EVM considers a call to a non-existing contract to
115115
always succeed, Solidity uses the ``extcodesize`` opcode to check that
116116
the contract that is about to be called actually exists (it contains code)
117-
and causes an exception if it does not.
117+
and causes an exception if it does not. This check is skipped if the return
118+
data will be decoded after the call and thus the ABI decoder will catch the
119+
case of a non-existing contract.
120+
118121
Note that this check is not performed in case of :ref:`low-level calls <address_related>` which
119122
operate on addresses rather than contract instances.
120123

124+
.. note::
125+
Be careful when using high-level calls to
126+
:ref:`precompiled contracts <precompiledContracts>`,
127+
since the compiler considers them non-existing according to the
128+
above logic even though they execute code and can return data.
129+
121130
Function calls also cause exceptions if the called contract itself
122131
throws an exception or goes out of gas.
123132

docs/introduction-to-smart-contracts.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,24 @@ contracts, the Ether is forever lost.
565565
If you want to deactivate your contracts, you should instead **disable** them
566566
by changing some internal state which causes all functions to revert. This
567567
makes it impossible to use the contract, as it returns Ether immediately.
568+
569+
570+
.. index:: ! precompiled contracts, ! precompiles, ! contract;precompiled
571+
572+
.. _precompiledContracts:
573+
574+
Precompiled Contracts
575+
=====================
576+
577+
There is a small set of contract addresses that are special:
578+
The address range between ``1`` and (including) ``8`` contains
579+
"precompiled contracts" that can be called as any other contract
580+
but their behaviour (and their gas consumption) is not defined
581+
by EVM code stored at that address (they do not contain code)
582+
but instead is implemented in the EVM execution environment itself.
583+
584+
Different EVM-compatible chains might use a different set of
585+
precompiled contracts. It might also be possible that new
586+
precompiled contracts are added to the Ethereum main chain in the future,
587+
but you can reasonabyly expect them to always be in the range between
588+
``1`` and ``0xffff`` (inclusive).

libsolidity/codegen/ExpressionCompiler.cpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2630,9 +2630,21 @@ void ExpressionCompiler::appendExternalFunctionCall(
26302630
// Check the target contract exists (has code) for non-low-level calls.
26312631
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall)
26322632
{
2633-
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
2634-
m_context.appendConditionalRevert(false, "Target contract does not contain code");
2635-
existenceChecked = true;
2633+
size_t encodedHeadSize = 0;
2634+
for (auto const& t: returnTypes)
2635+
encodedHeadSize += t->decodingType()->calldataHeadSize();
2636+
// We do not need to check extcodesize if we expect return data, since if there is no
2637+
// code, the call will return empty data and the ABI decoder will revert.
2638+
if (
2639+
encodedHeadSize == 0 ||
2640+
!haveReturndatacopy ||
2641+
m_context.revertStrings() >= RevertStrings::Debug
2642+
)
2643+
{
2644+
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
2645+
m_context.appendConditionalRevert(false, "Target contract does not contain code");
2646+
existenceChecked = true;
2647+
}
26362648
}
26372649

26382650
if (_functionType.gasSet())

libsolidity/codegen/ir/IRGeneratorForStatements.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,8 +2451,10 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
24512451
appendCode() << "mstore(add(" << m_utils.allocateUnboundedFunction() << "() , " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
24522452
}
24532453

2454-
Whiskers templ(R"(if iszero(extcodesize(<address>)) { <revertNoCode>() }
2455-
2454+
Whiskers templ(R"(
2455+
<?checkExtcodesize>
2456+
if iszero(extcodesize(<address>)) { <revertNoCode>() }
2457+
</checkExtcodesize>
24562458
// storage for arguments and returned data
24572459
let <pos> := <allocateUnbounded>()
24582460
mstore(<pos>, <shl28>(<funSel>))
@@ -2477,6 +2479,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
24772479
}
24782480
)");
24792481
templ("revertNoCode", m_utils.revertReasonIfDebugFunction("Target contract does not contain code"));
2482+
2483+
// We do not need to check extcodesize if we expect return data: If there is no
2484+
// code, the call will return empty data and the ABI decoder will revert.
2485+
size_t encodedHeadSize = 0;
2486+
for (auto const& t: returnInfo.returnTypes)
2487+
encodedHeadSize += t->decodingType()->calldataHeadSize();
2488+
bool const checkExtcodesize =
2489+
encodedHeadSize == 0 ||
2490+
!m_context.evmVersion().supportsReturndata() ||
2491+
m_context.revertStrings() >= RevertStrings::Debug;
2492+
templ("checkExtcodesize", checkExtcodesize);
2493+
24802494
templ("pos", m_context.newYulVariable());
24812495
templ("end", m_context.newYulVariable());
24822496
if (_functionCall.annotation().tryCall)
@@ -2532,6 +2546,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
25322546
u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10;
25332547
if (funType.valueSet())
25342548
gasNeededByCaller += evmasm::GasCosts::callValueTransferGas;
2549+
if (!checkExtcodesize)
2550+
gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know
25352551
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
25362552
}
25372553
// Order is important here, STATICCALL might overlap with DELEGATECALL.

0 commit comments

Comments
 (0)