Skip to content

Commit

Permalink
Merge branch 'master' into fix/safe-relpath
Browse files Browse the repository at this point in the history
  • Loading branch information
charles-cooper authored Oct 4, 2024
2 parents 7ba1972 + 5d8280f commit 6beec4c
Show file tree
Hide file tree
Showing 25 changed files with 396 additions and 135 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
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
18 changes: 16 additions & 2 deletions tests/functional/builtins/codegen/test_raw_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ def __default__():
assert env.message_call(caller.address, data=sig) == b""


def _strip_initcode_suffix(bytecode):
bs = bytes.fromhex(bytecode.removeprefix("0x"))
to_strip = int.from_bytes(bs[-2:], "big")
return bs[:-to_strip].hex()


# check max_outsize=0 does same thing as not setting max_outsize.
# compile to bytecode and compare bytecode directly.
def test_max_outsize_0():
Expand All @@ -276,7 +282,11 @@ def test_raw_call(_target: address):
"""
output1 = compile_code(code1, output_formats=["bytecode", "bytecode_runtime"])
output2 = compile_code(code2, output_formats=["bytecode", "bytecode_runtime"])
assert output1 == output2
assert output1["bytecode_runtime"] == output2["bytecode_runtime"]

bytecode1 = output1["bytecode"]
bytecode2 = output2["bytecode"]
assert _strip_initcode_suffix(bytecode1) == _strip_initcode_suffix(bytecode2)


# check max_outsize=0 does same thing as not setting max_outsize,
Expand All @@ -298,7 +308,11 @@ def test_raw_call(_target: address) -> bool:
"""
output1 = compile_code(code1, output_formats=["bytecode", "bytecode_runtime"])
output2 = compile_code(code2, output_formats=["bytecode", "bytecode_runtime"])
assert output1 == output2
assert output1["bytecode_runtime"] == output2["bytecode_runtime"]

bytecode1 = output1["bytecode"]
bytecode2 = output2["bytecode"]
assert _strip_initcode_suffix(bytecode1) == _strip_initcode_suffix(bytecode2)


# test functionality of max_outsize=0
Expand Down
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
17 changes: 17 additions & 0 deletions tests/unit/ast/nodes/test_fold_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,20 @@ def test_compare_type_mismatch(op):
old_node = vyper_ast.body[0].value
with pytest.raises(UnfoldableNode):
old_node.get_folded_value()


@pytest.mark.parametrize("op", ["==", "!="])
def test_compare_eq_bytes(get_contract, op):
left, right = "0xA1AAB33F", "0xa1aab33f"
source = f"""
@external
def foo(a: bytes4, b: bytes4) -> bool:
return a {op} b
"""
contract = get_contract(source)

vyper_ast = parse_and_fold(f"{left} {op} {right}")
old_node = vyper_ast.body[0].value
new_node = old_node.get_folded_value()

assert contract.foo(left, right) == new_node.value
3 changes: 3 additions & 0 deletions tests/unit/ast/nodes/test_hex.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def foo():
"""
foo: constant(bytes4) = 0x12_34_56
""",
"""
foo: constant(bytes4) = 0X12345678
""",
]


Expand Down
37 changes: 28 additions & 9 deletions tests/unit/compiler/test_bytecode_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ def test_bytecode_runtime():


def test_bytecode_signature():
out = vyper.compile_code(simple_contract_code, output_formats=["bytecode_runtime", "bytecode"])
out = vyper.compile_code(
simple_contract_code, output_formats=["bytecode_runtime", "bytecode", "integrity"]
)

runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))

metadata = _parse_cbor_metadata(initcode)
runtime_len, data_section_lengths, immutables_len, compiler = metadata
integrity_hash, runtime_len, data_section_lengths, immutables_len, compiler = metadata

assert integrity_hash.hex() == out["integrity"]

assert runtime_len == len(runtime_code)
assert data_section_lengths == []
Expand All @@ -73,14 +77,18 @@ def test_bytecode_signature_dense_jumptable():
settings = Settings(optimize=OptimizationLevel.CODESIZE)

out = vyper.compile_code(
many_functions, output_formats=["bytecode_runtime", "bytecode"], settings=settings
many_functions,
output_formats=["bytecode_runtime", "bytecode", "integrity"],
settings=settings,
)

runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))

