From e5aeaf5c4207f5579f5209d502de0c1ba05fdfc5 Mon Sep 17 00:00:00 2001 From: Elli610 Date: Sun, 29 Sep 2024 10:49:13 +0200 Subject: [PATCH] add transient storage --- src/challenges/solidity/transient-storage.mdx | 69 +++++++++++++++++++ src/constants/solidity/code-exercises.ts | 12 ++++ src/constants/solidity/code-solutions.ts | 12 ++++ yarn.lock | 21 +++--- 4 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 src/challenges/solidity/transient-storage.mdx diff --git a/src/challenges/solidity/transient-storage.mdx b/src/challenges/solidity/transient-storage.mdx new file mode 100644 index 00000000..59946ded --- /dev/null +++ b/src/challenges/solidity/transient-storage.mdx @@ -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; + } +} +``` diff --git a/src/constants/solidity/code-exercises.ts b/src/constants/solidity/code-exercises.ts index e30875e8..5c8c3841 100644 --- a/src/constants/solidity/code-exercises.ts +++ b/src/constants/solidity/code-exercises.ts @@ -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}", + } }; diff --git a/src/constants/solidity/code-solutions.ts b/src/constants/solidity/code-solutions.ts index e33e0fe0..becd3245 100644 --- a/src/constants/solidity/code-solutions.ts +++ b/src/constants/solidity/code-solutions.ts @@ -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}`, + } }; diff --git a/yarn.lock b/yarn.lock index 8b2c8596..49a31313 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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/swc-linux-x64-musl@14.1.0": + 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" @@ -1821,11 +1826,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@parcel/watcher-darwin-arm64@2.4.0": - 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/watcher-wasm@2.3.0": version "2.3.0" resolved "https://registry.npmjs.org/@parcel/watcher-wasm/-/watcher-wasm-2.3.0.tgz" @@ -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"