# Vyper and the EVM

**What is Vyper?**

Vyper is a contract-oriented, domain-specific programming language targeting the Ethereum Virtual Machine (EVM). It focuses on simplicity, security, and auditability. Designed as an alternative to Solidity, Vyper has gained traction among developers who prioritize security over complexity. However, vulnerabilities like the reentrancy bug in older Vyper versions remind us that even the most security-focused tools can be vulnerable if not properly tested.

**The Ethereum Virtual Machine (EVM)**

The EVM is a **single-threaded, non-concurrent** environment. When an Ethereum smart contract calls another contract, the control flow is passed entirely to the called contract. This means that the original contract's execution is "paused" until the invoked contract finishes executing. Reentrancy vulnerabilities occur when the called contract maliciously calls back into the original contract before it finishes updating its state.

***

#### Understanding Reentrancy Attacks

**What is a Reentrancy Attack?**

A **reentrancy attack** occurs when a contract temporarily passes control to an external contract, and that external contract calls back into the original contract before the original execution is complete. This can lead to unintended consequences, such as draining funds or performing unauthorized actions. Reentrancy attacks are possible because smart contracts in Ethereum do not automatically lock execution once an external call is made.

**Exploit Flow**

1. **External Call**: The original contract makes an external call to a malicious contract (e.g., sending tokens or Ether).
2. **Recursive Call**: The malicious contract calls back into the original contract before the first function finishes execution.
3. **Outdated State**: Since the original contract has not yet updated its state (e.g., reduced the balance), the attacker can perform actions multiple times, exploiting the outdated state.

This pattern can be used to drain contracts of funds or perform other malicious actions.

***

#### Reentrancy in Vyper: The Vulnerable Versions

In Vyper, reentrancy protection is typically provided by the `@nonreentrant` decorator, which prevents functions from being called recursively within the same transaction. However, in Vyper versions 0.2.15, 0.2.16, and 0.3.0, a bug in the compiler led to a malfunctioning implementation of this decorator, allowing reentrancy attacks to bypass the protection entirely.

**Vulnerable Vyper Versions (0.2.15, 0.2.16, 0.3.0)**

In these versions, the compiler introduced improper storage allocation for the `@nonreentrant` decorator, causing reentrancy guards to malfunction. Specifically:

* The storage slots allocated to track reentrancy keys were not managed correctly.
* This led to overlapping storage slots, where reentrancy keys could be overwritten by other variables in the contract.
* As a result, contracts compiled with these versions were vulnerable to reentrancy attacks, even if they used the `@nonreentrant` decorator.

This vulnerability went unnoticed for several months, affecting a number of high-profile projects, including Curve.Fi, which lost millions of dollars due to the exploit.

**Example of a Vulnerable Contract**

Here’s an example of a vulnerable contract using Vyper 0.2.15:

```python
# Vulnerable Contract
balances: public(map(address, uint256))

@external
@nonreentrant("withdraw")
def withdraw(amount: uint256):
    assert self.balances[msg.sender] >= amount, "Insufficient balance"
    # Send Ether to the user
    send(msg.sender, amount)
    # Update the state after the send
    self.balances[msg.sender] -= amount
```

In this example, the `@nonreentrant("withdraw")` decorator is supposed to prevent the function from being called recursively. However, due to the bug in Vyper 0.2.15, the reentrancy guard fails, and a malicious contract could exploit this vulnerability by reentering the `withdraw` function before the state is updated, allowing it to drain the contract.

***

#### How the Vulnerability was Fixed

The vulnerability was fixed in **Vyper 0.3.1**. The primary change was in how the compiler handled the allocation of storage slots for reentrancy keys. Instead of allocating a new slot for each instance of the `@nonreentrant` decorator, the compiler now ensures that reentrancy keys are allocated properly without overlapping with other storage variables.

**Fix in Vyper 0.3.1:**

```python
for node in vyper_module.get_children(vy_ast.FunctionDef):
    type_ = node._metadata["type"]
    if type_.nonreentrant is not None:
        variable_name = f"nonreentrant.{type_.nonreentrant}"

        # Ensure each key is allocated only once
        if variable_name in ret:
            continue

        type_.set_reentrancy_key_position(StorageSlot(storage_slot))
        ret[variable_name] = {
            "type": "nonreentrant lock",
            "location": "storage",
            "slot": storage_slot,
        }
        storage_slot += 1
```

With this fix, the compiler properly allocates storage slots for each reentrancy key, ensuring that reentrancy locks function as intended.

***

#### Mitigating Reentrancy in Vyper

There are two primary ways to mitigate reentrancy vulnerabilities in Vyper:

1. **Use the Latest Version of Vyper**: Always ensure you are using the latest stable version of Vyper, as vulnerabilities like the one in versions 0.2.15, 0.2.16, and 0.3.0 are patched in newer versions.
2. **Follow the Checks-Effects-Interactions (CEI) Pattern**: The CEI pattern helps ensure that state changes are made before any external calls, reducing the risk of reentrancy attacks.

**Example of the CEI Pattern in Vyper**

```python
@external
@nonreentrant("withdraw")
def safe_withdraw(amount: uint256):
    # Checks: Ensure the user has enough balance
    assert self.balances[msg.sender] >= amount, "Insufficient balance"
    
    # Effects: Update the user's balance
    self.balances[msg.sender] -= amount
    
    # Interactions: Transfer the Ether
    send(msg.sender, amount)
```

In this example, the state is updated before the external call (`send(msg.sender, amount)`), ensuring that any reentrancy attack would encounter an updated state and thus fail.

***

#### Conclusion

Reentrancy vulnerabilities are a critical security issue in smart contracts, and the Vyper compiler bug in versions 0.2.15, 0.2.16, and 0.3.0 demonstrated the importance of properly implemented reentrancy guards. Although Vyper provides the `@nonreentrant` decorator as a language-level protection, compiler bugs can undermine these safeguards, making it essential to stay up-to-date with the latest versions of the language.

By following best practices such as the CEI pattern and using the latest Vyper compiler, developers can protect their contracts from reentrancy attacks. Furthermore, regular auditing and security reviews should be a standard practice for any project handling significant amounts of funds.

Finally, it’s essential for the developer community to keep a close eye on compiler updates and vulnerabilities to avoid repeating these costly mistakes.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://zokyo-auditing-tutorials.gitbook.io/zokyo-tutorials/tutorial-61-vyper-vulnerable-versions/vyper-and-the-evm.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