metadata = _parse_cbor_metadata(initcode)
runtime_len, data_section_lengths, immutables_len, compiler = metadata
integrity_hash, runtime_len, data_section_lengths, immutables_len, compiler = metadata

assert integrity_hash.hex() == out["integrity"]

assert runtime_len == len(runtime_code)
assert data_section_lengths == [5, 35]
Expand All @@ -92,14 +100,18 @@ def test_bytecode_signature_sparse_jumptable():
settings = Settings(optimize=OptimizationLevel.GAS)

out = vyper.compile_code(
many_functions, output_formats=["bytecode_runtime", "bytecode"], settings=settings
many_functions,
output_formats=["bytecode_runtime", "bytecode", "integrity"],
settings=settings,
)

runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))

metadata = _parse_cbor_metadata(initcode)
runtime_len, data_section_lengths, immutables_len, compiler = metadata
integrity_hash, runtime_len, data_section_lengths, immutables_len, compiler = metadata

assert integrity_hash.hex() == out["integrity"]

assert runtime_len == len(runtime_code)
assert data_section_lengths == [8]
Expand All @@ -108,13 +120,17 @@ def test_bytecode_signature_sparse_jumptable():


def test_bytecode_signature_immutables():
out = vyper.compile_code(has_immutables, output_formats=["bytecode_runtime", "bytecode"])
out = vyper.compile_code(
has_immutables, output_formats=["bytecode_runtime", "bytecode", "integrity"]
)

runtime_code = bytes.fromhex(out["bytecode_runtime"].removeprefix("0x"))
initcode = bytes.fromhex(out["bytecode"].removeprefix("0x"))

metadata = _parse_cbor_metadata(initcode)
runtime_len, data_section_lengths, immutables_len, compiler = metadata
integrity_hash, runtime_len, data_section_lengths, immutables_len, compiler = metadata

assert integrity_hash.hex() == out["integrity"]

assert runtime_len == len(runtime_code)
assert data_section_lengths == []
Expand All @@ -129,7 +145,10 @@ def test_bytecode_signature_deployed(code, get_contract, env):
deployed_code = env.get_code(c.address)

metadata = _parse_cbor_metadata(c.bytecode)
runtime_len, data_section_lengths, immutables_len, compiler = metadata
integrity_hash, runtime_len, data_section_lengths, immutables_len, compiler = metadata

out = vyper.compile_code(code, output_formats=["integrity"])
assert integrity_hash.hex() == out["integrity"]

