Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.7.0 #157

Merged
merged 71 commits into from
May 7, 2024
Merged
Changes from 19 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
c894abe
Initialized balancer detector
Oct 18, 2023
63f413a
working version
Yhtiyar Oct 19, 2023
3bd7b04
updated severity
Yhtiyar Oct 19, 2023
e33037a
fixed docs
Yhtiyar Oct 19, 2023
e3a0cf9
output_result_typo
Oct 19, 2023
18ac522
Merge branch 'master' into detector_balancer
Yhtiyar Oct 19, 2023
ebf96ae
Merge branch 'detector_balancer' of github.com:pessimistic-io/custom_…
Yhtiyar Oct 19, 2023
13ef10d
bench updates&workflow google sheet upload
nikolay19 Feb 12, 2024
67a88c5
bench 8000 contracts limit
nikolay19 Feb 12, 2024
417e866
Update benchmark.yml
nikolay19 Feb 13, 2024
98921ea
workflow for old version bench
nikolay19 Feb 15, 2024
318605c
Merge pull request #130 from pessimistic-io/submodule_benchmark
nikolay19 Feb 15, 2024
94218f1
Update old_version.yml
nikolay19 Feb 15, 2024
53ef547
bench updates
nikolay19 Feb 15, 2024
dc6210d
Update benchmark.yml
nikolay19 Feb 15, 2024
32b4e0c
Create benchmark_old.yml
nikolay19 Feb 19, 2024
60487a9
Merge pull request #136 from pessimistic-io/nikolay19-patch-1
nikolay19 Feb 19, 2024
ae60974
Merge branch 'develop' of https://github.com/pessimistic-io/slitherin…
nikolay19 Feb 19, 2024
e1cb898
remove action
nikolay19 Feb 19, 2024
115cd26
add napalm integration
JoranHonig Feb 22, 2024
f4f0c5a
removed arbitrum solidity version detector
Yhtiyar Feb 26, 2024
b4ba7f3
bench updates
nikolay19 Feb 26, 2024
a9e7635
Merge branch 'benchmark_updates' of https://github.com/pessimistic-io…
nikolay19 Feb 26, 2024
53038d9
Merge pull request #140 from pessimistic-io/remove-arbsol-verion
Yhtiyar Feb 28, 2024
2fe2cb3
Merge pull request #133 from pessimistic-io/benchmark_updates
nikolay19 Feb 28, 2024
21a5ea6
added detector
Yhtiyar Mar 4, 2024
5b3a52c
tets for arb-chainlink detector
Yhtiyar Mar 4, 2024
f5769a3
docs for arb-chainlink detector
Yhtiyar Mar 4, 2024
e8e79e8
Merge branch 'develop' into chainlink-arbitrum
Yhtiyar Mar 4, 2024
b4eafaa
removing uniswap v3 folder, as it was accidentally added
Yhtiyar Mar 4, 2024
fea92e4
added curve readonly detector
Yhtiyar Mar 5, 2024
5f5555a
added docs for curve detector
Yhtiyar Mar 6, 2024
06d3ed8
Merge pull request #141 from pessimistic-io/chainlink-arbitrum
ndkirillov Mar 7, 2024
b13e34a
Merge pull request #142 from pessimistic-io/curve-readonly-reentrancy
ndkirillov Mar 7, 2024
709548a
PR&slither version to sheet
nikolay19 Mar 7, 2024
597794c
Merge pull request #144 from pessimistic-io/benchmark_updates
nikolay19 Mar 8, 2024
6bd5782
add erc1155 support
olegggatttor Feb 12, 2024
982b7ff
add SafeERC20 support
olegggatttor Feb 12, 2024
ada5f71
Merge branch 'develop' into detector_balancer
Mar 22, 2024
c1b067d
corrected_readme
Mar 22, 2024
a01fdbc
readme_from_master
Mar 22, 2024
fb08e52
replaced_balancer_detector
Mar 22, 2024
d613692
add_init_balancer
Mar 22, 2024
bee7127
package_name update
Mar 22, 2024
52aa8ec
updated_detectors_table
Mar 22, 2024
6bf933f
Merge pull request #146 from pessimistic-io/nft_approve_support_erc1155
ndkirillov Apr 9, 2024
539e746
remove relative imports
JoranHonig Feb 22, 2024
76d0afa
update napalm entry point to restrict inclusions
JoranHonig Feb 22, 2024
f9c8ac5
revert undesired changes to setup
JoranHonig Feb 22, 2024
3581c9a
removed arbitrum solidity version detector
Yhtiyar Feb 26, 2024
1d10755
Merge pull request #150 from pessimistic-io/master
olegggatttor Apr 12, 2024
d9a684c
add recursive check for base constructors | strange setter
olegggatttor Apr 12, 2024
93f49f2
typo
olegggatttor Apr 12, 2024
a2862f3
fix: PotentialArithmOverflow detector fail
shortdoom Apr 12, 2024
3870d21
fix starge setter fail on new contract creations
olegggatttor Apr 12, 2024
49a7ef6
Merge pull request #153 from shortdoom/fix-detector-fail-develop
olegggatttor Apr 15, 2024
fdec81b
add vyper version detector
olegggatttor Apr 16, 2024
7669e67
add file
olegggatttor Apr 16, 2024
9650c18
rm comment
olegggatttor Apr 16, 2024
9028442
add detector
olegggatttor Apr 16, 2024
a96c304
add native support
olegggatttor Apr 16, 2024
1c6881f
update bench submodule in branch
nikolay19 Apr 18, 2024
9a6c889
Merge pull request #152 from pessimistic-io/147-strange-setter-detect…
ndkirillov Apr 18, 2024
c476f26
Merge pull request #155 from pessimistic-io/price_manipulation_detector
ndkirillov Apr 18, 2024
9cf2a55
Merge branch 'develop' into curve-vyper-version-detector
ndkirillov Apr 18, 2024
95c35b5
Merge pull request #154 from pessimistic-io/curve-vyper-version-detector
ndkirillov Apr 18, 2024
d37342a
Merge branch 'develop' into detector_balancer
olegggatttor Apr 18, 2024
da6ca8c
add comma
olegggatttor Apr 18, 2024
91562ba
Merge pull request #91 from pessimistic-io/detector_balancer
olegggatttor Apr 18, 2024
2d386c8
Merge pull request #139 from JoranHonig/master
ndkirillov May 6, 2024
8659ab2
version update
May 7, 2024
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
26 changes: 26 additions & 0 deletions docs/curve_vyper_reentrancy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Curve Readonly Reentrancy

