5/5
## Understanding Reverts in Vyper In smart contract development, ensuring the integrity of the contract's state and enforcing operational rules is paramount. Vyper, like other smart contract languages, provides mechanisms to handle errors and unmet conditions. One fundamental mechanism is the "revert." This lesson explains what reverts are, how they function in Vyper, and their implications, particularly regarding state changes and gas consumption. ### What is a Revert? A revert is an operation within a smart contract transaction that halts the execution immediately. When a transaction reverts: 1. **Execution Stops:** No further operations within the transaction are performed. 2. **State Changes Undone:** Crucially, *all* modifications made to the contract's state (changes to storage variables) during that specific transaction are rolled back. It's as if those operations never occurred within the transaction's context. 3. **Remaining Gas Returned:** Any gas allocated for operations that were *not* executed due to the revert is returned to the transaction sender. Reverts are primarily used for error handling and condition enforcement. If a transaction attempts an operation under invalid circumstances – such as insufficient payment, lack of authorization, or invalid input – the contract can trigger a revert to prevent an undesirable or inconsistent state change. ### Triggering Reverts with `assert` A common way to trigger a revert in Vyper is by using the `assert` statement. `assert` checks if a specific condition is true. If the condition evaluates to `false`, the transaction execution halts, and a revert occurs. Let's illustrate this with a modified `buy_me_a_coffee.vy` contract. We want to enforce that anyone calling the `fund()` function must send at least 1 Ether. We also add a state variable `my_num` and increment it *before* the `assert` check to observe the effect of reverts on state changes. ```vyper # @version ^0.3.0 # Example version pragma # State variable added to demonstrate state rollback my_num: public(uint256) # Made public for easy checking in Remix @external @payable def fund(): """ Allows users to send Ether to this contract. Enforces a minimum amount of 1 Ether. Demonstrates revert behavior with state changes. """ # State change BEFORE the assert check self.my_num = self.my_num + 2 # Assertion checking if at least 1 Ether (in wei) is sent assert msg.value >= as_wei_value(1, "ether"), "You must send at least 1 ETH!" # What is a revert? # A revert undoes any state changes that have been made within the transaction # up to the point of the revert, and sends the remaining gas back. ``` In this code: * We declare a `public` state variable `my_num` so we can easily query its value. * Inside `fund()`, we first increment `my_num` by 2. * Then, we use `assert` to check if `msg.value` (the amount of Ether sent with the transaction) is greater than or equal to 1 Ether (converted to wei). If this condition is false, the transaction reverts, and the provided reason string ("You must send at least 1 ETH!") is included in the error information. ### Observing Reverts in Action (Remix Example) Let's walk through how this contract behaves when deployed and interacted with, for instance, using the Remix IDE with the Vyper plugin: 1. **Deploy:** Deploy the contract. 2. **Check Initial State:** Query the value of `my_num`. It will be `0` (the default for `uint256`). 3. **Successful Transaction:** * Call the `fund()` function, sending `1 ether` (or more) with the transaction (`msg.value`). * The `assert msg.value >= as_wei_value(1, "ether")` condition is `true`. * The transaction completes successfully. * Query `my_num` again. Its value is now `2` (0 + 2). The increment operation persisted because the transaction did not revert. 4. **Reverting Transaction:** * Call the `fund()` function again, but this time send `0 ether` (`msg.value` is 0). * The line `self.my_num = self.my_num + 2` executes first. *Temporarily*, `my_num` would become 4 (2 + 2). * However, the `assert msg.value >= as_wei_value(1, "ether")` condition is now `false` (0 is not >= 1 Ether). * The `assert` triggers a revert. * Execution stops immediately. The transaction fails. Remix (or a similar tool) will likely show an error message indicating a revert, possibly including the reason string "You must send at least 1 ETH!". * Query `my_num` again. Its value is *still* `2`. * **Key Observation:** Even though the line incrementing `my_num` was executed *before* the `assert` statement failed, the revert operation undid this state change. The transaction failed atomically – none of its state changes were committed. 5. **Repeat:** If you call `fund()` successfully again with 1 Ether, `my_num` will become `4` (2 + 2). If you then call it with 0 Ether, it will revert, and `my_num` will remain `4`. This demonstrates the core principle: **Reverts ensure atomic state transitions within a single transaction. Either all state changes are applied, or none are.** ### Gas Consumption and Reverts A crucial point often misunderstood is the cost of reverted transactions. **Question:** Does a transaction that reverts still consume gas? **Answer:** Yes. **Explanation:** The Ethereum Virtual Machine (EVM) executes operations sequentially. Each operation (like adding numbers, storing data, checking conditions) has an associated gas cost. When a transaction is sent, the sender allocates a certain amount of gas. As the EVM executes the transaction's code, it consumes gas for each step performed. If a transaction reverts (e.g., due to a failed `assert`), gas is consumed for all operations performed *up to the point of the revert*. In our example, gas was used for: * Reading the initial value of `my_num`. * Performing the addition (`self.my_num + 2`). * Writing the temporary new value to `my_num`. * Reading `msg.value`. * Performing the comparison in the `assert`. The network nodes executing this code performed computational work and must be compensated for it via the gas fees paid for the consumed gas. However, any gas allocated for operations *after* the revert point (which were never executed) is refunded to the sender. ### Best Practices and Considerations * **User Experience:** While reverts are essential for contract integrity, frequent reverts caused by user input errors lead to a poor user experience. Users pay gas for a transaction that ultimately achieves nothing from their perspective. * **Frontend Validations:** Decentralized applications (dApps) and wallets (like MetaMask) often simulate transactions *before* prompting the user for confirmation. If the simulation predicts a revert, the dApp can warn the user or prevent the transaction submission altogether, saving them gas and frustration. * **Use `assert` for Internal Errors/Invariants:** `assert` is best used for checking conditions that should *never* be false if the contract logic is correct or if inputs have been properly validated *before* reaching potentially dangerous code sections. Examples include checking for arithmetic overflows (though Vyper often handles this implicitly) or ensuring internal state invariants hold. For validating external inputs or access control, other mechanisms might sometimes be preferred, although `assert` is commonly used here too. In summary, reverts are a fundamental control flow mechanism in Vyper smart contracts. They stop execution, undo all state changes within the transaction, and ensure contract integrity by enforcing conditions. While essential, remember that reverted transactions still cost gas up to the point of failure, making it important to design contracts and interfaces that minimize unnecessary reverts for end-users.
In smart contract development, ensuring the integrity of the contract's state and enforcing operational rules is paramount. Vyper, like other smart contract languages, provides mechanisms to handle errors and unmet conditions. One fundamental mechanism is the "revert." This lesson explains what reverts are, how they function in Vyper, and their implications, particularly regarding state changes and gas consumption.
A revert is an operation within a smart contract transaction that halts the execution immediately. When a transaction reverts:
Execution Stops: No further operations within the transaction are performed.
State Changes Undone: Crucially, all modifications made to the contract's state (changes to storage variables) during that specific transaction are rolled back. It's as if those operations never occurred within the transaction's context.
Remaining Gas Returned: Any gas allocated for operations that were not executed due to the revert is returned to the transaction sender.
Reverts are primarily used for error handling and condition enforcement. If a transaction attempts an operation under invalid circumstances – such as insufficient payment, lack of authorization, or invalid input – the contract can trigger a revert to prevent an undesirable or inconsistent state change.
assert
A common way to trigger a revert in Vyper is by using the assert
statement. assert
checks if a specific condition is true. If the condition evaluates to false
, the transaction execution halts, and a revert occurs.
Let's illustrate this with a modified buy_me_a_coffee.vy
contract. We want to enforce that anyone calling the fund()
function must send at least 1 Ether. We also add a state variable my_num
and increment it before the assert
check to observe the effect of reverts on state changes.
In this code:
We declare a public
state variable my_num
so we can easily query its value.
Inside fund()
, we first increment my_num
by 2.
Then, we use assert
to check if msg.value
(the amount of Ether sent with the transaction) is greater than or equal to 1 Ether (converted to wei). If this condition is false, the transaction reverts, and the provided reason string ("You must send at least 1 ETH!") is included in the error information.
Let's walk through how this contract behaves when deployed and interacted with, for instance, using the Remix IDE with the Vyper plugin:
Deploy: Deploy the contract.
Check Initial State: Query the value of my_num
. It will be 0
(the default for uint256
).
Successful Transaction:
Call the fund()
function, sending 1 ether
(or more) with the transaction (msg.value
).
The assert msg.value >= as_wei_value(1, "ether")
condition is true
.
The transaction completes successfully.
Query my_num
again. Its value is now 2
(0 + 2). The increment operation persisted because the transaction did not revert.
Reverting Transaction:
Call the fund()
function again, but this time send 0 ether
(msg.value
is 0).
The line self.my_num = self.my_num + 2
executes first. Temporarily, my_num
would become 4 (2 + 2).
However, the assert msg.value >= as_wei_value(1, "ether")
condition is now false
(0 is not >= 1 Ether).
The assert
triggers a revert.
Execution stops immediately. The transaction fails. Remix (or a similar tool) will likely show an error message indicating a revert, possibly including the reason string "You must send at least 1 ETH!".
Query my_num
again. Its value is still 2
.
Key Observation: Even though the line incrementing my_num
was executed before the assert
statement failed, the revert operation undid this state change. The transaction failed atomically – none of its state changes were committed.
Repeat: If you call fund()
successfully again with 1 Ether, my_num
will become 4
(2 + 2). If you then call it with 0 Ether, it will revert, and my_num
will remain 4
.
This demonstrates the core principle: Reverts ensure atomic state transitions within a single transaction. Either all state changes are applied, or none are.
A crucial point often misunderstood is the cost of reverted transactions.
Question: Does a transaction that reverts still consume gas?
Answer: Yes.
Explanation: The Ethereum Virtual Machine (EVM) executes operations sequentially. Each operation (like adding numbers, storing data, checking conditions) has an associated gas cost. When a transaction is sent, the sender allocates a certain amount of gas. As the EVM executes the transaction's code, it consumes gas for each step performed.
If a transaction reverts (e.g., due to a failed assert
), gas is consumed for all operations performed up to the point of the revert. In our example, gas was used for:
Reading the initial value of my_num
.
Performing the addition (self.my_num + 2
).
Writing the temporary new value to my_num
.
Reading msg.value
.
Performing the comparison in the assert
.
The network nodes executing this code performed computational work and must be compensated for it via the gas fees paid for the consumed gas. However, any gas allocated for operations after the revert point (which were never executed) is refunded to the sender.
User Experience: While reverts are essential for contract integrity, frequent reverts caused by user input errors lead to a poor user experience. Users pay gas for a transaction that ultimately achieves nothing from their perspective.
Frontend Validations: Decentralized applications (dApps) and wallets (like MetaMask) often simulate transactions before prompting the user for confirmation. If the simulation predicts a revert, the dApp can warn the user or prevent the transaction submission altogether, saving them gas and frustration.
Use assert
for Internal Errors/Invariants: assert
is best used for checking conditions that should never be false if the contract logic is correct or if inputs have been properly validated before reaching potentially dangerous code sections. Examples include checking for arithmetic overflows (though Vyper often handles this implicitly) or ensuring internal state invariants hold. For validating external inputs or access control, other mechanisms might sometimes be preferred, although assert
is commonly used here too.
In summary, reverts are a fundamental control flow mechanism in Vyper smart contracts. They stop execution, undo all state changes within the transaction, and ensure contract integrity by enforcing conditions. While essential, remember that reverted transactions still cost gas up to the point of failure, making it important to design contracts and interfaces that minimize unnecessary reverts for end-users.
An essential guide to Understanding Reverts in Vyper - Explore the critical role of reverts in Vyper for ensuring contract integrity and handling errors. Understand how `assert` triggers reverts, their impact on state changes, and gas consumption implications.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025
Duration: 2h 08min
Duration: 2h 32min
Duration: 24min
Course Overview
About the course
The basics of blockchain transactions, how to send and receive money on a blockchain network.
How to write Python based smart contracts using Vyper.
How to read and understand Vyper smart contracts.
Vyper data structures, arrays, structs, hash maps.
How to build a smart contract application and deploy on ZKsync with Moccasin.
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
On-chain Data Analyst
$59,000 - $139,000 (avg. salary)
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Last updated on June 11, 2025