assert compiler == {"vyper": list(vyper.version.version_tuple)}

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/compiler/venom/test_duplicate_operands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_duplicate_operands():
%3 = mul %1, %2
stop
Should compile to: [PUSH1, 10, DUP1, DUP1, DUP1, ADD, MUL, STOP]
Should compile to: [PUSH1, 10, DUP1, DUP1, DUP1, ADD, MUL, POP, STOP]
"""
ctx = IRContext()
fn = ctx.create_function("test")
Expand All @@ -24,4 +24,4 @@ def test_duplicate_operands():
bb.append_instruction("stop")

asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.GAS)
assert asm == ["PUSH1", 10, "DUP1", "DUP1", "ADD", "MUL", "STOP"]
assert asm == ["PUSH1", 10, "DUP1", "DUP1", "ADD", "MUL", "POP", "STOP"]
31 changes: 31 additions & 0 deletions tests/unit/compiler/venom/test_sccp.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,34 @@ def test_cont_phi_const_case():
assert sccp.lattice[IRVariable("%5", version=1)].value == 106
assert sccp.lattice[IRVariable("%5", version=2)].value == 97
assert sccp.lattice[IRVariable("%5")].value == 2


def test_phi_reduction_after_unreachable_block():
ctx = IRContext()
fn = ctx.create_function("_global")

bb = fn.get_basic_block()

br1 = IRBasicBlock(IRLabel("then"), fn)
fn.append_basic_block(br1)
join = IRBasicBlock(IRLabel("join"), fn)
fn.append_basic_block(join)

op = bb.append_instruction("store", 1)
true = IRLiteral(1)
bb.append_instruction("jnz", true, br1.label, join.label)

op1 = br1.append_instruction("store", 2)

br1.append_instruction("jmp", join.label)

join.append_instruction("phi", bb.label, op, br1.label, op1)
join.append_instruction("stop")

ac = IRAnalysesCache(fn)
SCCP(ac, fn).run_pass()

assert join.instructions[0].opcode == "store", join.instructions[0]
assert join.instructions[0].operands == [op1]

assert join.instructions[1].opcode == "stop"
49 changes: 49 additions & 0 deletions tests/unit/compiler/venom/test_simplify_cfg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from vyper.venom.analysis.analysis import IRAnalysesCache
from vyper.venom.basicblock import IRBasicBlock, IRLabel, IRLiteral
from vyper.venom.context import IRContext
from vyper.venom.passes.sccp import SCCP
from vyper.venom.passes.simplify_cfg import SimplifyCFGPass


def test_phi_reduction_after_block_pruning():
ctx = IRContext()
fn = ctx.create_function("_global")

bb = fn.get_basic_block()

br1 = IRBasicBlock(IRLabel("then"), fn)
fn.append_basic_block(br1)
br2 = IRBasicBlock(IRLabel("else"), fn)
fn.append_basic_block(br2)

join = IRBasicBlock(IRLabel("join"), fn)
fn.append_basic_block(join)

true = IRLiteral(1)
bb.append_instruction("jnz", true, br1.label, br2.label)

op1 = br1.append_instruction("store", 1)
op2 = br2.append_instruction("store", 2)

br1.append_instruction("jmp", join.label)
br2.append_instruction("jmp", join.label)

join.append_instruction("phi", br1.label, op1, br2.label, op2)
join.append_instruction("stop")

ac = IRAnalysesCache(fn)
SCCP(ac, fn).run_pass()
SimplifyCFGPass(ac, fn).run_pass()

bbs = list(fn.get_basic_blocks())

assert len(bbs) == 1
final_bb = bbs[0]

inst0, inst1, inst2 = final_bb.instructions

assert inst0.opcode == "store"
assert inst0.operands == [IRLiteral(1)]
assert inst1.opcode == "store"
assert inst1.operands == [inst0.output]
assert inst2.opcode == "stop"
16 changes: 16 additions & 0 deletions tests/unit/compiler/venom/test_stack_cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from vyper.compiler.settings import OptimizationLevel
from vyper.venom import generate_assembly_experimental
from vyper.venom.context import IRContext


def test_cleanup_stack():
ctx = IRContext()
fn = ctx.create_function("test")
bb = fn.get_basic_block()
ret_val = bb.append_instruction("param")
op = bb.append_instruction("store", 10)
bb.append_instruction("add", op, op)
bb.append_instruction("ret", ret_val)

asm = generate_assembly_experimental(ctx, optimize=OptimizationLevel.GAS)
assert asm == ["PUSH1", 10, "DUP1", "ADD", "POP", "JUMP"]
28 changes: 28 additions & 0 deletions tests/unit/compiler/venom/test_stack_reorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from vyper.venom import generate_assembly_experimental
from vyper.venom.context import IRContext


def test_stack_reorder():
"""
Test to was created from the example in the
issue https://github.com/vyperlang/vyper/issues/4215
this example should fail with original stack reorder
algorithm but succeed with new one
"""
ctx = IRContext()
fn = ctx.create_function("_global")

bb = fn.get_basic_block()
var0 = bb.append_instruction("store", 1)
var1 = bb.append_instruction("store", 2)
var2 = bb.append_instruction("store", 3)
var3 = bb.append_instruction("store", 4)
var4 = bb.append_instruction("store", 5)

bb.append_instruction("staticcall", var0, var1, var2, var3, var4, var3)

ret_val = bb.append_instruction("add", var4, var4)

bb.append_instruction("ret", ret_val)

generate_assembly_experimental(ctx)
5 changes: 5 additions & 0 deletions vyper/ast/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,10 +854,15 @@ class Hex(Constant):

def validate(self):
if "_" in self.value:
# TODO: revisit this, we should probably allow underscores
raise InvalidLiteral("Underscores not allowed in hex literals", self)
if len(self.value) % 2:
raise InvalidLiteral("Hex notation requires an even number of digits", self)

if self.value.startswith("0X"):
hint = f"Did you mean `0x{self.value[2:]}`?"
raise InvalidLiteral("Hex literal begins with 0X!", self, hint=hint)

@property
def n_nibbles(self):
"""
Expand Down
Loading

0 comments on commit 6beec4c

Please sign in to comment.