Skip to content

Commit

Permalink
Merge branch 'master' into loop_invariant
Browse files Browse the repository at this point in the history
  • Loading branch information
HodanPlodky authored Sep 30, 2024
2 parents 8edce11 + e21f3e8 commit cf6b25e
Show file tree
Hide file tree
Showing 53 changed files with 840 additions and 193 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
with:
types: |
feat
perf
fix
chore
refactor
Expand Down
3 changes: 3 additions & 0 deletions FUNDING.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
"ethereum": {
"ownedBy": "0x70CCBE10F980d80b7eBaab7D2E3A73e87D67B775"
}
},
"opRetro": {
"projectId": "0x9ca1f7b0e0d10d3bd2619e51a54f2e4175e029c87a2944cf1ebc89164ba77ea0"
}
}
19 changes: 17 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Supported Versions

- it is recommended to follow the list of known [vulnerabilities](https://github.com/vyperlang/vyper/security/advisories) and stay up-to-date with the latest releases
- as of May 2024, the `0.4.0` release is the most secure and the most comprehensively reviewed one and is recommended for use in production environments
- as of May 2024, the [`0.4.0`](https://github.com/vyperlang/vyper/releases/tag/v0.4.0) release is the most comprehensively reviewed one and is recommended for use in production environments
- if a compiler vulnerability is found, a new compiler version with a patch will be released. The vulnerable version itself is not updated (see the examples below).
- `example1`: suppose `0.4.0` is the latest version and a hypothetical vulnerability is found in `0.4.0`, then a patch will be released in `0.4.1`
- `example2`: suppose `0.4.0` is the latest version and a hypothetical vulnerability is found both in `0.3.10` and `0.4.0`, then a patch will be released only in `0.4.1`
Expand All @@ -26,7 +26,22 @@ we will add an entry to the list of security advisories for posterity and refere


## Bug Bounty Program
- as of May 2024, Vyper does not have a bug bounty program. It is planned to instantiate one soon.
- Vyper runs a bug bounty program via the Ethereum Foundation.
- Bugs should be reported through the [Ethereum Foundation's bounty program](https://ethereum.org/bug-bounty).

### Scope
- Rules from the Ethereum Foundation's bug bounty program apply; for any questions please reach out [here](mailto:[email protected]). Here we further clarify the scope of the Vyper bounty program.
- If a compiler bug affects production code, it is in scope (excluding known issues).
- This includes bugs in older compiler versions still used in production.
- If a compiler bug does not currently affect production but is likely to in the future, it is in scope.
- This mainly applies to the latest compiler release (e.g., a new release is available but contracts are not yet deployed with it).
- Experimental features (e.g. `--experimental-codegen`) are out of scope, as they are not intended for production and are unlikely to affect production code.
- Bugs in older compiler versions are generally out of scope, as they are no longer used for new contracts.
- There might be exceptions, e.g., when an L2 doesn't support recent compiler releases. In such cases, it might be reasonable for an older version to be used. It is up to the discretion of the EF & Vyper team to decide if the bug is in scope.
- If a vulnerability affects multiple contracts, the whitehat is eligible for only one payout (though the severity of the bug may increase).
- Eligibility for project-specific bounties is independent of this bounty.
- [Security advisories](https://github.com/vyperlang/vyper/security/advisories) and [known issues](https://github.com/vyperlang/vyper/issues) are not eligible for the bounty program, as they are publicly disclosed and protocols should structure their contracts accordingly.
- Individuals or organizations contracted or engaged specifically for security development, auditing, or testing of this project are ineligible for the bounty program.

## Reporting a Vulnerability

Expand Down
1 change: 1 addition & 0 deletions docs/constants-and-vars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Name Type Value
``chain.id`` ``uint256`` Chain ID
``msg.data`` ``Bytes`` Message data
``msg.gas`` ``uint256`` Remaining gas
``msg.mana`` ``uint256`` Remaining gas (alias for ``msg.gas``)
``msg.sender`` ``address`` Sender of the message (current call)
``msg.value`` ``uint256`` Number of wei sent with the message
``tx.origin`` ``address`` Sender of the transaction (full call chain)
Expand Down
4 changes: 0 additions & 4 deletions docs/interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ The ``default_return_value`` parameter can be used to handle ERC20 tokens affect
extcall IERC20(USDT).transfer(msg.sender, 1, default_return_value=True) # returns True
extcall IERC20(USDT).transfer(msg.sender, 1) # reverts because nothing returned
.. warning::

When ``skip_contract_check=True`` is used and the called function returns data (ex.: ``x: uint256 = SomeContract.foo(skip_contract_check=True)``, no guarantees are provided by the compiler as to the validity of the returned value. In other words, it is undefined behavior what happens if the called contract did not exist. In particular, the returned value might point to garbage memory. It is therefore recommended to only use ``skip_contract_check=True`` to call contracts which have been manually ensured to exist at the time of the call.

Built-in Interfaces
===================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1441,22 +1441,29 @@ def get_lucky(gas_amount: uint256) -> int128:
c2.get_lucky(50) # too little gas.


def test_skip_contract_check(get_contract):
def test_skip_contract_check(get_contract, tx_failed):
contract_2 = """
@external
@view
def bar():
pass
# include fallback for sanity, make sure we don't get trivially rejected in
# selector table
@external
def __default__():
pass
"""
contract_1 = """
interface Bar:
def bar() -> uint256: view
def baz(): nonpayable
@external
def call_bar(addr: address):
# would fail if returndatasize check were on
x: uint256 = staticcall Bar(addr).bar(skip_contract_check=True)
def call_bar(addr: address) -> uint256:
# fails during abi decoding
return staticcall Bar(addr).bar(skip_contract_check=True)
@external
def call_baz():
# some address with no code
Expand All @@ -1466,7 +1473,10 @@ def call_baz():
"""
c1 = get_contract(contract_1)
c2 = get_contract(contract_2)
c1.call_bar(c2.address)

with tx_failed():
c1.call_bar(c2.address)

c1.call_baz()


Expand Down
33 changes: 33 additions & 0 deletions tests/functional/codegen/features/iteration/test_for_in_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,3 +897,36 @@ def foo():
compile_code(main, input_bundle=input_bundle)

assert e.value._message == "Cannot modify loop variable `queue`"


def test_iterator_modification_memory(get_contract):
code = """
@external
def foo() -> DynArray[uint256, 10]:
# check VarInfos are distinguished by decl_node when they have same type
alreadyDone: DynArray[uint256, 10] = []
_assets: DynArray[uint256, 10] = [1, 2, 3, 4, 3, 2, 1]
for a: uint256 in _assets:
if a in alreadyDone:
continue
alreadyDone.append(a)
return alreadyDone
"""
c = get_contract(code)
assert c.foo() == [1, 2, 3, 4]


def test_iterator_modification_func_arg(get_contract):
code = """
@internal
def boo(a: DynArray[uint256, 12] = [], b: DynArray[uint256, 12] = []) -> DynArray[uint256, 12]:
for i: uint256 in a:
b.append(i)
return b
@external
def foo() -> DynArray[uint256, 12]:
return self.boo([1, 2, 3])
"""
c = get_contract(code)
assert c.foo() == [1, 2, 3]
4 changes: 4 additions & 0 deletions tests/functional/codegen/features/test_constructor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import pytest

from tests.evm_backends.base_env import _compile
from vyper.exceptions import StackTooDeep
from vyper.utils import method_id


Expand Down Expand Up @@ -166,6 +169,7 @@ def get_foo() -> uint256:
assert c.get_foo() == 39


@pytest.mark.venom_xfail(raises=StackTooDeep, reason="stack scheduler regression")
def test_nested_dynamic_array_constructor_arg_2(env, get_contract):
code = """
foo: int128
Expand Down
11 changes: 11 additions & 0 deletions tests/functional/codegen/features/test_mana.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
def test_mana_call(get_contract):
mana_call = """
@external
def foo() -> uint256:
return msg.mana
"""

c = get_contract(mana_call)

assert c.foo(gas=50000) < 50000
assert c.foo(gas=50000) > 25000
31 changes: 31 additions & 0 deletions tests/functional/codegen/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,3 +695,34 @@ def test_call(a: address, b: {type_str}) -> {type_str}:
make_file("jsonabi.json", json.dumps(convert_v1_abi(abi)))
c3 = get_contract(code, input_bundle=input_bundle)
assert c3.test_call(c1.address, value) == value


def test_interface_function_without_visibility(make_input_bundle, get_contract):
interface_code = """
def foo() -> uint256:
...
@external
def bar() -> uint256:
...
"""

code = """
import a as FooInterface
implements: FooInterface
@external
def foo() -> uint256:
return 1
@external
def bar() -> uint256:
return 1
"""

input_bundle = make_input_bundle({"a.vyi": interface_code})

c = get_contract(code, input_bundle=input_bundle)

assert c.foo() == c.bar() == 1
60 changes: 56 additions & 4 deletions tests/functional/codegen/types/numbers/test_unsigned_ints.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,65 @@ def foo():
compile_code(code)


def test_invalid_div():
code = """
div_code_with_hint = [
(
"""
@external
def foo():
a: uint256 = 5 / 9
"""
""",
"did you mean `5 // 9`?",
),
(
"""
@external
def foo():
a: uint256 = 10
a /= (3 + 10) // (2 + 3)
""",
"did you mean `a //= (3 + 10) // (2 + 3)`?",
),
(
"""
@external
def foo(a: uint256, b:uint256, c: uint256) -> uint256:
return (a + b) / c
""",
"did you mean `(a + b) // c`?",
),
(
"""
@external
def foo(a: uint256, b:uint256, c: uint256) -> uint256:
return (a + b) / (a + c)
""",
"did you mean `(a + b) // (a + c)`?",
),
(
"""
@external
def foo(a: uint256, b:uint256, c: uint256) -> uint256:
return (a + (c + b)) / (a + c)
""",
"did you mean `(a + (c + b)) // (a + c)`?",
),
(
"""
interface Foo:
def foo() -> uint256: view
@external
def foo(a: uint256, b:uint256, c: uint256) -> uint256:
return (a + b) / staticcall Foo(self).foo()
""",
"did you mean `(a + b) // staticcall Foo(self).foo()`?",
),
]


@pytest.mark.parametrize("code, expected_hint", div_code_with_hint)
def test_invalid_div(code, expected_hint):
with pytest.raises(InvalidOperation) as e:
compile_code(code)

assert e.value._hint == "did you mean `5 // 9`?"
assert e.value._hint == expected_hint
4 changes: 2 additions & 2 deletions tests/functional/grammar/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ def test_basic_grammar_empty():
assert len(tree.children) == 0


def fix_terminal(terminal: str) -> bool:
def fix_terminal(terminal: str) -> str:
# these throw exceptions in the grammar
for bad in ("\x00", "\\ ", "\x0c"):
for bad in ("\x00", "\\ ", "\x0c", "\x0d"):
terminal = terminal.replace(bad, " ")
return terminal

Expand Down
12 changes: 12 additions & 0 deletions tests/functional/syntax/exceptions/test_constancy_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ def bar()->DynArray[uint16,3]:
@view
def topup(amount: uint256):
assert extcall self.token.transferFrom(msg.sender, self, amount)
""",
"""
@external
@view
def foo(_topic: bytes32):
raw_log([_topic], b"")
""",
"""
@external
@pure
def foo(_topic: bytes32):
raw_log([_topic], b"")
""",
],
)
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/syntax/test_external_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ def bar():
extcall Foo(msg.sender)
""",
StructureException,
"Function `type(interface Foo)` cannot be called without assigning the result",
"Function `type(Foo)` cannot be called without assigning the result",
None,
),
]
Expand Down
Loading

0 comments on commit cf6b25e

Please sign in to comment.