## Configuration

- Check: `pess-curve-vyper-reentrancy`
- Severity: `High`
- Confidence: `High`

## Description

Finds if the code is compiled with vulnerable Vyper compiler version and contains non-reentrant modifiers.
Details:
- [Curve exploit postmortem](https://hackmd.io/@LlamaRisk/BJzSKHNjn)
- [Postmortem from Vyper team](https://hackmd.io/@vyperlang/HJUgNMhs2)

## Vulnerable Scenario

[test scenarios](../../tests/vyper/curve_vyper_reentrancy_test.vy)

## Related Attacks

- [Vyper compiler exploits](https://www.halborn.com/blog/post/explained-the-vyper-bug-hack-july-2023)

## Recomendations

- Upgrade the version of your Vyper compiler.
4 changes: 2 additions & 2 deletions docs/nft_approve_warning.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
* Confidence: `Low`

## Description
The detector sees if a contract contains `erc721.[safe]TransferFrom(from, ...)` where `from` parameter is not related to `msg.sender`.
The detector sees if a contract contains `erc721.[safe]TransferFrom(from, ...)` or `erc1155.safe[Batch]TransferFrom(from, ...)` where `from` parameter is not related to `msg.sender`.
An attacker can steal any approved NFTs because `transferFrom` function does NOT check that the call is made by its owner.

## Vulnerable Scenario
@@ -17,4 +17,4 @@ An attacker can steal any approved NFTs because `transferFrom` function does NOT
[Unauthorized transfer_from Vulnerability](https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability)

## Recommendation
Make sure that in `erc721.[safe]TransferFrom(from, ...)` functions `from` parameter is related to `msg.sender`.
Make sure that in `erc721.[safe]TransferFrom(from, ...)` and `erc1155.safe[Batch]TransferFrom(from, ...)` functions `from` parameter is related to `msg.sender`.
15 changes: 15 additions & 0 deletions docs/price_manipulation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Price Manipulation through token transfers

## Configuration
* Check: `pess-price-manipulation`
* Severity: `High`
* Confidence: `Low`

## Description
The detector finds calculations that depend on the balance and supply of some token. Such calculations could be manipulated through direct transfers to the contract, increasing its balance.

## Vulnerable Scenario
[test scenario](../tests/price_manipulation_test.sol)

## Recommendation
Avoid possible manipulations of calculations because of external transfers.
4 changes: 4 additions & 0 deletions slitherin/__init__.py
Original file line number Diff line number Diff line change
@@ -33,6 +33,8 @@
from slitherin.detectors.potential_arith_overflow import PotentialArithmOverflow
from slitherin.detectors.curve.curve_readonly_reentrancy import CurveReadonlyReentrancy
from slitherin.detectors.balancer.balancer_readonly_reentrancy import BalancerReadonlyReentrancy
from slitherin.detectors.vyper.reentrancy_curve_vyper_version import CurveVyperReentrancy
from slitherin.detectors.price_manipulation import PriceManipulationDetector

artbitrum_detectors = [
ArbitrumPrevrandaoDifficulty,
@@ -67,6 +69,8 @@
PotentialArithmOverflow,
CurveReadonlyReentrancy,
BalancerReadonlyReentrancy
CurveVyperReentrancy,
PriceManipulationDetector
]
plugin_printers = []

12 changes: 8 additions & 4 deletions slitherin/detectors/nft_approve_warning.py
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations import Function, SolidityVariableComposed
from slither.core.declarations.function_contract import FunctionContract
from slither.analyses.data_dependency.data_dependency import is_dependent


SAFE_ERC20_LIB_SIG = "safeTransferFrom(address,address,address,uint256)"
class NftApproveWarning(AbstractDetector):
"""
Sees if contract contains erc721.[safe]TransferFrom(from, ...) where from parameter is not related to msg.sender
@@ -16,12 +17,14 @@ class NftApproveWarning(AbstractDetector):
CONFIDENCE = DetectorClassification.LOW

WIKI = 'https://ventral.digital/posts/2022/8/18/sznsdaos-bountyboard-unauthorized-transferfrom-vulnerability'
WIKI_TITLE = 'NFT Approve Warning'
WIKI_TITLE = 'Token Approve Warning'
WIKI_DESCRIPTION = "In [safe]TransferFrom() from parameter must be related to msg.sender"
WIKI_EXPLOIT_SCENARIO = '-'
WIKI_RECOMMENDATION = 'from parameter must be related to msg.sender'

_signatures=["transferFrom(address,address,uint256)", "safeTransferFrom(address,address,uint256,bytes)", "safeTransferFrom(address,address,uint256)"]
_signatures=["transferFrom(address,address,uint256)", "safeTransferFrom(address,address,uint256,bytes)", "safeTransferFrom(address,address,uint256)",
SAFE_ERC20_LIB_SIG, # SafeERC20.safeTransferFrom
"safeTransferFrom(address,address,uint256,uint256,bytes)", "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"] # ERC1155

def _detect_arbitrary_from(self, f: Function):
all_high_level_calls = [
@@ -48,7 +51,8 @@ def _arbitrary_from(self, nodes: List[Node]):
and hasattr(ir.function, 'solidity_signature')
and ir.function.solidity_signature in self._signatures
):
is_from_sender = is_dependent(ir.arguments[0], SolidityVariableComposed("msg.sender"), node.function.contract)
is_from_sender = ir.function.solidity_signature != SAFE_ERC20_LIB_SIG and is_dependent(ir.arguments[0], SolidityVariableComposed("msg.sender"), node.function.contract) or \
ir.function.solidity_signature == SAFE_ERC20_LIB_SIG and is_dependent(ir.arguments[1], SolidityVariableComposed("msg.sender"), node.function.contract)
# is_from_self = is_dependent(ir.arguments[0], SolidityVariable("this"), node.function.contract)
if (not is_from_sender): # and not is_from_self
irList.append(ir.node)
4 changes: 2 additions & 2 deletions slitherin/detectors/potential_arith_overflow.py
Original file line number Diff line number Diff line change
@@ -86,7 +86,7 @@ def _find_vulnerable_expressions(self, fun: Function) -> list:
errors.extend(response_errors)
if has_problems:
final_results.append((node, str(irs[-1].lvalue._type), errors))
if len(irs) > 0 and isinstance(irs[-1], ops.Return) and len(fun.return_type) == 1 and str(fun.return_type[0]) in INT_TYPES: # @todo currently works only with single returns
if len(irs) > 0 and isinstance(irs[-1], ops.Return) and fun.return_type is not None and len(fun.return_type) == 1 and str(fun.return_type[0]) in INT_TYPES: # @todo currently works only with single returns
expected_bits_cut = str(fun.return_type[0]).removeprefix("uint").removeprefix("int")
expected_bits = int(256 if not expected_bits_cut else expected_bits_cut)
has_problems = False
@@ -115,4 +115,4 @@ def _detect(self) -> List[Output]:
for (op, op_ret_type) in op_with_ret_type:
info += ["\t\t`", str(op), f"` returns {op_ret_type}, but the type of the resulting expression is {node_final_type}.", "\n"]
res.append(self.generate_result(info))
return res
return res
71 changes: 71 additions & 0 deletions slitherin/detectors/price_manipulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import List
from slither.utils.output import Output
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations.high_level_call import HighLevelCall
from slither.slithir.operations.internal_call import InternalCall
from slither.slithir.operations.solidity_call import SolidityCall
from slither.slithir.operations.binary import Binary
from slither.analyses.data_dependency.data_dependency import is_dependent


class PriceManipulationDetector(AbstractDetector):
ARGUMENT = 'pess-price-manipulation' # slither will launch the detector with slither.py --detect mydetector
HELP = 'Contract math can be manipulated through external token transfers.'
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.LOW

WIKI = 'https://github.com/pessimistic-io/slitherin/blob/master/docs/price_manipulation.md'
WIKI_TITLE = '# Price Manipulation through token transfers'
WIKI_DESCRIPTION = "Calculations could be manipulated through direct transfers to the contract, increasing its balance as they depends on these balances."
WIKI_EXPLOIT_SCENARIO = 'N/A'
WIKI_RECOMMENDATION = 'Avoid possible manipulations of calculations because of external transfers.'

def _detect(self) -> List[Output]:
all_balance_vars = []
all_supply_vars = []
all_binary_ops = []
for contract in self.contracts:
if not contract.is_interface:
for func in contract.functions:
for n in func.nodes:
for x in n.irs:
if isinstance(x, SolidityCall):
if x.function.name == "balance(address)" or x.function.name == "self.balance" or x.function.name == "this.balance()":
all_balance_vars.append((n, x._lvalue))
if isinstance(x, HighLevelCall):
if str(x.function_name).lower() == "balanceof":
all_balance_vars.append((n, x._lvalue))
if "supply" in str(x.function_name).lower():
all_supply_vars.append((n, x._lvalue))
if isinstance(x, InternalCall):
if "supply" in str(x.function_name).lower():
all_supply_vars.append((n, x._lvalue))
if isinstance(x, Binary):
all_binary_ops.append((n, x))
results = []
for (balance_node, bal) in all_balance_vars:
for (supply_node, supply) in all_supply_vars:
for (node, bin_op) in all_binary_ops:
l, r = bin_op.get_variable
is_bal_dependent = is_dependent(l, bal, contract)
is_supply_dependent = is_dependent(r, supply, contract)
if is_bal_dependent and is_supply_dependent:
results.append((node, balance_node, supply_node))
if not results:
return []


response = []
for issue_node, balance_node, supply_node in results:
res = []
res.append("Calculation ")
res.append(issue_node)
res.append(" depends on balance and token supply - these values could be manipulated through external calls.\n")
res.append("\tBalance dependency: ")
res.append(balance_node)
res.append("\n")
res.append("\tSupply dependency: ")
res.append(supply_node)
res.append("\n")
response.append(self.generate_result(res))
return response
11 changes: 11 additions & 0 deletions slitherin/detectors/strange_setter.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Function
import slither.core.expressions.new_array as na
import slither.core.expressions.new_contract as nc
from slither.analyses.data_dependency.data_dependency import is_dependent


class StrangeSetter(AbstractDetector):
@@ -51,9 +53,18 @@ def _is_strange_setter(self, fun: Function) -> bool:
for external in fun.external_calls_as_expressions:
if isinstance(external._called, na.NewArray):
continue
if isinstance(external._called, nc.NewContract): # skip new contract calls, idk how to get arguments passed to creation
continue
for arg in [*external.arguments, external._called._expression]:
if str(arg) == str(param):
used_params.add(param)
if fun.name == "constructor":
for base_call in fun.explicit_base_constructor_calls:
if not self._is_strange_constructor(base_call):
for param_cur in fun.parameters:
for param_base in base_call.parameters:
if is_dependent(param_base, param_cur, base_call):
used_params.add(param_cur)
intersection_len = len(set(fun.parameters) & used_params)
return intersection_len != len(fun.parameters)

40 changes: 40 additions & 0 deletions slitherin/detectors/vyper/reentrancy_curve_vyper_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from typing import List
from slither.utils.output import Output
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Function
from slither.slithir.operations.event_call import EventCall

VULNERABLE_VERSIONS = ['0.2.15', '0.2.16', '0.3.0']
class CurveVyperReentrancy(AbstractDetector):
ARGUMENT = 'pess-curve-vyper-reentrancy' # slither will launch the detector with slither.py --detect mydetector
HELP = f'Vyper compiler versions {", ".join(VULNERABLE_VERSIONS)} are vulnerable to malfunctioning re-entrancy guards. Upgrade your compiler version.'
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH

WIKI = 'https://github.com/pessimistic-io/slitherin/blob/master/docs/curve_vyper_reentrancy.md'
WIKI_TITLE = 'Vulnerable Vyper version'
WIKI_DESCRIPTION = "Some Vyper versions are vulnerable to malfunctioning re-entrancy guards."
WIKI_EXPLOIT_SCENARIO = 'https://hackmd.io/@LlamaRisk/BJzSKHNjn'
WIKI_RECOMMENDATION = 'Upgrade the version of your Vyper compiler.'

def _detect(self) -> List[Output]:
res = []
if not self.compilation_unit.is_vyper:
return res
compiler_version = self.compilation_unit.compiler_version.version
if compiler_version not in VULNERABLE_VERSIONS:
return res

for contract in self.contracts:
for f in contract.functions_entry_points:
for modifier in f.modifiers:
if modifier.name.startswith("nonreentrant"):
res += ["\t", modifier.name, " in ", f, " function.\n"]
if not res:
return res
return [self.generate_result(
[f"Vyper {compiler_version} compiler version is vulnerable to malfunctioning re-entrancy guards. Found vulnerable usages:\n",
*[x for x in res],
"\n"
]
)]
53 changes: 53 additions & 0 deletions tests/nft_approve_warning_test.sol
Original file line number Diff line number Diff line change
@@ -36,3 +36,56 @@ contract Test
}
}

interface IERC1155 {
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory values,
bytes memory data
) external;
}

contract TestERC1155 {
function vuln_safeTransferFrom(address target, address from, address to) external {
IERC1155(target).safeTransferFrom(from, to, 1, 1, "");
}

function ok_safeTransferFrom(address target, address to) external {
IERC1155(target).safeTransferFrom(msg.sender, to, 1, 1, "");
}

function vuln_safeBatchTransferFrom(address target, address from, address to) external {
IERC1155(target).safeBatchTransferFrom(from, to, new uint256[](2), new uint256[](2), "");
}

function ok_safeBatchTransferFrom(address target, address to) external {
IERC1155(target).safeBatchTransferFrom(msg.sender, to, new uint256[](2), new uint256[](2), "");
}
}


library SafeERC20 {
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
// _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
}

contract TestSafeERC20LibCall {
function vuln1_safeErc20TransferFrom(IERC20 token, address from, address to, uint256 value) external {
SafeERC20.safeTransferFrom(token, from, to, value);
}

function ok1_safeErc20TransferFrom(IERC20 token, address to, uint256 value) external {
SafeERC20.safeTransferFrom(token, msg.sender, to, value);
}

function vuln2_safeErc20TransferFrom(address from, address to, uint256 value) external {
SafeERC20.safeTransferFrom(IERC20(msg.sender), from, to, value);
}

function ok2_safeErc20TransferFrom(address to, uint256 value) external {
SafeERC20.safeTransferFrom(IERC20(msg.sender), msg.sender, to, value);
}
}
47 changes: 47 additions & 0 deletions tests/price_manipulation_test.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
}
contract Test1 {
IERC20 token;

function test_vuln_1() external returns(uint256 price) {
price = token.balanceOf(address(this)) / token.totalSupply();
}

function test_vuln_2() external returns(uint256 price) {
uint256 bal = token.balanceOf(address(this));
uint256 supply = token.totalSupply();
price = bal / supply;
}

function test_vuln_3() external returns(uint256 price) {
uint256 bal = getBalance();
price = bal / token.totalSupply();
}

function test_vuln_4() external returns(uint256 price) {
uint256 bal = getBalance();
price = 10 + (bal / (token.totalSupply() * 5));
}

function test_vuln_5() external returns(uint256 price) {
price = getBalance() / mySupply();
}

function test_vuln_6() external returns(uint256 price) {
price = getBalance() + mySupply() + 1;
}

function test_vuln_7() external returns(uint256 price) {
price = address(token).balance / mySupply();
}

function getBalance() public returns(uint256 bal) {
bal = token.balanceOf(msg.sender);
}

function mySupply() public returns (uint256) {
return 100;
}
}
10 changes: 10 additions & 0 deletions tests/strange_setter_test.sol
Original file line number Diff line number Diff line change
@@ -87,3 +87,13 @@ contract OkConstructor {
init = true;
}
}

contract TestInheritance is StrangeSetter{
constructor(uint256 _toSet) StrangeSetter(_toSet) {}
}

contract TestNewContract {
constructor(uint256 _toSet) {
new TestInheritance(_toSet);
}
}
15 changes: 15 additions & 0 deletions tests/vyper/curve_vyper_reentrancy_test.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# @version =0.2.15

@external
@nonreentrant("hello_lock")
def helloWorld() -> String[24]:
return "Hello World!"

@external
@nonreentrant("another_lock")
def another_reentrant_func() -> uint256:
return 1

@external
def normal_func() -> uint256:
return 0