Skip to content

add transient storage solidity exercise #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/challenges/solidity/transient-storage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: Transient Storage
index: 27
lesson: 26
summary: Introduction to Transient Storage in Solidity and its use in preventing reentrancy attacks
labels: ["solidity"]
---

# Transient Storage

Transient storage in Solidity is a new data storage location designed to be cleared out after each transaction. Unlike storage, which retains data across transactions, and memory, which is cleared after each function call, transient storage is cleared immediately after a transaction completes, providing a temporary storage option within the transaction scope.

## Purpose of Transient Storage

Transient storage was introduced to provide an efficient and cost-effective way to store data temporarily during a transaction. This storage location is particularly useful for implementing patterns such as reentrancy locks, where temporary state is needed only within the duration of a transaction.

## Reentrancy Attacks

Transient storage was initially added to the evm to protect contracts against what we call a **reentrancy attack**. It is a common vulnerability in Ethereum smart contracts that occurs when a contract calls an external contract and the external contract calls back into the original contract before the first call is completed. This can lead to unintended behavior, such as multiple withdrawals of funds. The attacker exploits the contract by recursively calling it in an attempt to drain funds before the original transaction is completed.

## Example Code

The transient storage location is accessed using the `tload` and `tstore` assembly functions, following the [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) standard.

Here are some examples illustrating the use of transient storage in Solidity:

```solidity
pragma solidity ^0.8.26;

// Make sure EVM version and VM is set to Cancun

// Storage - data is stored on the blockchain
// Memory - data is cleared out after a function call
// Transient storage - data is cleared out after a transaction

contract Generosity {
mapping(address => bool) sentGifts;

/**
* This modifier prevents a function from being called recursively
* by using transient storage to track the state of the function.
* the assembly scripts check if the function is already being called
* (ie: the value at storage slot 0 is 1) and reverts if it is.
*
* After the function exits, it resets the value at storage slot 0 to 0.
*/
modifier nonreentrant {
assembly {
if tload(0) { revert(0, 0) }
tstore(0, 1)
}
_;
// Unlocks the guard, making the pattern composable.
// After the function exits, it can be called again, even in the same transaction.
assembly {
tstore(0, 0)
}
}
function claimGift() nonreentrant public {
require(address(this).balance >= 1 ether);
require(!sentGifts[msg.sender]);
(bool success, ) = msg.sender.call{value: 1 ether}("");
require(success);

// In a reentrant function, doing this last would open up the vulnerability
sentGifts[msg.sender] = true;
}
}
```
12 changes: 12 additions & 0 deletions src/constants/solidity/code-exercises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,16 @@ export const CODE_EXERCISES: CodeTemplatesType = {
exercise5:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n// Step 1: Implement a contract named 'SendEtherChecked'\n// Step 2: Inside the 'SendEtherChecked' contract, declare a public mapping named 'balances' that maps addresses to uint256\n// Step 3: Still inside the 'SendEtherChecked' contract, implement a function named 'sendEther' that takes two parameters: an address payable 'recipient' and a uint256 'amount'\n// Step 4: The 'sendEther' function should be public and payable\n// Step 5: Inside the 'sendEther' function, add a require statement to check that the sender has enough Ether to send\n// Step 6: Still inside the 'sendEther' function, update the balances of the sender and the recipient\n// Step 7: Still inside the 'sendEther' function, use the 'call' method to send the specified amount of Ether to the specified address\n// Step 8: Add a require statement to check that the call was successful\n",
},
"transient-storage": {
exercise1:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TransientStorageExercise1 {\n\n\t// Task 1: Declare a constant bytes32 variable\n\t// named 'TRANSIENT_SLOT'\n\t// and set it to 0 to be used as the transient storage slot.\n\n\t// Task 2: Create a function named\n\t// 'storeTransientData' that:\n\t// - Takes a uint256 argument named 'data'\n\t// - Uses inline assembly to store 'data' in transient storage using 'tstore' at 'TRANSIENT_SLOT'\n\n}",
exercise2:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TransientStorageExercise2 {\n\n\tbytes32 constant TRANSIENT_SLOT = 0;\n\n\t// Create a function named 'loadTransientData' that:\n\t// - Returns a uint256\n\t// - Uses inline assembly to load and\n\t//return the value stored in\n\t// transient storage at 'TRANSIENT_SLOT'\n\n}",
exercise3:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TemporaryCounter {\n\n\tbytes32 constant COUNTER_SLOT = 3;\n\n\t// write a save() function to save the uint 8888 into the COUNTER_SLOT slot. Then update it to 9999\n\n}",
exercise4:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TransientStorageExercise4 {\n\n\t// Task 1: Declare a constant bytes32 variable\n\t// named 'TRANSIENT_SLOT'\n\t// and set it to 6 to be used as the transient storage slot.\n\n\t// Task 2: Create a function named\n\t// 'storeTransientData' that:\n\t// - Takes a uint256 argument named 'data'\n\t// - Uses inline assembly to store 'data' in transient storage using 'tstore' at 'TRANSIENT_SLOT'\n\n}",
exercise5:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TemporaryCounter {\n\n\tbytes32 constant COUNTER_SLOT = 188;\n\n\t// write a save() function to save the uint 123456789 into the COUNTER_SLOT slot. Then update it to 987654321\n\n}",
}
};
12 changes: 12 additions & 0 deletions src/constants/solidity/code-solutions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,16 @@ export const CODE_SOLUTIONS: CodeTemplatesType = {
exercise5:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\n// Step 1: Implement a contract named 'SendEtherChecked'\ncontract SendEtherChecked {\n\n // Step 2: Inside the 'SendEtherChecked' contract, declare a public mapping named 'balances' that maps addresses to uint256\n mapping(address => uint256) public balances;\n\n // Step 3: Still inside the 'SendEtherChecked' contract, implement a function named 'sendEther' that takes two parameters: an address payable 'recipient' and a uint256 'amount'\n // Step 4: The 'sendEther' function should be public and payable\n function sendEther(address payable recipient, uint256 amount) public payable {\n // Step 5: Inside the 'sendEther' function, add a require statement to check that the sender has enough Ether to send\n require(balances[msg.sender] >= amount, 'Not enough Ether');\n // Step 6: Still inside the 'sendEther' function, update the balances of the sender and the recipient\n balances[msg.sender] -= amount;\n balances[recipient] += amount;\n // Step 7: Still inside the 'sendEther' function, use the 'call' method to send the specified amount of Ether to the specified address\n (bool success, ) = recipient.call{value: amount}('');\n // Step 8: Add a require statement to check that the call was successful\n require(success, 'Failed to send Ether');\n }\n}",
},
"transient-storage": {
exercise1:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TransientStorageExercise1 {\n\n\t// Task 1: Declare a constant bytes32 variable\n\t// named 'TRANSIENT_SLOT'\n\t// and set it to 0 to be used as the transient storage slot.\n\n\tbytes32 constant TRANSIENT_SLOT = 0;\n\n\t// Task 2: Create a function named\n\t// 'storeTransientData' that:\n\t// - Takes a uint256 argument named 'data'\n\t// - Uses inline assembly to store 'data' in transient storage using 'tstore' at 'TRANSIENT_SLOT'\n\n}",
exercise2:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TransientStorageExercise2 {\n\n\tbytes32 constant TRANSIENT_SLOT = 0;\n\n\t// Create a function named 'loadTransientData' that:\n\t// - Returns a uint256\n\t// - Uses inline assembly to load and\n\t//return the value stored in\n\t// transient storage at 'TRANSIENT_SLOT'\n\n\tfunction loadTransientData() public view returns (uint256) {\n\t\tuint256 data;\n\t\tassembly {\n\t\t\tdata := tload(TRANSIENT_SLOT)\t\t}\n\t\treturn data;\n\t}\n}",
exercise3:
`// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TemporaryCounter {\n\n\tbytes32 constant COUNTER_SLOT = 3;\n\n\t// write a save() function to save the uint 8888 into the COUNTER_SLOT slot. Then update it to 9999\n\nfunction save() external {\n\t\tassembly {\n\t\t\tsstore(COUNTER_SLOT, 8888)\n\t\t\tsstore(COUNTER_SLOT, 9999)\n\t\t}\n\t}\n}`,
exercise4:
"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TransientStorageExercise4 {\n\n\t// Task 1: Declare a constant bytes32 variable\n\t// named 'TRANSIENT_SLOT'\n\t// and set it to 6 to be used as the transient storage slot.\n\n\tbytes32 constant TRANSIENT_SLOT = 6;\n\n\t// Task 2: Create a function named\n\t// 'storeTransientData' that:\n\t// - Takes a uint256 argument named 'data'\n\t// - Uses inline assembly to store 'data' in transient storage using 'tstore' at 'TRANSIENT_SLOT'\n\n}",
exercise5:
`// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\ncontract TemporaryCounter {\n\n\tbytes32 constant COUNTER_SLOT = 188;\n\n\t// write a save() function to save the uint 123456789 into the COUNTER_SLOT slot. Then update it to 987654321\n\nfunction save() external {\n\t\tassembly {\n\t\t\tsstore(COUNTER_SLOT, 123456789)\n\t\t\tsstore(COUNTER_SLOT, 987654321)\n\t\t}\n\t}\n}`,
}
};
21 changes: 8 additions & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1764,10 +1764,15 @@
dependencies:
source-map "^0.7.0"

"@next/swc-darwin-arm64@14.1.0":
"@next/swc-linux-x64-gnu@14.1.0":
version "14.1.0"
resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz"
integrity sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==
resolved "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz"
integrity sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==

"@next/[email protected]":
version "14.1.0"
resolved "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz"
integrity sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==

"@next/third-parties@^14.2.5":
version "14.2.5"
Expand Down Expand Up @@ -1821,11 +1826,6 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"

"@parcel/[email protected]":
version "2.4.0"
resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.0.tgz"
integrity sha512-T/At5pansFuQ8VJLRx0C6C87cgfqIYhW2N/kBfLCUvDhCah0EnLLwaD/6MW3ux+rpgkpQAnMELOCTKlbwncwiA==

"@parcel/[email protected]":
version "2.3.0"
resolved "https://registry.npmjs.org/@parcel/watcher-wasm/-/watcher-wasm-2.3.0.tgz"
Expand Down Expand Up @@ -5383,11 +5383,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==

fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==

function-bind@^1.1.1, function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
Expand Down