diff --git a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/2-erc20-basics/+page.md b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/2-erc20-basics/+page.md index 1936507eb..1517d39b7 100644 --- a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/2-erc20-basics/+page.md +++ b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/2-erc20-basics/+page.md @@ -24,7 +24,7 @@ If `EIPs` get enough traction to warrant genuine consideration they will often g New `Improvement Proposals` and `Requests for Comments` are tracked on websites such as [**eips.ethereum.org**](https://eips.ethereum.org/), where you can watch these proposals go through the process real time and be adopted or rejected by the community. - +::image{src='/foundry-erc20s/1-erc20-basics/erc20-basics1.png' style='width: 100%; height: auto;'} ### ERC20 diff --git a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/3-erc20-manual-creation/+page.md b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/3-erc20-manual-creation/+page.md index 7a97e00dd..4983c61bc 100644 --- a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/3-erc20-manual-creation/+page.md +++ b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/3-erc20-manual-creation/+page.md @@ -32,7 +32,7 @@ Completing these steps sets up a development environment with some convenient bo Go ahead and delete our 3 `Counter` examples so we can start with a clean slate. - +::image{src='/foundry-erc20s/2-erc20-manual-creation/erc20-manual-creation1.png' style='width: 100%; height: auto;'} I'm going to show you 2 different ways to create our own token, first the hard way and then a much easier way! diff --git a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/4-erc20-open-zeppelin/+page.md b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/4-erc20-open-zeppelin/+page.md index 0bc6e1f88..7be518c18 100644 --- a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/4-erc20-open-zeppelin/+page.md +++ b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/4-erc20-open-zeppelin/+page.md @@ -19,7 +19,7 @@ Access [OpenZeppelin's documentation](https://docs.openzeppelin.com/contracts/4. Additionally, OpenZeppelin offers a contract wizard, streamlining the contract creation process — perfect for tokens, governances, or custom contracts. - +::image{src='/foundry-erc20s/3-erc20-open-zeppelin/ERC20-open-zeppelin1.png' style='width: 100%; height: auto;'} Let's leverage OpenZeppelin to create a new ERC20 Token. Create a new file within `src` named `OurToken.sol`. Once that's done, let's install the OpenZeppelin library into our contract. @@ -70,7 +70,7 @@ For the purposes of simple examples like this, I like to mint the initialSupply As always we can perform a sanity check to assure things are working as expected by running `forge build`. - +::image{src='/foundry-erc20s/3-erc20-open-zeppelin/ERC20-open-zeppelin2.png' style='width: 100%; height: auto;'} Nailed it. diff --git a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/5-erc20-deploy-script/+page.md b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/5-erc20-deploy-script/+page.md index 3f8b9de92..4a33359e2 100644 --- a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/5-erc20-deploy-script/+page.md +++ b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/5-erc20-deploy-script/+page.md @@ -116,7 +116,7 @@ verify: Now, by running `make anvil` (open a new terminal once your chain has started!) followed by `make deploy`... - +::image{src='/foundry-erc20s/4-erc20-deploy-script/erc20-deploy-script1.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/6-erc20-ai-tests-and-recap/+page.md b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/6-erc20-ai-tests-and-recap/+page.md index aa6ed70fd..775c23184 100644 --- a/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/6-erc20-ai-tests-and-recap/+page.md +++ b/courses/advanced-foundry/1-How-to-create-an-erc20-crypto-currency/6-erc20-ai-tests-and-recap/+page.md @@ -98,7 +98,7 @@ Let's run it! forge test --mt testBobBalance ``` - +::image{src='/foundry-erc20s/5-erc20-ai-tests-and-recap/erc20-ai-tests-and-recap1.png' style='width: 100%; height: auto;'} Easy pass. Let's write a couple more tests and then we'll see what AI can do to help us. @@ -106,19 +106,19 @@ Easy pass. Let's write a couple more tests and then we'll see what AI can do to Next, let's test some approvals. The ERC20 standard contains an important function, `transferFrom`. It is often the case that a smart contract protocol may need to transfer tokens _on behalf_ of a user the way this access is controlled is through the `transferFrom` function. - +::image{src='/foundry-erc20s/5-erc20-ai-tests-and-recap/erc20-ai-tests-and-recap2.png' style='width: 100%; height: auto;'} In summary, an address needs to be approved by another in order to transfer tokens on their behalf, otherwise the transaction should revert with an error. Approvals, naturally, are handled through the `approve` and allowance functionality within the ERC20 standard. - +::image{src='/foundry-erc20s/5-erc20-ai-tests-and-recap/erc20-ai-tests-and-recap3.png' style='width: 100%; height: auto;'} Through these methods a user is able to approve another address to spend, or otherwise interact with, a limited (or often unlimited) number of tokens. The security risks associated with this are pretty clear, which is why we've seen services like Etherscan's Token Approval Checker pop up. These allow you to see at a glance which addresses possess approvals for tokens in your wallet. - +::image{src='/foundry-erc20s/5-erc20-ai-tests-and-recap/erc20-ai-tests-and-recap4.png' style='width: 100%; height: auto;'} While it costs a little gas, it's good practice to regularly assess your approvals and revoke them when no longer applicable or appropriate. @@ -159,11 +159,11 @@ Let's run the test! forge test --mt testAllowancesWork ``` - +::image{src='/foundry-erc20s/5-erc20-ai-tests-and-recap/erc20-ai-tests-and-recap5.png' style='width: 100%; height: auto;'} Nice, another pass! However, if we run `forge coverage` ... - +::image{src='/foundry-erc20s/5-erc20-ai-tests-and-recap/erc20-ai-tests-and-recap6.png' style='width: 100%; height: auto;'} Abysmal. We have a long way to go! diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/1-nfts/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/1-nfts/+page.md index 3937d4b59..3ade0608f 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/1-nfts/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/1-nfts/+page.md @@ -17,13 +17,13 @@ Welcome back! In this section of the course we'll be investigate Non-fungible To As mentioned, we'll be learning two approaches to simple NFT development in this course. This first will be a basic implementation using these cute puppies! - +::image{src='/foundry-nfts/1-nfts/nfts1.png' style='width: 100%; height: auto;'} In this first basic implementation our images are going to be stored in [**IPFS**](https://ipfs.tech/). With our second NFT, the art is going to be stored _on-chain_ and dynamic, changing based on a criteria we set, setting our mood from happy to sad or vice versa! - +::image{src='/foundry-nfts/1-nfts/nfts2.png' style='width: 100%; height: auto;'} And, perhaps most excitingly, by the end of this section you'll have you're very own NFTs imported into your own wallet/metamask. You can also view them on service like OpenSea which will allow you to sell, trade, view and collect all sorts of NFTs! diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/10-ipfs-https/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/10-ipfs-https/+page.md index e7b5c7af1..3d976027a 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/10-ipfs-https/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/10-ipfs-https/+page.md @@ -30,15 +30,15 @@ In addition to the above, the `IPFS` network doesn't automatically distribute al Fortunately, there are services available which developers can use to pin their data for them, decentralizing access to it. One such service is [**Pinata.cloud**](https://www.pinata.cloud/). - +::image{src='/foundry-nfts/10-ipfs-https/ipfs-https1.png' style='width: 100%; height: auto;'} Once an account is created and you've logged in, the UI functions much like an `IPFS` node and you can simply upload any files you want the service to pin on your behalf. - +::image{src='/foundry-nfts/10-ipfs-https/ipfs-https2.png' style='width: 100%; height: auto;'} Once uploaded, `Pinata` will provide a `CID`, just like `IPFS` itself will. - +::image{src='/foundry-nfts/10-ipfs-https/ipfs-https3.png' style='width: 100%; height: auto;'} > ❗ **PROTIP** > Whenever I work on a project, I will upload my images/data both to my local `IPFS` node as well as `Pinata` to assure the data is always pinned _somewhere_. @@ -51,4 +51,4 @@ In the next lesson we'll discuss `Scalable Vector Graphics`, or `SVGs` and how i See you there! - +::image{src='/foundry-nfts/10-ipfs-https/ipfs-https4.png' style='width: 100%; height: auto;'} diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/11-what-is-svg/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/11-what-is-svg/+page.md index da63dd7d6..c06217235 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/11-what-is-svg/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/11-what-is-svg/+page.md @@ -31,7 +31,7 @@ To understand what an `SVG` is, we'll dive right into a helpful tutorial from ou ``` - +::image{src='/foundry-nfts/11-what-is-svg/what-is-svg1.png' style='width: 100%; height: auto;'} SVGs are awesome because they maintain their quality, no matter what size you make them. If you stretch a traditional image file like a .jpg or .png, they become pixelated and lose clarity. SVGs don’t suffer from this issue because they’re scalable. They’re defined within an exact parameter, thus maintaining their quality regardless of scale. @@ -52,7 +52,7 @@ Let's look at how we can create our own simple SVG, right in our IDE. Create the > ❗ **IMPORTANT** > You will likely need to download a SVG preview extention to view the SVG in your IDE. I recommend trying [**SVG Preview**](https://marketplace.visualstudio.com/items?itemName=SimonSiefke.svg-preview). - +::image{src='/foundry-nfts/11-what-is-svg/what-is-svg2.png' style='width: 100%; height: auto;'} Importantly, this SVG code **_is not_** a URI, but we can convert this into a URI that a browser can understand by passing all the necessary data through the URL of our browser. @@ -86,7 +86,7 @@ Copy this whole string into your browser and you should see our SVG! data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIj4KPHRleHQgeD0iMjAwIiB5PSIyNTAiIGZpbGw9IndoaXRlIj5IaSEgWW91IGRlY29kZWQgdGhpcyEgPC90ZXh0Pgo8L3N2Zz4= ``` - +::image{src='/foundry-nfts/11-what-is-svg/what-is-svg3.png' style='width: 100%; height: auto;'} This same process can be applied to our SVG images for our NFTs. You can navigate to the [**GitHub Repo**](https://github.com/Cyfrin/foundry-nft-f23/blob/main/images/dynamicNft/happy.svg?short_path=224d82e) to see the code which represents our happy and sad SVGs. diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/12-svg-nft/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/12-svg-nft/+page.md index 10525a68d..932005775 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/12-svg-nft/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/12-svg-nft/+page.md @@ -12,7 +12,7 @@ Ok, we've gained lots of context and understand about data storage in general an At the core of the NFT we'll build is a `flipMood` function which allows the owner to flip their NFT between happy and sad images. - +::image{src='/foundry-nfts/12-svg-nft/svg-nft1.png' style='width: 100%; height: auto;'} Start with creating the file `src/MoodNft.sol` and filling out the usual boilerplate. We're definitely getting good at this by now. diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/13-svg-nft-encoding/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/13-svg-nft-encoding/+page.md index e89034a5b..b03d4f8f6 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/13-svg-nft-encoding/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/13-svg-nft-encoding/+page.md @@ -276,11 +276,11 @@ Logs: This looks pretty good! If we paste this into our browser we should see... - +::image{src='/foundry-nfts/13-svg-nft-encoding/svg-nft-encoding2.png' style='width: 100%; height: auto;'} ... That looks like a JSON to me! Now, let's copy that imageURI into our browser... - +::image{src='/foundry-nfts/13-svg-nft-encoding/svg-nft-encoding3.png' style='width: 100%; height: auto;'} Close enough! diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/15-svg-deploy/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/15-svg-deploy/+page.md index 1255865cc..6364afe31 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/15-svg-deploy/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/15-svg-deploy/+page.md @@ -119,7 +119,7 @@ All that's left is to run our test! forge test --mt testConvertSvgToUri ``` - +::image{src='/foundry-nfts/15-svg-deploy/svg-deploy1.png' style='width: 100%; height: auto;'} Nailed it! Our solidity scripted encoding is working just like our command line. @@ -178,7 +178,7 @@ Because we're now using a deployment script, our testing framework is changing a Create the directories `test/integration` and `test/unit`. Within `test/integration` create a copy of our `MoodNftTest.t.sol` and name it something like `MoodNftIntegrationsTest.t.sol`, and move our `BasicNft.t.sol` file here as well (it uses a deployer too!). - +::image{src='/foundry-nfts/15-svg-deploy/svg-deploy2.png' style='width: 100%; height: auto;'} We'll adjust `MoodNftIntegrationsTest.t.sol` to use our deployer next. @@ -259,7 +259,7 @@ Let's run it! forge test --mt testFlipMoodIntegration ``` - +::image{src='/foundry-nfts/15-svg-deploy/svg-deploy3.png' style='width: 100%; height: auto;'} Uh oh. That ain't right. diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/16-svg-debug/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/16-svg-debug/+page.md index 83b67bbdd..96412a28a 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/16-svg-debug/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/16-svg-debug/+page.md @@ -14,7 +14,7 @@ In the last lesson we left off with a gross error that hit us when running our n forge test --mt testFlipMoodIntegration -vvv ``` - +::image{src='/foundry-nfts/16-svg-debug/svg-debug1.png' style='width: 100%; height: auto;'} Hmm, this gives us a little more information, detailing that our assertion failed as well as providing us an output of one of the SVG URIs, but I think we can do better. @@ -33,7 +33,7 @@ Let's run it again. forge test --mt testFlipMoodIntegration -vvv ``` - +::image{src='/foundry-nfts/16-svg-debug/svg-debug2.png' style='width: 100%; height: auto;'} Well, our hashes are definitely different. We can import console and log out some variables to see what's going wrong. @@ -62,11 +62,11 @@ Running this now, should output our tokenURI, which we can verify in our browser Pasting this into our browser and checking the imageUri we should be able to verify that this _is_ the Sad tokenUri.. so what's going on? - +::image{src='/foundry-nfts/16-svg-debug/svg-debug3.png' style='width: 100%; height: auto;'} Let's check the other side of the assertion. We have the SAD_SVG_URI as a constant variable, let's toss it into our browser. - +::image{src='/foundry-nfts/16-svg-debug/svg-debug4.png' style='width: 100%; height: auto;'} Wait a minute! One of these is returning our **_tokenURI_** and the other is our **_imageURI_**! This is why it's important to be explicit in our naming conventions! Let's adjust these constants, and our test, right away. We can define a variable with what we expect the **_tokenURI_** to be and assert versus that. @@ -88,7 +88,7 @@ function testFlipMoodIntegration() public { With these adjustments, we can run our test again... - +::image{src='/foundry-nfts/16-svg-debug/svg-debug5.png' style='width: 100%; height: auto;'} Beautiful! @@ -102,4 +102,4 @@ I highly encourage you to try to write this script for MoodNFT. It should be abl Your second call to action is going to be increasing the coverage of our contracts. Write some tests and try to get MoodNFT and our scripts closer to 100%! - +::image{src='/foundry-nfts/16-svg-debug/svg-debug6.png' style='width: 100%; height: auto;'} diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/17-svg-anvil/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/17-svg-anvil/+page.md index 57fdea050..3e362a568 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/17-svg-anvil/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/17-svg-anvil/+page.md @@ -24,23 +24,23 @@ deployMood: Looks great! Remember, you can add anvil as at network to Metamask by navigating to your network selector and choosing `+ Add network`. - +::image{src='/foundry-nfts/17-svg-anvil/svg-anvil2.png' style='width: 100%; height: auto;'} Choose to add a network manually and enter the details as shown below: - +::image{src='/foundry-nfts/17-svg-anvil/svg-anvil3.png' style='width: 100%; height: auto;'} If you need to import an anvil account, this is simple as well. When an anvil chain is spun up, it provides you with public and private keys for a number of default accounts. In your Metamask account selector, choose `+ add account or hardware wallet` - +::image{src='/foundry-nfts/17-svg-anvil/svg-anvil4.png' style='width: 100%; height: auto;'} Select `import account` and enter one of the default private keys offered by the anvil chain. - +::image{src='/foundry-nfts/17-svg-anvil/svg-anvil5.png' style='width: 100%; height: auto;'} Once everything is set up, we should be able to run `make deployMood`... - +::image{src='/foundry-nfts/17-svg-anvil/svg-anvil1.png' style='width: 100%; height: auto;'} With the contract address, we should be able to use a cast command to interact with it. @@ -52,7 +52,7 @@ When that transaction completes, what we can _finally_ do, is take that contract Once imported ... - +::image{src='/foundry-nfts/17-svg-anvil/svg-anvil6.png' style='width: 100%; height: auto;'} LETS GOOOO! Now we need to flip it. We should be able to use largely the same `cast` command, let's just adjust the function to `flipMood` @@ -66,7 +66,7 @@ rpc-url http://localhost:8545 Once we reimport our NFT however... - +::image{src='/foundry-nfts/17-svg-anvil/svg-anvil7.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/18-filecoin-arweave/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/18-filecoin-arweave/+page.md index bed8ddcb3..48e6ebe8d 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/18-filecoin-arweave/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/18-filecoin-arweave/+page.md @@ -26,7 +26,7 @@ Alison Haire brings us her expert take from the Filecoin Foundation, providing a ### Filecoin - +::image{src='/foundry-nfts/18-filecoin-arweave/filecoin-arweave1.png' style='width: 100%; height: auto;'} Filecoin, since its launch in 2020, has been working tirelessly towards decentralizing the data infrastructure for the internet. Their layer one solution, Filecoin Virtual Machine (FVM), has launched some impressive functionalities. @@ -38,7 +38,7 @@ And many more use cases are being developed, showcased in the [Filecoin docs](ht To get started with Filecoin, try deploying a smart contract to FVM, or use the storage helper - [**Web3 Storage**](https://web3.storage/) or [**NFT Storage**](https://nft.storage/), to engage with the technology directly. - +::image{src='/foundry-nfts/18-filecoin-arweave/filecoin-arweave2.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/19-advanced-evm/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/19-advanced-evm/+page.md index a0ab179cd..87e6921a8 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/19-advanced-evm/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/19-advanced-evm/+page.md @@ -39,7 +39,7 @@ function combineStrings() public pure returns(string memory){ Now, if we deploy this and call our combineStrings function, our output is `Hi Mom! Miss you!`. - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm1.png' style='width: 100%; height: auto;'} What our function is ultimtely doing is encoding `Hi Mom! ` and `Miss you!` into its bytes form and then casting these bytes into a string. @@ -62,13 +62,13 @@ Before we dive deeper into what's happening when we call `encodePacked`, let's f When we compile using `forge build`, a JSON file is added to our `out` directory. This file contains a lot of data, but our purposes focus primarily on the abi and the bytecode. - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm2.png' style='width: 100%; height: auto;'} Looking at this for the first time can be a little overwhelming, but don't worry we'll break things down a bit. You can actually get this data right out of Remix. With our `Encoding.sol` deployed, navigate to the `Solidity Compiler` tab and click `Compilation Details`. This will provide a readout which includes the ABI and Bytecode for this contract! - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm3.png' style='width: 100%; height: auto;'} The Bytecode object represents the binary that is actually being put on the blockchain, when we send a transaction. @@ -93,11 +93,11 @@ We can see this in Etherscan for any contract we've deployed. Here's an [**examp The above may look like random numbers and letters to us, but to the `Ethereum Virtual Machine (EVM)`, this is effectively the alphabet it uses to perform computation. Every 2 bytes in the data above actually represents an op code. The website [**evm.codes**](https://www.evm.codes/) is an amazing resource for referencing these things. - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm5.png' style='width: 100%; height: auto;'} You could almost use this reference like a dictionary. It tells us any time we see `00` in our bytecode, this represents the `STOP` operation, for example. In the bytecode example above, the first op code is `60`. This pertains to the PUSH1 operation! - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm6.png' style='width: 100%; height: auto;'} This is what is meant by being `EVM Compatible`, `Polygon`, `Avalanche`, `Arbitrum` etc all compile to the same style of binary, readable by the `Ethereum Virtual Machine`. @@ -123,7 +123,7 @@ function encodeNumber() public pure returns(bytes memory){ Go ahead and compile/deploy Encoding.sol with this new function and call it. We should have the encoded version of the number `1` output. - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm7.png' style='width: 100%; height: auto;'} This hex formmat, this encoding, is how a computer understands the number `1`. @@ -136,7 +136,7 @@ function encodeString() public pure returns(string memory){ } ``` - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm8.png' style='width: 100%; height: auto;'} Something you may notice of each of our outputs is how many bytes of the output are comprised of zeros. This padding takes up a lot of space, whether or not it is important to the value being returned. @@ -167,7 +167,7 @@ function encodeStringPacked() public pure returns(bytes memory){ } ``` - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm9.png' style='width: 100%; height: auto;'} We can clearly see how much smaller the encodePacked output is, if we were trying to by gas efficient, the advantages of one over the other are obvious. @@ -180,7 +180,7 @@ function encodeStringBytes() public pure returns(bytes memory) { } ``` - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm10.png' style='width: 100%; height: auto;'} So, it looks like abi.encodePacked and bytes casting are doing the same thing here, and for us - functionally they are - but behind the scenes things are a little more complicated. We won't go into the spefics here, but I encourage you to check out the deep dive in [**this forum post**](https://forum.openzeppelin.com/t/difference-between-abi-encodepacked-string-and-bytes-string/11837). @@ -190,7 +190,7 @@ Concatenating strings is fun and all, but in addition to _encoding_ things, we c From the docs we can see the decode function takes the encoded data and a tuple of types to decode the data into. - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm11.png' style='width: 100%; height: auto;'} ```js function decodeString() public pure returns(string memory) { @@ -201,7 +201,7 @@ function decodeString() public pure returns(string memory) { Once again, we can add this function to our Encoding.sol contract and redeploy in remix to see how it works. - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm12.png' style='width: 100%; height: auto;'} ### Muli-Encoding/MultiDecoding @@ -219,7 +219,7 @@ function multiDecode() public pure returns(string memory, string memory){ } ``` - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm13.png' style='width: 100%; height: auto;'} When we multiEncode, you can see that our output is an _even bigger_ bytes object, with tonnes of padding. What do you think we can do about it? @@ -232,7 +232,7 @@ function multiEncodePacked() public pure returns (bytes memory){ } ``` - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm14.png' style='width: 100%; height: auto;'} This is actually where our fun stops a little bit. Because we're packing the encoding of multiple strings, the decoding function is unable to properly split these up. It's not possible to multiDecode a multiEncodePacked object 😦. If you try something like: @@ -254,7 +254,7 @@ function multiStringCastPacked() public pure returns (string memory){ This one actually _will_ work. - +::image{src='/foundry-nfts/19-advanced-evm/advanced-evm15.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/20-evm-encoding/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/20-evm-encoding/+page.md index 6e69d9295..f0da8751b 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/20-evm-encoding/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/20-evm-encoding/+page.md @@ -12,11 +12,11 @@ What we've learnt so far is that any `EVM compatible` chain is looking for the ` What these two things combined mean is that we can encode our own function calls as data that we send to a contracts address. - +::image{src='/foundry-nfts/20-evm-encoding/evm-encoding1.png' style='width: 100%; height: auto;'} If we view a function call on Etherscan, we can see the input data in a human readable form as well as its original form, which is the `bytecode` representing that function (`function selector`). - +::image{src='/foundry-nfts/20-evm-encoding/evm-encoding2.png' style='width: 100%; height: auto;'} The ability to do this empowers us as developers to do a lot of cool low-level things like making arbitrary function calls. diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/21-evm-recap/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/21-evm-recap/+page.md index 8502754cc..2a4728802 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/21-evm-recap/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/21-evm-recap/+page.md @@ -25,7 +25,7 @@ string memory someString = string(abi.encodePacked("Hi Mom! ", "Miss you!")) We learnt that when a contract is compiled, it's actually compiled into an ABI (application binary interface) and a binary or bytecode format. - +::image{src='/foundry-nfts/21-evm-recap/evm-recap1.png' style='width: 100%; height: auto;'} Any transaction we send to the blockchain is ultimately compiled down to this bytecode. For contract creation transactions, the data field of the transaction _is_ this bytecode. @@ -35,11 +35,11 @@ Any system capable of reading the operations contained within this bytecode is s We also learnt that we can use the encoding functionality of the EVM to encode basically anything. Basic encoding is accomplished with `abi.encode`, but we've a few options available to us. - +::image{src='/foundry-nfts/21-evm-recap/evm-recap2.png' style='width: 100%; height: auto;'} `abi.encode` will result in a padded return value, however the EVM offers a way to save space/gas by packing our encodings through `abi.encodePacked`. - +::image{src='/foundry-nfts/21-evm-recap/evm-recap3.png' style='width: 100%; height: auto;'} The EVM also affords us the ability to decode and multi-encode, really giving us flexibility to work with our data. diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/22-evm-signatures-selectors/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/22-evm-signatures-selectors/+page.md index e0df54d0a..780dd47bd 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/22-evm-signatures-selectors/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/22-evm-signatures-selectors/+page.md @@ -64,13 +64,13 @@ function getSelectorOne() public pure returns(bytes4 selector){ Adding this to our Remix contract, we can compile and deploy. Calling this function results in... - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors1.png' style='width: 100%; height: auto;'} This is exactly what we'd expect it to be! Great! Now what else do we need? The parameters we're passing our function call are going to need to be encoded with this signature. Much like abi.encode and abi.encodePacked, the EVM offers us a way to encode our parameters with a given selector through `abi.encodeWithSelector` - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors2.png' style='width: 100%; height: auto;'} We can write another function to compile this data for our function call for us. @@ -122,17 +122,17 @@ Let's run this function in Remix to see it in action. Compile and redeploy `Call As expected, after deployment our storage variables initialize as `0` - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors3.png' style='width: 100%; height: auto;'} Now, if we pass the contract address and 50 as an amount to our `callTransferWithBinary` function, Remix's terminal should provide us an output on what happened. - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors4.png' style='width: 100%; height: auto;'} Here we can see that our transaction was successful, represented by the bool `true`. The bytes4 value of our returnData is empty, because our transfer function doesn't actually return anything! With this transaction complete, we should be able to repoll the storage variables in our contract. We would expect them to be updated with the values we passed `callTransferWithBinary`... - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors5.png' style='width: 100%; height: auto;'} ...and they are! Amazing! Another option Solidity affords us is the ability to encode with a signature. This effectively saves us a step since we don't have to determine the function selector first. @@ -295,15 +295,15 @@ contract CallFunctionWithoutContract { By passing this contract the address of our `CallAnything.sol` deployment. We're able to use the functions it possesses to interact with `CallAnything.sol` - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors6.png' style='width: 100%; height: auto;'} Before we interact with anything, recall what the values of our storage variables on `CallAnything.sol` are currently. - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors7.png' style='width: 100%; height: auto;'} Now we can call `callTransferFunctionDirectlyThree` on our `CallFunctionWithoutContract.sol` by passing a new address and amount. This should result in an updating of the storage variables on CallAnything.sol via this low-level call. - +::image{src='/foundry-nfts/22-evm-signatures-selectors/evm-signatures-selectors8.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/23-verifying-metamask/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/23-verifying-metamask/+page.md index 4a044348e..762324aef 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/23-verifying-metamask/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/23-verifying-metamask/+page.md @@ -12,7 +12,7 @@ Possessing this better understanding of encoding empowers us to do something ver If we write to a contract on Etherscan, a transaction will pop up in our Metamask wallet, by navigating to the HEX tab, we can see the data being sent in this transaction. - +::image{src='/foundry-nfts/23-verifying-metamask/verifying-metamask1.png' style='width: 100%; height: auto;'} We should recognize this calldata as similar to the data we sent in our previous lessons. @@ -42,7 +42,7 @@ We can see that this matches the first 4 bytes of the calldata in our Metamask t cast --calldata-decode "mintNFT(string)" 0xfb37e883000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000076578616d706c6500000000000000000000000000000000000000000000000000 ``` - +::image{src='/foundry-nfts/23-verifying-metamask/verifying-metamask2.png' style='width: 100%; height: auto;'} Worked like a charm! @@ -54,11 +54,11 @@ To see this yourselves, navigate to [**openchain.xyz/signatures**](https://openc In the search field, enter `0x23b872dd`. You'll see that this function signature is attributed to multiple, completely different functions! - +::image{src='/foundry-nfts/23-verifying-metamask/verifying-metamask3.png' style='width: 100%; height: auto;'} Importantly, the Solidity compiler **will not** allow a contract to contain two or more functions which share a selector. You'll receive a compiler error: - +::image{src='/foundry-nfts/23-verifying-metamask/verifying-metamask4.png' style='width: 100%; height: auto;'} I encourage you to try this out yourself in Remix! See if you can find any other conflicting function selectors! This is why it may be important to verify through the contract's code directly, which function is actually being called. diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/24-recap/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/24-recap/+page.md index c8a7162c4..0dfb5b3eb 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/24-recap/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/24-recap/+page.md @@ -12,7 +12,7 @@ Guess what. You just learnt an insane amount in this section. Let's run down the We started off by learning what a `Non-fungible token (NFT)` is. And we explored this technology by creating our very own `BasicNFT`. - +::image{src='/foundry-nfts/24-recap/recap1.png' style='width: 100%; height: auto;'} While going through this process, we learnt all about options for decentralized data storage including services like @@ -23,7 +23,7 @@ While going through this process, we learnt all about options for decentralized We also learnt that we could store data and image directly on the blockchain by creating a dynamic, on-chain `SVG NFT` that we can use to reflect our mood! - +::image{src='/foundry-nfts/24-recap/recap2.png' style='width: 100%; height: auto;'} While we're able to store this data on chain, it's important to note that it may become prohibitively expensive from the perspective of gas and computation. We proposed protocols such as [**Filecoin**](https://filecoin.io/) and [**Arweave**](https://arweave.org/) which serve to mitigate these concerns in decentralized data storage. @@ -37,7 +37,7 @@ In addition to this we learnt how to encode our transaction data into this bytec As a result of our greater understanding of how encoded data is used in EVM transactions, we also gained the ability to verify any transaction sent to our wallet and to keep ourselves safe from malicious ones. - +::image{src='/foundry-nfts/24-recap/recap3.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/3-foundry-setup/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/3-foundry-setup/+page.md index 79afa220b..30bdd4c9b 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/3-foundry-setup/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/3-foundry-setup/+page.md @@ -147,15 +147,15 @@ By navigating to any NFT on OpenSea, you can find a link to the collection's con Entering any valid tokenId should return the TokenURI of that NFT! - +::image{src='/foundry-nfts/3-foundry-setup/foundry-setup1.png' style='width: 100%; height: auto;'} By opening this URI in your browser, the details of that token's metadata should be made available: - +::image{src='/foundry-nfts/3-foundry-setup/foundry-setup2.png' style='width: 100%; height: auto;'} Note the imageURI property. This is what defines what the NFT actually looks like, you can copy this into your browser as well to view the NFT's image. - +::image{src='/foundry-nfts/3-foundry-setup/foundry-setup3.png' style='width: 100%; height: auto;'} Both the tokenUri and imageUri for this example are hosted on IPFS (Inter-planetary file system), a service offering decentralized storage that we'll go into in greater detail, in the next lesson. @@ -168,7 +168,7 @@ function tokenURI(uint256 tokenId) public view override returns (string memory) Now, I've prepared some images you can choose from to use in your project, but feel free to use your own. Making these projects _yours_ goes a long way towards committing these things to memory. You can find the images I've provided in the [**GitHub Repo**](https://github.com/Cyfrin/foundry-nft-f23/tree/main/images/dogNft) for this section. - +::image{src='/foundry-nfts/3-foundry-setup/foundry-setup4.png' style='width: 100%; height: auto;'} Create a new folder in your workspace names `img` (image) and add the image of your choice to this directory. diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/4-ipfs/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/4-ipfs/+page.md index 55ff694b3..8a7059a5d 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/4-ipfs/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/4-ipfs/+page.md @@ -21,7 +21,7 @@ Each IPFS Node is once part of a much larger network and each of them constantly What we would do then is upload our data to IPFS and then pin it in our node, assuring that the IPFS Hash of the data is available to anyone calling the network. - +::image{src='/foundry-nfts/4-ipfs/IPFS1.png' style='width: 100%; height: auto;'} Importantly, unlike a blockchain, where every node has a copy of the entire register, IPFS nodes can choose what they want to pin. @@ -31,18 +31,18 @@ There are a few ways to actually use IPFS including a CLI installation, a browse Let's go ahead and [**install the IPFS Desktop application**](https://docs.ipfs.tech/install/ipfs-desktop/). Once installed you should be able to open the application and navigate to a files section that looks like this: - +::image{src='/foundry-nfts/4-ipfs/IPFS2.png' style='width: 100%; height: auto;'} Pay no mind to all my pictures of cats. If you have no data to view, navigate to import in the top right and select any small file you don't mind being public. > ❗ **IMPORTANT** > Any data uploaded to this service will be **_public_** by nature. - +::image{src='/foundry-nfts/4-ipfs/IPFS3.png' style='width: 100%; height: auto;'} Once a file is uploaded, you can click on that file and view that data. - +::image{src='/foundry-nfts/4-ipfs/IPFS4.png' style='width: 100%; height: auto;'} What makes this _really_ cool, is we can then copy the data's CID (content ID), as seen above and view our data within our browser by entering it into our address bar. @@ -59,7 +59,7 @@ Alternatively, if you're having trouble viewing your data directly from the IPFS https://ipfs.io/ipfs/ ``` - +::image{src='/foundry-nfts/4-ipfs/IPFS5.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/6-deploy-script/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/6-deploy-script/+page.md index 174422982..b5eaed7af 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/6-deploy-script/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/6-deploy-script/+page.md @@ -30,6 +30,6 @@ contract DeployBasicNft is Script { That's literally all there is to it. Run a quick `forge compile` as a sanity check to assure things build. - +::image{src='/foundry-nfts/6-deploy-script/deploy-script1.png' style='width: 100%; height: auto;'} Great work! diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/7-basic-nft-tests/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/7-basic-nft-tests/+page.md index 42fc49d0f..985d978fe 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/7-basic-nft-tests/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/7-basic-nft-tests/+page.md @@ -46,7 +46,7 @@ Now, you may believe that we can simply do something like the above. We know the We actually run into an issue here. - +::image{src='/foundry-nfts/7-basic-nft-tests/basic-nft-tests1.png' style='width: 100%; height: auto;'} ### Comparing Strings @@ -71,11 +71,11 @@ string memory dog = "dog"; Now if you type `cat`, you should get a kinda crazy output that's representing the hex of that string. - +::image{src='/foundry-nfts/7-basic-nft-tests/basic-nft-tests2.png' style='width: 100%; height: auto;'} We'll leverage abi.encodePacked to convert this to bytes, then finally we can use keccak256 to hash the value into bytes32, which we can can use in our value comparison. - +::image{src='/foundry-nfts/7-basic-nft-tests/basic-nft-tests3.png' style='width: 100%; height: auto;'} > ❗ **NOTE** > I know we haven't covered encoding or abi.encodePacked in great detail yet, but don't worry - we will. @@ -93,7 +93,7 @@ function testNameisCorrect() public view { In the above, we're encoding and hashing both of our strings before comparing them in our assertion. Now, if we run our test with `forge test --mt testNameIsCorrect`... - +::image{src='/foundry-nfts/7-basic-nft-tests/basic-nft-tests4.png' style='width: 100%; height: auto;'} Great work! Let's write a couple more tests together. @@ -120,7 +120,7 @@ contract BasicNftTest is Test { With this, we again should just be able to run `forge test` and see how things resolve. - +::image{src='/foundry-nfts/7-basic-nft-tests/basic-nft-tests5.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/9-testnet-demo/+page.md b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/9-testnet-demo/+page.md index 1eda3af50..e55d0badd 100644 --- a/courses/advanced-foundry/2-how-to-create-an-NFT-collection/9-testnet-demo/+page.md +++ b/courses/advanced-foundry/2-how-to-create-an-NFT-collection/9-testnet-demo/+page.md @@ -92,7 +92,7 @@ make deploy ARGS="--netwok sepolia" After a brief wait... - +::image{src='/foundry-nfts/9-testnet-demo/testnet-demo1.png' style='width: 100%; height: auto;'} All deployed! @@ -107,11 +107,11 @@ make mint ARGS="--network sepolia" While this is minting, we can navigate to our Metamask wallet and import our NFT Token. Grab the address of the contract we deployed from Etherscan (or `broadcast/DeployBasicNft.s.sol/11155111/run-latest.json`). - +::image{src='/foundry-nfts/9-testnet-demo/testnet-demo2.png' style='width: 100%; height: auto;'} Enter the contract address and a tokenId of `0` when prompted. Then, after a brief wait... - +::image{src='/foundry-nfts/9-testnet-demo/testnet-demo3.png' style='width: 100%; height: auto;'} We can see our NFT in our wallet!!! @@ -123,7 +123,7 @@ We've learnt so much already and you should be very proud, but it's not time to Let's gooo! - +::image{src='/foundry-nfts/9-testnet-demo/testnet-demo4.png' style='width: 100%; height: auto;'} While testing is a vital part of NFT creation, deploying it in a real use case can bring more clarity to your understanding. Luckily, there are several ways to deploy your NFT. You could consider using Anvil, your own Anvil server, or a testnet. If you're not keen on waiting for the testnet or spending the gas, I'd recommend deploying it to Anvil. @@ -135,7 +135,7 @@ Rather than typing out long scripts, we'll use a makefile here. The associated G In the makefile, we've captured most of the topics we've discussed so far, including our deploy script, which we'll use to deploy our basic NFT. - +::image{src='/foundry-nfts/9-testnet/testnet1.png' style='width: 100%; height: auto;'} Here is what the deploy script looks like: diff --git a/courses/advanced-foundry/3-develop-defi-protocol/1-defi-introduction/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/1-defi-introduction/+page.md index 21497c593..3b5ba4fca 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/1-defi-introduction/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/1-defi-introduction/+page.md @@ -17,7 +17,7 @@ Decentralized Finance (DeFi) is an enormous ecosystem, we couldn't hope to offer A good starting place is [**DeFi Llama**](https://defillama.com/). This website aggregates data from major DeFi protocols and provides a snapshot of what's happening in the space. - +::image{src='/foundry-defi/1-defi-introduction/defi-introduction1.PNG' style='width: 100%; height: auto;'} DeFi Llama demonstrates the size of various DeFi protocols by ranking them by Total Value Locked (TVL). Some of the biggest include: @@ -44,7 +44,7 @@ I often encourage people to go checkout AAVE and Uniswap to get a feel for what [**AAVE**](https://aave.com/), as mentioned, is a decentralized borrowing and lending platform. By logging into the dApp by connecting a wallet, users can see assets they hold which can be offered as collateral and assets available to borrow. - +::image{src='/foundry-defi/1-defi-introduction/defi-introduction2.png' style='width: 100%; height: auto;'} Each asset has it's own calculated Annual Percentage Yield (APY) which effectively represents the interest a lender to the protocol would make on deposits of a given asset. @@ -52,7 +52,7 @@ Each asset has it's own calculated Annual Percentage Yield (APY) which effective Another massively popular protocol that I like to bring to people's attention is [**Uniswap**](https://uniswap.org/). - +::image{src='/foundry-defi/1-defi-introduction/defi-introduction3.png' style='width: 100%; height: auto;'} Uniswap is a decentralized exchange which allows users to swap tokens for any other token. @@ -95,11 +95,11 @@ If you are new to DeFi, a great starting point is [DeFi Llama](https://defillama ### The Beauty of DeFi - +::image{src='/foundry-defi/1-defi-introduction/defi-introduction1.PNG' style='width: 100%; height: auto;'} The beauty of DeFi and the reason for its growing popularity is the access it provides to sophisticated financial products and instruments in a decentralized context. - +::image{src='/foundry-defi/1-defi-introduction/defi-introduction2.PNG' style='width: 100%; height: auto;'} In my opinion, DeFi is possibly the most exciting and important application of smart contracts. I highly recommend spending some time to become conversant with the basics of DeFi, if not becoming fully fluent. Start with useful resources such as the [Bankless](https://www.bankless.com/) podcast and [MetaMask Learn](https://learn.metamask.io/). @@ -119,7 +119,7 @@ In this course, we will be building our version of a stablecoin. The concept of ## IV. Foundry Stablecoin Project is the Most Advanced. - +::image{src='/foundry-defi/1-defi-introduction/defi-introduction3.PNG' style='width: 100%; height: auto;'} Even though we have following lessons on upgrades, governance, introduction to security, this Foundry Stablecoin project is the most advanced one we're working with, hands down. @@ -129,6 +129,6 @@ You can even check out Coinbase's educational content to get a headstart on DeFi And remember, - +::image{src='/foundry-defi/1-defi-introduction/defi-introduction4.PNG' style='width: 100%; height: auto;'} In the following section, we will be walking you through the code. Happy learning! diff --git a/courses/advanced-foundry/3-develop-defi-protocol/11-defi-tests/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/11-defi-tests/+page.md index eca61098e..a5822f125 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/11-defi-tests/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/11-defi-tests/+page.md @@ -134,7 +134,7 @@ When you're ready, let see how we've done! forge test --mt testGetUsdValue ``` - +::image{src='/foundry-defi/11-defi-tests/defi-tests1.png' style='width: 100%; height: auto;'} It works! We're clearly still on track. This is great. It's good practice to test things as you go to avoid getting too far down the rabbit-hole of compounding errors. Sanity checks along the way like this can save you time in having to refactor and change a bunch of code later. @@ -201,7 +201,7 @@ Let's run it! forge test --mt testRevertsIfCollateralZero ``` - +::image{src='/foundry-defi/11-defi-tests/defi-tests2.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/3-develop-defi-protocol/16-defi-leveling-up-testing/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/16-defi-leveling-up-testing/+page.md index 147de671c..aa227ac1d 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/16-defi-leveling-up-testing/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/16-defi-leveling-up-testing/+page.md @@ -46,7 +46,7 @@ function testSetNumber() public { Running this test, we'd see: - +::image{src='/foundry-defi/16-defi-leveling-up-testing/defi-leveling-up-testing1.png' style='width: 100%; height: auto;'} The unit test catches this right away. All of the most popular frameworks have unit tests built in! @@ -88,7 +88,7 @@ Here's an example of a fuzz test we could perform: You can see, we don't explicitly declare the value for `data` in our test, and instead pass it as an argument to the test function. The Foundry framework will satisfy this argument with random data until it breaks our invariant (or stops based on configurations set). When run, we can see the framework identifies the edge case which breaks our asserted property. - +::image{src='/foundry-defi/16-defi-leveling-up-testing/defi-leveling-up-testing2.png' style='width: 100%; height: auto;'} ### Layer 3 Static Analysis @@ -108,7 +108,7 @@ function withdraw() external { The above withdraw function has a classic reentrancy attack. We know an issue like this arrises from not following the CEI pattern! A static analysis tool like Slither will be able to pick up on this quite easily. - +::image{src='/foundry-defi/16-defi-leveling-up-testing/defi-leveling-up-testing3.png' style='width: 100%; height: auto;'} ### Layer 4 Formal Verification diff --git a/courses/advanced-foundry/3-develop-defi-protocol/17-defi-handler-fuzz-tests/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/17-defi-handler-fuzz-tests/+page.md index 7ce0022b4..2f754eda7 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/17-defi-handler-fuzz-tests/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/17-defi-handler-fuzz-tests/+page.md @@ -10,7 +10,7 @@ _Follow along the course with this video._ Ok, welcome back! I hope you had a chance to take a break, and I _also_ hope you took the time to try to write your own tests. Hopefully your `forge coverage` is outputting something closer to this: - +::image{src='/foundry-defi/18-defi-handler-fuzz-tests/defi-handler-fuzz-tests1.png' style='width: 100%; height: auto;'} If not...I **_strongly_** encourage you to pause the video and practice writing some tests. @@ -168,7 +168,7 @@ function testIAlwaysGetZero(uint256 data) public { That's it. Now, if we run this test with Foundry, it'll throw random data at our function as many times as we tell it to (we'll discuss runs soon), until it breaks our assertion. - +::image{src='/foundry-defi/18-defi-handler-fuzz-tests/defi-handler-fuzz-tests2.png' style='width: 100%; height: auto;'} I'll mention now that the fuzzer isn't using _truly_ random data, it's pseudo-random, and how your fuzzing tool chooses its data matters! Echidna and Foundry are both solid choices in this regard, but I encourage you to research the differences on your own. @@ -178,7 +178,7 @@ Important properties of the fuzz tests we configure are its `runs` and `depth`. In our example, the fuzz tester took 18 random inputs to find our edge case. - +::image{src='/foundry-defi/18-defi-handler-fuzz-tests/defi-handler-fuzz-tests3.png' style='width: 100%; height: auto;'} However, we can customize how many attempts the fuzzer makes within our foundry.toml by adding a section like: @@ -199,7 +199,7 @@ function doStuff(uint256 data) public { ... and run the fuzzer again... - +::image{src='/foundry-defi/18-defi-handler-fuzz-tests/defi-handler-fuzz-tests4.png' style='width: 100%; height: auto;'} We can see it will run all .. 1001 runs (I guess zero counts 😅). @@ -279,7 +279,7 @@ function invariant_testAlwaysReturnsZero() public view { Now, if our fuzzer ever calls our doStuff function with a value of 7, hiddenValue will be assigned 7 and the next time doStuff is called, our invariant should break. Let's run it. - +::image{src='/foundry-defi/18-defi-handler-fuzz-tests/defi-handler-fuzz-tests5.png' style='width: 100%; height: auto;'} We can see in the output the two subsequent function calls that lead to our invariant breaking. First doStuff was called with the argument of `7`, then it was called with `429288169336124586202452331323751966569421912`, but it doesn't matter what it was called with next, we knew our invariant was going to break. diff --git a/courses/advanced-foundry/3-develop-defi-protocol/18-defi-handler-stateful-fuzz-tests/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/18-defi-handler-stateful-fuzz-tests/+page.md index b89dd76ea..8ad14eaf3 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/18-defi-handler-stateful-fuzz-tests/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/18-defi-handler-stateful-fuzz-tests/+page.md @@ -33,11 +33,11 @@ function deposit(uint256 assets) public virtual { To illustrate, as show in the Foundry Docs as well, open testing has our framework calling functions directly as defined in the contracts within scope. - +::image{src='/foundry-defi/19-defi-handler-stateful-fuzz-tests/defi-handler-stateful-fuzz-tests1.png' style='width: 100%; height: auto;'} Conversely, handler based tests route our frameworks function calls through our handler, allowing us to configure only the functions/behaviour we want it to perform, filtering out bad runs from our tests. - +::image{src='/foundry-defi/19-defi-handler-stateful-fuzz-tests/defi-handler-stateful-fuzz-tests2.png' style='width: 100%; height: auto;'} Let's finally start applying this methodology to our code base. @@ -172,7 +172,7 @@ With this in place our open invariant test is ready! Try to run it. forge test --mt invariant_protocolMustHaveMoreValueThanTotalSupply -vvvv ``` - +::image{src='/foundry-defi/19-defi-handler-stateful-fuzz-tests/defi-handler-stateful-fuzz-tests3.png' style='width: 100%; height: auto;'} Our test identified a break in our assertion immediately.. but it's because we have no tokens or collateral. We can adjust our assertion to be `>=`, but it's a little bit cheaty. @@ -180,11 +180,11 @@ Our test identified a break in our assertion immediately.. but it's because we h assert(wethValue + wbtcValue >= totalSupply); ``` - +::image{src='/foundry-defi/19-defi-handler-stateful-fuzz-tests/defi-handler-stateful-fuzz-tests4.png' style='width: 100%; height: auto;'} Things pass! We didn't find any issues. This is where we may want to bump up the number of runs we're performing, you can see in the image above our fuzzer executed `128 runs` and `16,384 function calls`. If we bump this up to `1000 runs`, our fuzz test will be more thorough, but will take much longer to run. Try it out! - +::image{src='/foundry-defi/19-defi-handler-stateful-fuzz-tests/defi-handler-stateful-fuzz-tests5.png' style='width: 100%; height: auto;'} Things pass again, but you can see how much more intense the test process was. There's a catch, however. In the image above, notice how many calls were made vs how many times a function call reverted. Every single call is reverting! This in essence means that our test wasn't able to _do_ anything. This is not a very reassuring test. @@ -198,11 +198,11 @@ fail_on_revert can be great for quick testing and keeping things simple, but it Let's set this option to `true` and run our test once more. - +::image{src='/foundry-defi/19-defi-handler-stateful-fuzz-tests/defi-handler-stateful-fuzz-tests6.png' style='width: 100%; height: auto;'} We can see the first function being called by the fuzzer is `depositCollateral` and its passing a random `tokenAddress` argument causing our revert immediately. - +::image{src='/foundry-defi/19-defi-handler-stateful-fuzz-tests/defi-handler-stateful-fuzz-tests7.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/3-develop-defi-protocol/19-defi-handler-deposit-collateral/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/19-defi-handler-deposit-collateral/+page.md index 6e2c6aaa0..38ad36422 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/19-defi-handler-deposit-collateral/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/19-defi-handler-deposit-collateral/+page.md @@ -114,7 +114,7 @@ contract InvariantsTest is StdInvariant Test { We can see this fails for the expected reasons below. - +::image{src='/foundry-defi/20-defi-handler-deposit-collateral/defi-handler-redeem-collateral1.png' style='width: 100%; height: auto;'} Let's use our Handler to ensure that only _valid_ collateral is deposited. Begin by importing ERC20Mock as we'll need this for our collateral types. In our constructor, we can leverage the getCollateralTokens function added to DSCEngine.sol. @@ -166,11 +166,11 @@ Now our test should only call this Handler function with valid collateral addres forge test --mt invariant_ProtocolTotalSupplyLessThanCollateralValue -vvvv ``` - +::image{src='/foundry-defi/20-defi-handler-deposit-collateral/defi-handler-redeem-collateral2.png' style='width: 100%; height: auto;'} Look! Our address passed is valid, but we're getting a different error `DSCEngine__NeedsMoreThanZero()`. This is actually great progress and shows we've accounted for at least some of the causes of our reverts. - +::image{src='/foundry-defi/20-defi-handler-deposit-collateral/defi-handler-redeem-collateral3.png' style='width: 100%; height: auto;'} Let's keep narrowing the focus of our tests and the validity of our data. @@ -193,11 +193,11 @@ We can declare a MAX_DEPOSIT_SIZE constant at the top of our contract. I like to uint256 MAX_DEPOSIT_SIZE = type(uint96).max; ``` - +::image{src='/foundry-defi/20-defi-handler-deposit-collateral/defi-handler-redeem-collateral4.png' style='width: 100%; height: auto;'} Not a massive change, but we _have_ made progress on the number of reverts our function it hitting. Running the test again with `fail_on_revert` set to true should reveal what's causing our reverts now. - +::image{src='/foundry-defi/20-defi-handler-deposit-collateral/defi-handler-redeem-collateral5.png' style='width: 100%; height: auto;'} Well, of course this is going to revert! We haven't set an allowance on our tokens! Let's remedy this by leveraging vm.prank in our Handler to ensure appropriate addresses are approved for our deposit function. @@ -220,7 +220,7 @@ function depositCollateral(uint256 collateralSeed, uint256 amountCollateral) pub If we run our test now... - +::image{src='/foundry-defi/20-defi-handler-deposit-collateral/defi-handler-redeem-collateral6.png' style='width: 100%; height: auto;'} Woah! We eliminated **_all_** of the situations that were causing our test to revert! This means we're using our fuzz runs much more efficiently, and no matter how often depositCollateral is called, our totalCollateral will never be less than our totalSupply. diff --git a/courses/advanced-foundry/3-develop-defi-protocol/20-defi-handler-redeem-collateral/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/20-defi-handler-redeem-collateral/+page.md index 2b166a43c..a763a320f 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/20-defi-handler-redeem-collateral/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/20-defi-handler-redeem-collateral/+page.md @@ -35,7 +35,7 @@ Let's run it! forge test --mt invariant_ProtocolTotalSupplyLessThanCollateralValue -vvvv ``` - +::image{src='/foundry-defi/21-defi-handler-redeem-collateral/defi-handler-redeem-collateral1.png' style='width: 100%; height: auto;'} Uh oh, it looks like we're running into an issue when the maxCollateralToRedeem is 0. We can fix this with a small adjustment to our function. @@ -55,7 +55,7 @@ function redeemCollateral(uint256 collateralSeed, uint256 amountCollateral) publ } ``` - +::image{src='/foundry-defi/21-defi-handler-redeem-collateral/defi-handler-redeem-collateral2.png' style='width: 100%; height: auto;'} Woo, nailed it again! Our handler now allows us to test both the depositCollateral and redeemCollateral functionality of our protocol. Through the use of our handler, we've ensured that all the calls to deposit and redeem are going to be valid as well, avoiding reverts and wasted fuzz runs. @@ -121,10 +121,10 @@ In the above function, we are constraining the amount minted to be greater than Lo and behold, let's run the functional mint DSC and observe the result. - +::image{src='/foundry-defi/21-defi-handler-minting-dsc/defi-handler-minting-dsc1.PNG' style='width: 100%; height: auto;'} You should notice that we've performed multiple calls without any reverts, and that's exactly what success looks like! Your mint function is now up and running and ready to increase the supply of DSC. Stay tuned for our next adventure! We hope you are now more comfortable with testing the mechanism used for injecting tokens into the DSC ecosystem. - +::image{src='/foundry-defi/21-defi-handler-minting-dsc/defi-handler-minting-dsc2.PNG' style='width: 100%; height: auto;'} diff --git a/courses/advanced-foundry/3-develop-defi-protocol/21-defi-handler-mint-dsc/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/21-defi-handler-mint-dsc/+page.md index 04c38bf2f..f0c7ce9f2 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/21-defi-handler-mint-dsc/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/21-defi-handler-mint-dsc/+page.md @@ -37,14 +37,14 @@ Let's run our function and see how things look. forge test --mt invariant_ProtocolTotalSupplyLessThanCollateralValue ``` - +::image{src='/foundry-defi/22-defi-handler-mint-dsc/defi-handler-mint-dsc1.png' style='width: 100%; height: auto;'} > ❗ **NOTE** > The `totalSupply = 0` here because of a mistake we made, we'll fix it soon! Ok, so things work when we have `fail_on_revert` set to `false`. We want our tests to be quite focused, so moving forward we'll leave `fail_on_revert` to `true`. What happens when we run it now? - +::image{src='/foundry-defi/22-defi-handler-mint-dsc/defi-handler-mint-dsc2.png' style='width: 100%; height: auto;'} As expected, our user's Health Factor is breaking. This is because we haven't considered _who_ is minting our DSC with respect to who has deposited collateral. We can account for this in our test by ensuring that the user doesn't attempt to mint more than the collateral they have deposited, otherwise we'll return out of the function. We'll determine the user's amount to mint by calling our `getAccountInformation` function. @@ -70,7 +70,7 @@ function mintDsc(uint256 amount) public { Let's try it! - +::image{src='/foundry-defi/22-defi-handler-mint-dsc/defi-handler-mint-dsc3.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/3-develop-defi-protocol/22-defi-fuzz-debugging/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/22-defi-fuzz-debugging/+page.md index 9746015fa..e62a884be 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/22-defi-fuzz-debugging/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/22-defi-fuzz-debugging/+page.md @@ -63,7 +63,7 @@ function invariant_ProtocolTotalSupplyLessThanCollateralValue() external view re Run it! - +::image{src='/foundry-defi/23-defi-fuzz-debugging/defi-fuzz-debugging1.png' style='width: 100%; height: auto;'} Well, at least we've confirmed that mintDsc isn't being called. It's _likely_ because one of our conditionals in our function are catching. What I would suggest is moving our Ghost Variable up this function to determine why things revert. @@ -137,7 +137,7 @@ Let's give our tests a shot now. forge test --mt invariant_ProtocolTotalSupplyLessThanCollateralValue -vvvv ``` - +::image{src='/foundry-defi/23-defi-fuzz-debugging/defi-fuzz-debugging2.png' style='width: 100%; height: auto;'} A new error! New errors mean progress. It seems as though our mintDsc function is causing a `division or modulo by 0`. Ah, this is because our new array of usersWithCollateralDeposited may be empty. Let's account for this with a conditional. @@ -171,7 +171,7 @@ function mintDsc(uint256 amount, uint256 addressSeed) public { Once more with feeling. - +::image{src='/foundry-defi/23-defi-fuzz-debugging/defi-fuzz-debugging3.png' style='width: 100%; height: auto;'} diff --git a/courses/advanced-foundry/3-develop-defi-protocol/23-defi-handler-price-feed/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/23-defi-handler-price-feed/+page.md index ed766675f..e87c3ed90 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/23-defi-handler-price-feed/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/23-defi-handler-price-feed/+page.md @@ -54,11 +54,11 @@ With this new function, our test runs will intermittently change the price of ou forge test --mt invariant_ProtocolTotalSupplyLessThanCollateralValue -vvvv ``` - +::image{src='/foundry-defi/24-defi-handler-price-feed/defi-handler-price-feed1.png' style='width: 100%; height: auto;'} Our assertion is breaking! If we look more closely at the trace of executions we can obtain a clearer understanding of what actually happened: - +::image{src='/foundry-defi/24-defi-handler-price-feed/defi-handler-price-feed2.png' style='width: 100%; height: auto;'} When updateCollateralPrice was called, the price was updated to a number so low as to break our invariant! The minted DSC was not longer collateralized by the weth which had been deposited. diff --git a/courses/advanced-foundry/3-develop-defi-protocol/24-defi-oracle-lib/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/24-defi-oracle-lib/+page.md index 93dc17826..f62dee939 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/24-defi-oracle-lib/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/24-defi-oracle-lib/+page.md @@ -118,7 +118,7 @@ contract DSCEngine is Reentrancy Guard { We've done a tonne of refactoring, and we're very nearly done. We should run `forge test` just to ensure everything is working as expected! - +::image{src='/foundry-defi/25-defi-oracle-lib/defi-oracle-lib1.png' style='width: 100%; height: auto;'} Beautiful. diff --git a/courses/advanced-foundry/3-develop-defi-protocol/3-defi-stablecoins-but-actually/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/3-defi-stablecoins-but-actually/+page.md index ecb7e770f..238b04d21 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/3-defi-stablecoins-but-actually/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/3-defi-stablecoins-but-actually/+page.md @@ -26,7 +26,7 @@ Let's get started. That's really it, at the end of the day. More accurately put: - +::image{src='/foundry-defi/3-defi-stablecoins-but-actually/defi-stablecoins-but-actually1.png' style='width: 100%; height: auto;'} [**Investopedia**](https://www.investopedia.com/terms/s/stablecoin.asp) describes `stablecoins` as - Cryptocurriencies the value of which is pegged, or tied, to that of another currency, commondity or financial instrument. @@ -42,7 +42,7 @@ A simple example of unstable buying power is `Bitcoin (BTC)`. The number of appl **_Money is important._** - +::image{src='/foundry-defi/3-defi-stablecoins-but-actually/defi-stablecoins-but-actually2.png' style='width: 100%; height: auto;'} Ok, not Scrooge McDuck important. @@ -105,7 +105,7 @@ You can see what I mean by spectrum by comparing how some of these tokens functi [**The Dirt Roads blog**](https://dirtroads.substack.com/p/-40-pruning-memes-algo-stables-are) has a great article and visualizations outlining these differences in detail and where popular assets fall on this spectrum. - +::image{src='/foundry-defi/3-defi-stablecoins-but-actually/defi-stablecoins-but-actually3.png' style='width: 100%; height: auto;'} > ❗ **NOTE** > Dirt Roads uses `Dumb` as the opposite of algorithmic, instead of governed. @@ -263,7 +263,7 @@ Now, here's something that may give you whiplash: **_The stablecoin preferred by the average user, is likely much less important than those preferred by the 'rich whales' in the space_** - +::image{src='/foundry-defi/3-defi-stablecoins-but-actually/defi-stablecoins-but-actually4.png' style='width: 100%; height: auto;'} Let me explain with another thought experiment. diff --git a/courses/advanced-foundry/3-develop-defi-protocol/7-defi-mint-dsc/+page.md b/courses/advanced-foundry/3-develop-defi-protocol/7-defi-mint-dsc/+page.md index ab05666b0..7818d0087 100644 --- a/courses/advanced-foundry/3-develop-defi-protocol/7-defi-mint-dsc/+page.md +++ b/courses/advanced-foundry/3-develop-defi-protocol/7-defi-mint-dsc/+page.md @@ -65,7 +65,7 @@ function _revertIfHealthFactorIsBroken(address user){} `Health Factor` is a concept borrowed from Aave. - +::image{src='/foundry-defi/7-defi-mint-dsc/defi-mint-dsc1.png' style='width: 100%; height: auto;'} In addition to the above, we'll need a function which checks an account's `Health Factor`. Let's write that now. diff --git a/courses/advanced-foundry/4-merkle-airdrop/1-introduction/+page.md b/courses/advanced-foundry/4-merkle-airdrop/1-introduction/+page.md index e3e7c1b02..494242684 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/1-introduction/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/1-introduction/+page.md @@ -12,7 +12,7 @@ _Follow along with the video_ An airdrop occurs when a token development team distributes tokens or allows people to claim them. These tokens can be of various types, including ERC-20, ERC-1155, or ERC-721. - +::image{src='/foundry-merkle-airdrop/01-introduction/airdrop.png' style='width: 75%; height: auto;'} Tokens are tipically given for free, with eligibility criteria such as contributing to the project's GitHub repository or participating in the community. This process helps to _bootstrap the project_ by distributing tokens to a **list of eligible addresses**. diff --git a/courses/advanced-foundry/4-merkle-airdrop/10-signature-standards/+page.md b/courses/advanced-foundry/4-merkle-airdrop/10-signature-standards/+page.md index 14aa6b394..426fadadd 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/10-signature-standards/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/10-signature-standards/+page.md @@ -22,7 +22,7 @@ function getSignerSimple(uint256 message, uint8 _v, bytes32 _r, bytes32 _s) publ } ``` -> 🗒️ **NOTE**
> `ecrecover` is a function built into the Ethereum protocol. +> 🗒️ **NOTE**:br > `ecrecover` is a function built into the Ethereum protocol. ```js function verifySignerSimple( @@ -42,7 +42,7 @@ function verifySignerSimple( EIP 191 facilitates pre-made signatures or _sponsored transactions_. For instance, Bob can sign a message, and Alice can send the transaction and pay for Bob’s gas fees. - +::image{src='/foundry-merkle-airdrop/10-signature-standards/signed-tx.png' style='width: 100%; height: auto;'} This EIP standardizes the signed data format: @@ -223,7 +223,7 @@ function verifySignerOZ( } ``` -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > EIP 712 prevents replay attacks by uniquely identifying the transaction. ### Conclusion diff --git a/courses/advanced-foundry/4-merkle-airdrop/11-ecdsa-signatures/+page.md b/courses/advanced-foundry/4-merkle-airdrop/11-ecdsa-signatures/+page.md index ac2001e31..307c685cb 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/11-ecdsa-signatures/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/11-ecdsa-signatures/+page.md @@ -59,5 +59,5 @@ ECDSA signatures consist of three integers: `v`, `r`, and `s`: Verifying ECDSA signatures involves using the signed message, the signature, and the public key to check if the signature is valid. This process essentially reverses the signing algorithm to ensure the provided `r` coordinate matches the calculated one. -> 👮‍♂️ **BEST PRACTICE**
+> 👮‍♂️ **BEST PRACTICE**:br > Using `ecrecover` directly can lead to security issues such as signature malleability. This can be mitigated by restricting the value of `s` to one half of the curve. The use of **OpenZeppelin's ECDSA library** is recommended, which provides protection against signature malleability and prevents invalid signatures from returning a zero address. diff --git a/courses/advanced-foundry/4-merkle-airdrop/12-transaction-types-introduction/+page.md b/courses/advanced-foundry/4-merkle-airdrop/12-transaction-types-introduction/+page.md index 6f9a44b22..bfc8c161d 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/12-transaction-types-introduction/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/12-transaction-types-introduction/+page.md @@ -12,4 +12,4 @@ We'll utilize the ZK Sync Remix plugin for deployment, ensuring our environment This MetaMask **signature request** displays details about the message, including transaction type, sender, recipient, and gas limit. This message shows an EIP712 message, with a transaction type `113`. Upon signing, the Remix terminal will then show the signed message's details like type, recipient, sender, gasLimit, gasPerPubdataByteLimit. - +::image{src='/foundry-merkle-airdrop/12-transaction-types-introduction/signature-request.png' style='width: 100%; height: auto;'} diff --git a/courses/advanced-foundry/4-merkle-airdrop/13-transaction-types/+page.md b/courses/advanced-foundry/4-merkle-airdrop/13-transaction-types/+page.md index 5e742a24c..eee821fd9 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/13-transaction-types/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/13-transaction-types/+page.md @@ -28,7 +28,7 @@ Introduced by EIP1559 during Ethereum's London fork, this transaction type aims - Adds the **Max Priority Fee per Gas**, the maximum fee the sender is willing to pay for prioritization. - Introduces the **Max Fee per Gas**, the total maximum fee the sender is willing to pay. - > 🗒️ **NOTE**
+ > 🗒️ **NOTE**:br > While ZK Sync supports type 2 transactions, it does not utilize the max fee parameters, as gas functions differently on ZK Sync. ### Type 3 (0x03) @@ -37,7 +37,7 @@ Introduced by EIP4844, this transaction type provides an initial scaling solutio - **Max Blob Fee per Gas**: This parameter sets the maximum fee the sender is willing to pay per gas unit specifically for **blob gas**. - > 🗒️ **NOTE**
+ > 🗒️ **NOTE**:br > Blob gas is a specific type of gas used in Ethereum to handle large data structures and is used in rollups. Blob gas is distinct from regular gas and has its own market. - **Blob Versioned Hashes**: A list of versioned blob hashes associated with the transaction blobs. These hashes are used to verify the integrity of the blobs and ensure they are correctly linked to the transaction. @@ -50,7 +50,7 @@ Next, we have two transaction types specific to ZK Sync: Defined by EIP712, these transactions standardize **data hashing** and **signing**, enabling features like **account abstraction** and **paymasters**. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Smart contracts on ZK Sync must be deployed using type 113 transactions. Fields specific to type 113 transactions are: diff --git a/courses/advanced-foundry/4-merkle-airdrop/15-understanding-type-113-transactions/+page.md b/courses/advanced-foundry/4-merkle-airdrop/15-understanding-type-113-transactions/+page.md index 49e38f80b..fbc800630 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/15-understanding-type-113-transactions/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/15-understanding-type-113-transactions/+page.md @@ -13,5 +13,5 @@ On Ethereum, there are two types of accounts: 1. **externally owned accounts (EOAs)**, which require a public and private key pair and users have to sign/initiate their own transactions 2. **contract accounts**. These are smart contracts that behave as accounts but are also capable of implementing more complex logic. They enable features such as _multiple signers_ or allowing others to pay for gas and send transactions on our behalf. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Aaccounts on ZK Sync are automatically _smart contract_ accounts. They are fully customizable and include different signature schemes, multi-signature capabilities, and setting spending limits. diff --git a/courses/advanced-foundry/4-merkle-airdrop/18-test-on-zksync-(optional)/+page.md b/courses/advanced-foundry/4-merkle-airdrop/18-test-on-zksync-(optional)/+page.md index dca856b18..2439675b8 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/18-test-on-zksync-(optional)/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/18-test-on-zksync-(optional)/+page.md @@ -6,7 +6,7 @@ _Follow along with the video_ --- -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > This lesson is optional We can also run our `MerkleAirdrop.t::testUsersCanClaim` test on the zkSync chain. @@ -17,7 +17,7 @@ To do this, we can start by switching to the zkSync version by running `foundryu forge build --ZK Sync ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > If you encounter any warnings, they may be related to the use of `ecrecover`. These warnings can be safely ingnored since indicate that the accounts should use an ECDSA private key and should be EOAs. This warning are shown because the ZK Sync era supports native account abstraction. Finally, we can run our tests on zkSync with the following command: diff --git a/courses/advanced-foundry/4-merkle-airdrop/20-creating-a-signature/+page.md b/courses/advanced-foundry/4-merkle-airdrop/20-creating-a-signature/+page.md index ac4f9c4b0..3cbc09a58 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/20-creating-a-signature/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/20-creating-a-signature/+page.md @@ -27,7 +27,7 @@ cast call 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 "getMessageHash(address,uin With the data ready for signing, use the `cast wallet sign` command. Include the `--no-hash` flag to prevent rehashing, as the message is already in bytes format. Also, use the `--private-key` flag with the first Anvil private key. -> 👮‍♂️ **Best Practice**
+> 👮‍♂️ **Best Practice**:br > When working on a testnet or using a real account, avoid using the private key directly. Instead, use the `--account` flag and your keystore account for signing. ```bash diff --git a/courses/advanced-foundry/4-merkle-airdrop/21-splitting-a-signature/+page.md b/courses/advanced-foundry/4-merkle-airdrop/21-splitting-a-signature/+page.md index f8911cddc..06ad0d795 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/21-splitting-a-signature/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/21-splitting-a-signature/+page.md @@ -33,5 +33,5 @@ To isolate each component, we'll create a function called `splitSignature` . Thi } ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > When working with functions from libraries like OpenZeppelin or other APIs, the signature format typically follows the order _v,r,s_ instead of the _r,s,v_ we used in this lesson. diff --git a/courses/advanced-foundry/4-merkle-airdrop/24-deploy-and-claim-on-zksync-sepolia-(optional)/+page.md b/courses/advanced-foundry/4-merkle-airdrop/24-deploy-and-claim-on-zksync-sepolia-(optional)/+page.md index e72914890..c6ab38a4e 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/24-deploy-and-claim-on-zksync-sepolia-(optional)/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/24-deploy-and-claim-on-zksync-sepolia-(optional)/+page.md @@ -12,7 +12,7 @@ In this lesson, we will be **manually deploying on zkSync Sepolia**. Although sc As usual, we will deploy the contracts `BagelToken` and `MerkleAirdrop`, generate the message hash, sign it, and split our long signature into its _v, r, s_ components. We'll then mint and trasfer tokens to the `MerkleAirdrop` contract, claim the tokens from a third party address and finally verify this claim. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > In MetaMask, you can create a wallet, for example, "updraft," using keystores where accounts are pre-saved, preventing the need to use the private key directly. For this demonstration, _updraft_ will deploy the contracts while _updraft 2_ will handle token claims. ### Deploying Contracts diff --git a/courses/advanced-foundry/4-merkle-airdrop/3-merkle-proofs/+page.md b/courses/advanced-foundry/4-merkle-airdrop/3-merkle-proofs/+page.md index b30e980f7..86681296a 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/3-merkle-proofs/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/3-merkle-proofs/+page.md @@ -10,7 +10,7 @@ _Follow along with the video_ Merkle Trees, Merkle Proofs, and Root Hashes are very important concepts in the realm of IT and blockchain technology. Invented by Ralph Merkle in 1979, a Merkle tree is a hierarchical structure where its base consists of **leaf nodes** representing data that has been hashed. The top of the tree is the **root hash**, created by hashing together pairs of adjacent nodes. This process continues up the tree, resulting in a single **root hash** that will represents all the data in the tree. - +::image{src='/foundry-merkle-airdrop/03-merkle-proof/merkle-tree.png' style='width: 100%; height: auto;'} ### Merkle Proofs @@ -20,7 +20,7 @@ For example, to prove that `Hash B` is part of the Merkle Tree, you would provid This allows the Merkle Tree **verifier** to reconstruct a root hash and compare it to the expected root hash. If they match, the original data is confirmed to be part of the Merkle tree. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Secure hashing functions, such as `keccak256`, are designed to prevent hash collisions ### Applications @@ -37,7 +37,7 @@ function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pur } ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > The **root** is typically stored _on-chain_, while the **proof** is generated _off-chain_. The [`processProof`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dbb6104ce834628e473d2173bbc9d47f81a9eec3/contracts/utils/cryptography/MerkleProof.sol#L49) function iterates through the proof array, updating the computed hash by hashing it with the next proof element. This process ultimately returns a computed hash, which is compared to the expected root to verify the leaf's presence in the Merkle Tree. diff --git a/courses/advanced-foundry/4-merkle-airdrop/4-base-airdrop-contract/+page.md b/courses/advanced-foundry/4-merkle-airdrop/4-base-airdrop-contract/+page.md index 362ace03d..eb3ac51a7 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/4-base-airdrop-contract/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/4-base-airdrop-contract/+page.md @@ -77,7 +77,7 @@ We can then use `safeTransfer` from the `SafeERC20` library to handle token tran i_airdropToken.safeTransfer(account, amount); ``` -> 👮‍♂️ **BEST PRACTICE**
+> 👮‍♂️ **BEST PRACTICE**:br > Using `safeTransfer` instead of `transfer` in ERC-20 token contracts provides an added level of security when performing token transfers. This library includes built-in checks to **automatically revert** the transaction if the transfer fails for any reason. If `transfer` fails, on the other hand, it can go unnoticed and create inconsistencies in the contract logic. ### Conclusion diff --git a/courses/advanced-foundry/4-merkle-airdrop/6-merkle-tree-script/+page.md b/courses/advanced-foundry/4-merkle-airdrop/6-merkle-tree-script/+page.md index 04e02b58d..a80ec219e 100644 --- a/courses/advanced-foundry/4-merkle-airdrop/6-merkle-tree-script/+page.md +++ b/courses/advanced-foundry/4-merkle-airdrop/6-merkle-tree-script/+page.md @@ -69,7 +69,7 @@ The `GenerateInput.s.sol` file will write the claim amounts, types, and addresse forge script script/GenerateInput.s.sol:GenerateInput ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > To avoid the following error: ``` diff --git a/courses/advanced-foundry/5-upgradeable-smart-contracts/1-upgradeable/+page.md b/courses/advanced-foundry/5-upgradeable-smart-contracts/1-upgradeable/+page.md index 20bd6f76a..3a9c954e9 100644 --- a/courses/advanced-foundry/5-upgradeable-smart-contracts/1-upgradeable/+page.md +++ b/courses/advanced-foundry/5-upgradeable-smart-contracts/1-upgradeable/+page.md @@ -12,11 +12,11 @@ Welcome to another informative blog post on the world of smart contracts. In thi To put this into perspective, upgradable smart contracts are a complex subject with potential drawbacks, which isn't the best route to default on. They sound great in theory, promising flexibility and adaptability. However, we've repeatedly seen that when there's too much centralized control over contracts, problems arise. - +::image{src='/upgrades/1-intro/upgrade1.png' style='width: 100%; height: auto;'} Let's dig deeper to understand the nuance of this subject and why it's important for your career as a smart contract developer. - +::image{src='/upgrades/1-intro/upgrade2.png' style='width: 100%; height: auto;'} ## What Are the Downside of Upgradable Smart Contracts? diff --git a/courses/advanced-foundry/5-upgradeable-smart-contracts/2-delegate-call/+page.md b/courses/advanced-foundry/5-upgradeable-smart-contracts/2-delegate-call/+page.md index 7b234a887..081c8f811 100644 --- a/courses/advanced-foundry/5-upgradeable-smart-contracts/2-delegate-call/+page.md +++ b/courses/advanced-foundry/5-upgradeable-smart-contracts/2-delegate-call/+page.md @@ -39,7 +39,7 @@ contract B { If we recall, storage acts _kind of_ like an array and each storage variable is sequentially assigned a slot in storage, in the order in which the variable is declared in a contract. - +::image{src='/foundry-upgrades/2-delegatecall/delegatecall1.png' style='width: 100%; height: auto;'} Now consider Contract A: @@ -64,7 +64,7 @@ This works fundamentally similar to `call`. In the case of `call` we would be ca With delegateCall however, we're borrowing the logic from Contract B and referencing the storage of Contract A. This is entirely independent of what the variables are actually named. - +::image{src='/foundry-upgrades/2-delegatecall/delegatecall2.png' style='width: 100%; height: auto;'} ### Remix @@ -72,28 +72,28 @@ Let's give this a shot ourselves, in Remix. If you'd like to try this yourself a Once the code has been pasted into Remix, we should be able to compile and begin with deploying Contract B. We can see that all of our storage variables begin empty. - +::image{src='/foundry-upgrades/2-delegatecall/delegatecall3.png' style='width: 100%; height: auto;'} By calling setVars and passing an argument, we can see how the storage variables within Contract B are updated as we've come to expect. - +::image{src='/foundry-upgrades/2-delegatecall/delegatecall4.png' style='width: 100%; height: auto;'} Now we can deploy Contract A. This contract should default to empty storage variables as well. When we call `setVars` on Contract A however, it's going to borrow the setVars logic from Contract B and we'll see it update it's own storage, rather than Contract B's. > ❗ **NOTE** > We'll need to pass Contract B as an input parameter to Contract A's setVars function so it knows where to delegate to! - +::image{src='/foundry-upgrades/2-delegatecall/delegatecall5.png' style='width: 100%; height: auto;'} Importantly, this behaviour, due to referencing storage slots directly, is independent of any naming conventions used for the variables themselves. - +::image{src='/foundry-upgrades/2-delegatecall/delegatecall6.png' style='width: 100%; height: auto;'} In fact, if Contract A didn't have any of it's own declared variables at all, the appropriate storage slots would _still_ be updated! Now, this is where things get really interesting. What if we changed the variable type of `number` in Contract A to a `bool`? If we then call delegateCall on Contract B, we'll see it's set our storage slot to `true`. The bool type detects our input as `true`, with `0` being the only acceptable input for `false`. - +::image{src='/foundry-upgrades/2-delegatecall/delegatecall7.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/5-upgradeable-smart-contracts/3-eip-1967/+page.md b/courses/advanced-foundry/5-upgradeable-smart-contracts/3-eip-1967/+page.md index 4b9b636e0..aef663411 100644 --- a/courses/advanced-foundry/5-upgradeable-smart-contracts/3-eip-1967/+page.md +++ b/courses/advanced-foundry/5-upgradeable-smart-contracts/3-eip-1967/+page.md @@ -178,7 +178,7 @@ function readStorage() public view returns(uint256 valueAtStorageSlotZero){ With these functions in place, we should be able to deploy our contracts in Remix. The first thing we'll need to do is call the setImplementation function on our SmallProxy contract, passing the address of `ImplementationA`. This is how the proxy knows where to delegate calls. - +::image{src='/foundry-upgrades/3-eip-1967/eip-1967-1.png' style='width: 100%; height: auto;'} By passing an argument to `getDataToTransact` we're provided the encoded call data necessary to set our `valueAtStorageSlotZero` to `777`. Remember, sending a transaction to our proxy with this call data should update the storage _in the proxy_. @@ -187,7 +187,7 @@ By passing an argument to `getDataToTransact` we're provided the encoded call da To see this in action, we just need to paste our `getDataToTransact` return value into the CALLDATA field and his `Transact`. - +::image{src='/foundry-upgrades/3-eip-1967/eip-1967-2.png' style='width: 100%; height: auto;'} `valueAtStorageSlotZero` has been updated on our proxy contract! @@ -209,7 +209,7 @@ Next, deploy ImplementationB and then call setImplementation on SmallProxy, pass Just like before, we can use `getDataToTransact` to determine our necessary call data. By passing _the same_ call data, pertaining to the argument 777 we can see ... - +::image{src='/foundry-upgrades/3-eip-1967/eip-1967-3.png' style='width: 100%; height: auto;'} `valueAtStorageSlotZero` now reflects the new implementation logic of `newValue + 2`! diff --git a/courses/advanced-foundry/5-upgradeable-smart-contracts/4-uups/+page.md b/courses/advanced-foundry/5-upgradeable-smart-contracts/4-uups/+page.md index d1c8a9888..196c1a17b 100644 --- a/courses/advanced-foundry/5-upgradeable-smart-contracts/4-uups/+page.md +++ b/courses/advanced-foundry/5-upgradeable-smart-contracts/4-uups/+page.md @@ -131,7 +131,7 @@ Within UUPSUpgradeable we can see the main function we'll need to leverage upgra Once inherited, we'll see a compiler warning advising that BoxV1 should be marked as abstract. - +::image{src='/foundry-upgrades/4-UUPS/UUPS1.png' style='width: 100%; height: auto;'} We receive this error because we don't have all the necessary functions defined in BoxV1 as required by UUPSUpgradeable which is an abstract contract. @@ -173,7 +173,7 @@ In older implementations of UUPSUpgradeable, you may see a line that I wanted to uint256[50] private __gap; ``` - +::image{src='/foundry-upgrades/4-UUPS/UUPS2.png' style='width: 100%; height: auto;'} If you recall to previous lessons, when values are assigned by a function, the variable name doesn't ultimately matter as the value is assigned to a storage slot. We saw that storage clashes were possible when an upgraded implementation contract made changes to the order of storage variable assignements, leading to some funky behaviours. @@ -211,7 +211,7 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I If we open Intializable.sol, there's a lot of valuable insight to be gained about what's happening and how it works. - +::image{src='/foundry-upgrades/4-UUPS/UUPS3.png' style='width: 100%; height: auto;'} This line of the documentation really gets to the heart of what the purpose of the initializer is. diff --git a/courses/advanced-foundry/5-upgradeable-smart-contracts/7-uups-tests/+page.md b/courses/advanced-foundry/5-upgradeable-smart-contracts/7-uups-tests/+page.md index 6721d4246..3a1c46bfb 100644 --- a/courses/advanced-foundry/5-upgradeable-smart-contracts/7-uups-tests/+page.md +++ b/courses/advanced-foundry/5-upgradeable-smart-contracts/7-uups-tests/+page.md @@ -111,7 +111,7 @@ forge test --mt testProxyStartAsBoxV1 We would expect this to pass if it reverts. - +::image{src='/foundry-upgrades/7-uups-tests/uups-tests1.png' style='width: 100%; height: auto;'} Looks great! Our BoxV1 doesn't have the setNumber function. Now we can try our other test! @@ -119,7 +119,7 @@ Looks great! Our BoxV1 doesn't have the setNumber function. Now we can try our o forge test --mt testUpgrades ``` - +::image{src='/foundry-upgrades/7-uups-tests/uups-tests1.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/6-account-abstraction/1-introduction/+page.md b/courses/advanced-foundry/6-account-abstraction/1-introduction/+page.md index 73ae4bdca..cd545d8c7 100644 --- a/courses/advanced-foundry/6-account-abstraction/1-introduction/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/1-introduction/+page.md @@ -12,11 +12,7 @@ Account abstraction is a fundamental concept of blockchain technology. It offers ## Problems Solved - +::image{src='/foundry-account-abstraction/1-introduction/current-wallet-issues.png' style='width: 100%; height: auto;'} ### Use of Private Keys for Signing Transactions Traditionally, users need to manage and use private keys to sign transactions. This can be annoying, confusing, and risky. Losing a private key means losing access to the account. Even worse, a stolen private key means that you've just lost all the value in that account. Account abstraction solves this problem by allowing users to sign transactions without using private keys. Instead, users can use a different type of key that is more user-friendly and secure. This simplifies the process of signing transactions and reduces the risk of losing access to an account, enhancing both security and user experience. @@ -28,11 +24,7 @@ Another challenge is that traditional transactions are validated by the sender's ## Two Entry Points - +::image{src='/foundry-account-abstraction/1-introduction/trade-eth-trans.png' style='width: 100%; height: auto;'} The traditional Ethereum transactions consists of first the signing of the transaction by the sender's private key, and then sending it to an Ethereum node. The node verifies that the signature is valid and if so, adds it to its mempool for later inclusion in a block. Account Absctraction, as we have already mentioned add improvemnts to this process. There are two entry points that we need to understand - Ethereum's `EntryPoint.sol` and zkSync's native integration. @@ -47,11 +39,7 @@ zkSync, on the other hand, has account abstraction natively integrated into its ## Account Abstraction Uses Alt-Mempools - +::image{src='/foundry-account-abstraction/1-introduction/user-op.png' style='width: 100%; height: auto;'} ### User Operations (Off-Chain) In Ethereum, user operations are first sent off-chain. This means that the initial handling and validation occur outside the main blockchain network, reducing congestion and improving efficiency. In the above example, the user operation is signed with Google and is sent to the alt-mempool, which then sends it to the main blockchain network. The alt mempool is any nodes which are facilitating this operation. So the user is not sending their transaction to the Ethereum nodes. @@ -70,11 +58,7 @@ Another optional component is the pay master. It handles gas payments, allowing ## zkSync - +::image{src='/foundry-account-abstraction/1-introduction/zksync-entry-point.png' style='width: 100%; height: auto;'} ### Acts as an Alt-Mempool In zkSync, the alt-mempool nodes are also the zkSync nodes. This means that sending the transaction to the alt-mempool can be skipped. The reason zkSync can do this is because every account (e.g., MetaMask) is by default a smart contract account as it is automatically connected to a [DefaultAccount.sol](https://github.com/matter-labs/era-contracts/blob/main/system-contracts/contracts/DefaultAccount.sol). diff --git a/courses/advanced-foundry/6-account-abstraction/15-advanced-debugging/+page.md b/courses/advanced-foundry/6-account-abstraction/15-advanced-debugging/+page.md index df29424e0..c43f71b60 100644 --- a/courses/advanced-foundry/6-account-abstraction/15-advanced-debugging/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/15-advanced-debugging/+page.md @@ -16,46 +16,27 @@ forge test --debug testEntryPointCanExecuteCommands -vvv --- - +::image{src='/foundry-account-abstraction/15-advanced-debugging/forge-debug1.png' style='width: 100%; height: auto;'} --- --- - - +::image{src='/foundry-account-abstraction/15-advanced-debugging/forge-debug2.png' style='width: 100%; height: auto;'} --- Simply hit shift G, and you will be taken to where the test reverted. You'll see that the line of code where the issue is highlighted. --- - - +::image{src='/foundry-account-abstraction/15-advanced-debugging/forge-debug3.png' style='width: 100%; height: auto;'} --- We know that the issue is likely in the `handleOps`, as this is the part that we recently refactored in this line. Now we have to find the line in the `handleOps` code that actually failed. Start hitting the J to walk back through the code base. It may take a few seconds, but eventually you should see this: --- - - +::image{src='/foundry-account-abstraction/15-advanced-debugging/forge-debug4.png' style='width: 100%; height: auto;'} --- ### Getting the Correct Sender diff --git a/courses/advanced-foundry/6-account-abstraction/16-mid-session-recap/+page.md b/courses/advanced-foundry/6-account-abstraction/16-mid-session-recap/+page.md index 7e68995b8..fcfb7fc34 100644 --- a/courses/advanced-foundry/6-account-abstraction/16-mid-session-recap/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/16-mid-session-recap/+page.md @@ -1,12 +1,7 @@ ## Account Abstraction Lesson 16: Mid Session Recap --- - - +::image{src='/foundry-account-abstraction/1-introduction/user-op.png' style='width: 100%; height: auto;'} --- ### Summary of Our Account Abstraction Minimal Account Journey diff --git a/courses/advanced-foundry/6-account-abstraction/18-zksync-setup/+page.md b/courses/advanced-foundry/6-account-abstraction/18-zksync-setup/+page.md index 5c5a5b290..57e841c2c 100644 --- a/courses/advanced-foundry/6-account-abstraction/18-zksync-setup/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/18-zksync-setup/+page.md @@ -6,12 +6,7 @@ Welcome to the beginning of our journey with **account abstraction** on **zkSync **zkSync Account Flow** - - +::image{src='/foundry-account-abstraction/18-zksync-setup/zksync-account-flow.png' style='width: 100%; height: auto;'} --- ### Overview of ZK System Contracts @@ -46,12 +41,7 @@ On the other hand, in zkSync EOAs are smart contracts. Thus, all smart contract --- - - +::image{src='/foundry-account-abstraction/18-zksync-setup/zk-era-explorer.png' style='width: 100%; height: auto;'} --- ### IAccount Interface diff --git a/courses/advanced-foundry/6-account-abstraction/2-code-overview/+page.md b/courses/advanced-foundry/6-account-abstraction/2-code-overview/+page.md index c4a539877..c30d61cd3 100644 --- a/courses/advanced-foundry/6-account-abstraction/2-code-overview/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/2-code-overview/+page.md @@ -18,10 +18,6 @@ Or, you can head straight to the main sources below. These two ecosystems are vastly different. The upcoming lessons will allow us to become more familiar with both of them. For now, we can see some examples from the README file. Click on ZkMinimalAccount.sol to see the example deployments for zkSync and MinimalAccount.sol for Ethereum. - +::image{src='/foundry-account-abstraction/2-code-overview/example-deployments.png' style='width: 100%; height: auto;'} When you are ready, let's begin our journey with Ethereum minimal account abstraction. diff --git a/courses/advanced-foundry/6-account-abstraction/20-system-contracts/+page.md b/courses/advanced-foundry/6-account-abstraction/20-system-contracts/+page.md index 2a56488e8..de27fa601 100644 --- a/courses/advanced-foundry/6-account-abstraction/20-system-contracts/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/20-system-contracts/+page.md @@ -41,11 +41,7 @@ A system contract contains smart contracts that are deployed on zkSync by defaul - You should see **ContractDeployer**. --- - +::image{src='/foundry-account-abstraction/20-system-contracts/contract-deployer1.png' style='width: 100%; height: auto;'} --- Scroll down a bit and click on the contract tab. You should then be able to see `ContractDeployer.sol` along with many other zkSync contracts. Essentially, the ContractDeployer governs other contracts. It is a system contract that is responsible: diff --git a/courses/advanced-foundry/6-account-abstraction/3-eth-setup/+page.md b/courses/advanced-foundry/6-account-abstraction/3-eth-setup/+page.md index 74d4ad605..716add1c6 100644 --- a/courses/advanced-foundry/6-account-abstraction/3-eth-setup/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/3-eth-setup/+page.md @@ -92,12 +92,7 @@ From this information, we know that we will need some specific functions to make --- - - +::image{src='/foundry-account-abstraction/3-eth-setup/eip-4337.png' style='width: 100%; height: auto;'} --- Here we will find the `UserOperation` containing all of the data that needs to go to the alt-mempools. When passed to on-chain contracts, a packed version of this called **EntryPoint definition** is used. You can have a [look at the contract on Etherscan here](https://etherscan.io/address/0x0000000071727de22e5e9d8baf0edac6f37da032). @@ -106,12 +101,7 @@ Furthermore, we can [view the contract code directly in our browser here.](https --- - - +::image{src='/foundry-account-abstraction/3-eth-setup/etherscan-deth.png' style='width: 100%; height: auto;'} --- Click on the magnifying glass icon in the top left of the screen. Type **function handleops** in the search box. You will see that it takes a `PackedUserOperation` and an `address payable`. When we send our information to the alt-mempool nodes, we need to send it so that the nodes can then send the `PackedUserOperation`, which is essentially a struct and is a stand alone contract - `PackedUserOperation.sol`. @@ -136,12 +126,7 @@ Scroll down until you see the **Account Contract Interface**. --- - - +::image{src='/foundry-account-abstraction/3-eth-setup/account-interface.png' style='width: 100%; height: auto;'} --- The function takes a userOp, userOpHas, and missingAccountFunds to determine whether or not the user operation is valid. If not valid, it will revert and the alt-mempool nodes won't be able to send the transaction. diff --git a/courses/advanced-foundry/6-account-abstraction/7-execute/+page.md b/courses/advanced-foundry/6-account-abstraction/7-execute/+page.md index 3a173247d..7372553d2 100644 --- a/courses/advanced-foundry/6-account-abstraction/7-execute/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/7-execute/+page.md @@ -16,12 +16,7 @@ We've come a long way, but we aren't quite done yet. We still need to enable our --- - - +::image{src='/foundry-account-abstraction/7-execute-function-ethereum/account-abstraction.jpg' style='width: 100%; height: auto;'} --- In order for us to make all this happen, we need a new external function called `execute`. It will pass an address for the destination, uint256 for eth, and bytes calldata for ABI encoded function data. If not successful, it will revert. Be sure to add `MinimalAccount__CallFailed(result)` to the errors section of your code. diff --git a/courses/advanced-foundry/6-account-abstraction/9-owner-execute-test/+page.md b/courses/advanced-foundry/6-account-abstraction/9-owner-execute-test/+page.md index 2d260d4b2..4239dc13d 100644 --- a/courses/advanced-foundry/6-account-abstraction/9-owner-execute-test/+page.md +++ b/courses/advanced-foundry/6-account-abstraction/9-owner-execute-test/+page.md @@ -84,12 +84,7 @@ The owner should be able to call this function and send a transaction. --- - - +::image{src='/foundry-account-abstraction/9-owner-execute-test/basic-account-flow.png' style='width: 100%; height: auto;'} --- ### Test if Owner Can Execute Commands diff --git a/courses/advanced-foundry/7-daos/1-intro/+page.md b/courses/advanced-foundry/7-daos/1-intro/+page.md index 8c07f03b8..003e5e484 100644 --- a/courses/advanced-foundry/7-daos/1-intro/+page.md +++ b/courses/advanced-foundry/7-daos/1-intro/+page.md @@ -45,15 +45,15 @@ Let's take a closer look at an active an live DAO Today. Compound protocol is se We can access their governance system, and view past and pending proposals, through the Governance UI of their website. - +::image{src='/foundry-daos/1-intro/intro1.png' style='width: 100%; height: auto;'} If we navigate to one [**specific proposal**](https://compound.finance/governance/proposals/256), we can gain a lot of insight into how this process works. The proposal view breaks down the votes received, the number of particating addresses and importantly the Proposal History. We're able to see that every proposal begins with a transaction. - +::image{src='/foundry-daos/1-intro/intro2.png' style='width: 100%; height: auto;'} Let's take a closer look at this [**create transaction**](https://etherscan.io/tx/0xbe0b8152195a29c7ac61144dbaa9f98b00fbb7c59d15b19e96105a42195fa829)! This transaction will show us all of the data submitted to the proposal. - +::image{src='/foundry-daos/1-intro/intro3.png' style='width: 100%; height: auto;'} Decoding this function selector shows a pretty standard propose function. Typically a proposal will be broken down into: @@ -70,11 +70,11 @@ Often the functions being called are part of the DAO's functionality and typical Once created, and after a brief delay configured by the protocol, a proposal becomes active for voting. This is a predetermined duration during which members of the DAO can vote to accept or refuse a proposal - +::image{src='/foundry-daos/1-intro/intro4.png' style='width: 100%; height: auto;'} Voting can happen directly on-chain of course, or through the provided app interface provided [**here**](https://app.compound.finance/vote?market=usdc-mainnet). - +::image{src='/foundry-daos/1-intro/intro5.png' style='width: 100%; height: auto;'} If voting succeeds, a proposal will be queued for execution. This queue period affords time before execution of a proposal for a number of things including: diff --git a/courses/advanced-foundry/7-daos/2-aragon/+page.md b/courses/advanced-foundry/7-daos/2-aragon/+page.md index 19564d8c4..c2d007d9e 100644 --- a/courses/advanced-foundry/7-daos/2-aragon/+page.md +++ b/courses/advanced-foundry/7-daos/2-aragon/+page.md @@ -12,43 +12,43 @@ In this lesson we're guided by Juliette Chevalier in the process of creating a D Begin by navigating to [**app.aragon.org**](https://app.aragon.org/). Once on the landing page you can connect a wallet and jump right into `Create your DAO`. - +::image{src='/foundry-daos/2-aragon/aragon1.png' style='width: 100%; height: auto;'} --- You should be met with a wonderful breakdown of the steps we'll take to create our DAO, select `Build your DAO` again to continue. - +::image{src='/foundry-daos/2-aragon/aragon2.png' style='width: 100%; height: auto;'} --- Next, you'll be asked to select a chain you expect to deploy on, with Sepolia available as a testnet. - +::image{src='/foundry-daos/2-aragon/aragon3.png' style='width: 100%; height: auto;'} --- Following this you'll be tasked with describing some details of the DAO, giving it a name, logo and personality. This is also where links to DAO socials would be included such as LinkedIn, Twitter, Discord etc. - +::image{src='/foundry-daos/2-aragon/aragon4.png' style='width: 100%; height: auto;'} --- Next is all about defining membership and who/how governance is allocated. In this stage you can decide the details of a new ERC20 governance token and to whom they are minted, or even import an existing token that the community can use. - +::image{src='/foundry-daos/2-aragon/aragon5.png' style='width: 100%; height: auto;'} --- From here, we need to define the specific, granular details pertaining to how governance and proposals are handled by the protocol. These details include the threshold needed for successful proposals, duration to vote, who can create proposals etc. - +::image{src='/foundry-daos/2-aragon/aragon7.png' style='width: 100%; height: auto;'} --- And finally, we need to verify our configurations before deployment! Importantly, all of our configurations can be adjusted after deployment with a successful proposal (with the exception of the blockchain we're deployed on). - +::image{src='/foundry-daos/2-aragon/aragon6.png' style='width: 100%; height: auto;'} --- diff --git a/courses/advanced-foundry/7-daos/4-governance-tokens/+page.md b/courses/advanced-foundry/7-daos/4-governance-tokens/+page.md index 9357524fe..199c766e4 100644 --- a/courses/advanced-foundry/7-daos/4-governance-tokens/+page.md +++ b/courses/advanced-foundry/7-daos/4-governance-tokens/+page.md @@ -10,7 +10,7 @@ _Follow along with this video._ As mentioned in the previous closing remarks, I suspect this will mostly be review, as we set up this token, so let's keep our momentum and jump right into it. Start with creating `src/GovToken.sol`. The token we'll use in this demonstration will be _so standard_ that we can just lean on [**OpenZeppelin's Contract Wizard**](https://wizard.openzeppelin.com/) and select `ERC20` and `votes`. - +::image{src='/foundry-daos/4-governance-tokens/governance-tokens1.png' style='width: 100%; height: auto;'} Copying this into our contract and we're already almost set (I've adjusted below to utilize named imports). diff --git a/courses/advanced-foundry/7-daos/5-governor-contract/+page.md b/courses/advanced-foundry/7-daos/5-governor-contract/+page.md index c8112c43f..eb78f9ad2 100644 --- a/courses/advanced-foundry/7-daos/5-governor-contract/+page.md +++ b/courses/advanced-foundry/7-daos/5-governor-contract/+page.md @@ -14,7 +14,7 @@ Fortunately, [**OpenZeppelin's Contract Wizard**](https://wizard.openzeppelin.co By selecting the `Governor` configuration, we're presenting with a number of options to customize. - +::image{src='/foundry-daos/5-governor-contract/governor-contract1.png' style='width: 100%; height: auto;'} Voting Delay - Time between proposal creation and the start of voting diff --git a/courses/advanced-foundry/7-daos/6-tests/+page.md b/courses/advanced-foundry/7-daos/6-tests/+page.md index 0ee24efca..d55778241 100644 --- a/courses/advanced-foundry/7-daos/6-tests/+page.md +++ b/courses/advanced-foundry/7-daos/6-tests/+page.md @@ -150,7 +150,7 @@ All we need, let's run it as a sanity check! forge test --mt testCantUpdateBoxWithoutGovernance ``` - +::image{src='/foundry-daos/6-tests/tests1.png' style='width: 100%; height: auto;'} Beautiful! @@ -369,7 +369,7 @@ Woo! This is exciting, we're ready to run the test. Forge test --mt testGovernanceUpdatesBox -vvv ``` - +::image{src='/foundry-daos/6-tests/tests2.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/7-daos/7-recap/+page.md b/courses/advanced-foundry/7-daos/7-recap/+page.md index 7b8507229..839ac3c08 100644 --- a/courses/advanced-foundry/7-daos/7-recap/+page.md +++ b/courses/advanced-foundry/7-daos/7-recap/+page.md @@ -93,7 +93,7 @@ Some of the adjustments to this contract which improve gas efficiency include: What does all of this mean for the gas spent with this contract? - +::image{src='/foundry-daos/7-recap/recap1.png' style='width: 100%; height: auto;'} Over 600,000 gas savings! Simple changes and gas conscious development is all it takes! diff --git a/courses/advanced-foundry/8-security/4-top-tools/+page.md b/courses/advanced-foundry/8-security/4-top-tools/+page.md index 1abac3e3d..a535c1133 100644 --- a/courses/advanced-foundry/8-security/4-top-tools/+page.md +++ b/courses/advanced-foundry/8-security/4-top-tools/+page.md @@ -164,7 +164,7 @@ We haven't gone over the exploit in this code before, but it's known as reentran With Slither installed, I can run the command `slither .` and Slither will output all of the issues it detects in our code, right to the terminal. - +::image{src='/foundry-security/3-top-tools/top-tools1.png' style='width: 100%; height: auto;'} Look how easy that is. It won't catch everything, but Slither is one of those tools I believe everyone should run on their codebase before going to audit. @@ -242,7 +242,7 @@ contract CaughtWithFuzzTest is Test { Running this test shows us clearly the power of a thorough fuzz testing suite. - +::image{src='/foundry-security/3-top-tools/top-tools2.png' style='width: 100%; height: auto;'} Our fuzz test identifies the counter-example of 1265! @@ -301,7 +301,7 @@ contract CaughtWithStatefulFuzzTest is StdInvariant, Test { We can see here the running our stateful fuzz test `invariant_testMathDoesntReturnZero` indentifies the arguments to pass and order of functions to call which breaks our invariant. - +::image{src='/foundry-security/3-top-tools/top-tools3.png' style='width: 100%; height: auto;'} Lastly, we have `CaughtWithSymbolic.sol` where we can actually just use the solidity compiler to try and catch some bugs. @@ -341,7 +341,7 @@ targets = ['assert'] By running `forge build` with these settings, we'll receive an output from our compiler, clearly indicating where the assertion is violated with a counter-example: - +::image{src='/foundry-security/3-top-tools/top-tools4.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/advanced-foundry/8-security/5-manual-review/+page.md b/courses/advanced-foundry/8-security/5-manual-review/+page.md index fd81bbc5b..137f6dc61 100644 --- a/courses/advanced-foundry/8-security/5-manual-review/+page.md +++ b/courses/advanced-foundry/8-security/5-manual-review/+page.md @@ -41,7 +41,7 @@ It's recommended to start with the smaller and more manageable contracts and bui There's a point in an audit where your frame of mind should switch to an adversarial one. You should be thinking _"How can I break this..."_ - +::image{src='/security-section-3/6-the-tincho/tincho1.png' style='width: 100%; height: auto;'} Given even simple functions like above, we should be asking ourselves diff --git a/courses/advanced-foundry/8-security/6-formal-verification/+page.md b/courses/advanced-foundry/8-security/6-formal-verification/+page.md index 9895c4da1..57c652b76 100644 --- a/courses/advanced-foundry/8-security/6-formal-verification/+page.md +++ b/courses/advanced-foundry/8-security/6-formal-verification/+page.md @@ -41,7 +41,7 @@ function testSetNumber() public { Running this test, we'd see: - +::image{src='/foundry-defi/16-defi-leveling-up-testing/defi-leveling-up-testing1.png' style='width: 100%; height: auto;'} The unit test catches this right away. All of the most popular frameworks have unit tests built in! @@ -83,7 +83,7 @@ Here's an example of a fuzz test we could perform: You can see, we don't explicitly declare the value for `data` in our test, and instead pass it as an argument to the test function. The Foundry framework will satisfy this argument with random data until it breaks our invariant (or stops based on configurations set). When run, we can see the framework identifies the edge case which breaks our asserted property. - +::image{src='/foundry-defi/16-defi-leveling-up-testing/defi-leveling-up-testing2.png' style='width: 100%; height: auto;'} ### Layer 3 Static Analysis @@ -103,7 +103,7 @@ function withdraw() external { The above withdraw function has a classic reentrancy attack. We know an issue like this arrises from not following the CEI pattern! A static analysis tool like Slither will be able to pick up on this quite easily. - +::image{src='/foundry-defi/16-defi-leveling-up-testing/defi-leveling-up-testing3.png' style='width: 100%; height: auto;'} ### Layer 4 Formal Verification diff --git a/courses/blockchain-basics/1-basics/12-signing-transactions/+page.md b/courses/blockchain-basics/1-basics/12-signing-transactions/+page.md index 3cf50eaae..d6d9051a1 100644 --- a/courses/blockchain-basics/1-basics/12-signing-transactions/+page.md +++ b/courses/blockchain-basics/1-basics/12-signing-transactions/+page.md @@ -18,17 +18,17 @@ The private key is then passed through an algorithm (the [**Elliptic Curve Digit When we send a transaction to the blockchain, we're passing a private key - this allows others to verify the transaction through the generated public key -signing-transactions1 +::image{src='/blockchain-basics/08-signing-transactions/signing-transactions1.png' style='width: 100%; height: auto;' alt='signing-transactions1'} ### How does Transaction Signing Happen? When we sign a transaction on the blockchain, we're digitally signing some data with our private key. The hashing algorithm used makes it impossible for something to derive your private key from a message signature. -signing-transactions2 +::image{src='/blockchain-basics/08-signing-transactions/signing-transactions2.png' style='width: 100%; height: auto;' alt='signing-transactions2'} This signing method allows anyone to verify the validity of a transaction by comparing the message signature to a user's public key! -signing-transactions3 +::image{src='/blockchain-basics/08-signing-transactions/signing-transactions3.png' style='width: 100%; height: auto;' alt='signing-transactions3'} ### Importance of Hiding Private Keys diff --git a/courses/blockchain-basics/1-basics/13-gas-II/+page.md b/courses/blockchain-basics/1-basics/13-gas-II/+page.md index 25cb8616f..c169cd674 100644 --- a/courses/blockchain-basics/1-basics/13-gas-II/+page.md +++ b/courses/blockchain-basics/1-basics/13-gas-II/+page.md @@ -21,7 +21,7 @@ Before we continue, there are a couple important terms to understand. Wei: 1,000,000,000 Wei = 1 Gwei (Gigawei) Gwei: 1000,000,000 Gwei = 1 Eth - +::image{src='/blockchain-basics/09-gas-II/gas-II1.png' style='width: 75%; height: auto;'} _Reference the above image, the labelled sections will be detailed below_ @@ -31,7 +31,7 @@ _Reference the above image, the labelled sections will be detailed below_ In Metamask, you can navigate to `Market > Advanced > Edit Gas Limit` in order to set this value. - +::image{src='/blockchain-basics/09-gas-II/gas-II2.png' style='width: 75%; height: auto;'} **3. Base Gas Fee:** The base fee of a transaction, represented in Gwei. Remember, this is cost per gas. diff --git a/courses/blockchain-basics/1-basics/15-l1s-l2s-and-rollups/+page.md b/courses/blockchain-basics/1-basics/15-l1s-l2s-and-rollups/+page.md index 0ea66b44e..04742fbb4 100644 --- a/courses/blockchain-basics/1-basics/15-l1s-l2s-and-rollups/+page.md +++ b/courses/blockchain-basics/1-basics/15-l1s-l2s-and-rollups/+page.md @@ -20,19 +20,11 @@ A **Layer 2** is any application built on outside an L1 blockchain that _hooks b **Rollups** are L2 scaling solutions that enable to increase the number of transactions on Ethereum by bundling multiple transactions into one, reducing gas costs. - +::image{src='/blockchain-basics/15-l1s-l2s-and-rollups/tx-bundle.png' style='width: 100%; height: auto;'} Rollups help solve the blockchain trilemma, which states that a blockchain can only achieve two out of three properties: _decentralization_, _security_, and _scalability_. In the case of Ethereum, **scalability** is sacrificed as it can only process approximately 15 transactions per second. Rollups, on the other hand, aim to enhance scalability without compromising security or decentralization. - +::image{src='/blockchain-basics/15-l1s-l2s-and-rollups/bc-trilemma.png' style='width: 100%; height: auto;'} #### How Rollups Work diff --git a/courses/blockchain-basics/1-basics/17-rollup-stages/+page.md b/courses/blockchain-basics/1-basics/17-rollup-stages/+page.md index 596766e03..c0a523202 100644 --- a/courses/blockchain-basics/1-basics/17-rollup-stages/+page.md +++ b/courses/blockchain-basics/1-basics/17-rollup-stages/+page.md @@ -22,11 +22,7 @@ A Layer 2 (L2) chains maturity is evaluated based on specific properties and cat In the [L2Beat summary](https://l2beat.com/scaling/summary) it's possible to see the actual stage of each rollup: - +::image{src='/blockchain-basics/17-rollup-stages/l2beat-summary.png' style='width: 100%; height: auto;'} Currently, [Zksync Era](https://l2beat.com/scaling/projects/zksync-era) is operating as a `Stage 0` rollup. In the dedicated page on L2, we can find a risk analysis: diff --git a/courses/blockchain-basics/1-basics/18-making-your-first-transaction-on-zksync/+page.md b/courses/blockchain-basics/1-basics/18-making-your-first-transaction-on-zksync/+page.md index e7eb4a1bc..042a5b757 100644 --- a/courses/blockchain-basics/1-basics/18-making-your-first-transaction-on-zksync/+page.md +++ b/courses/blockchain-basics/1-basics/18-making-your-first-transaction-on-zksync/+page.md @@ -24,19 +24,11 @@ Our first transaction involves receiving funds. There are two ways to receive fu - **Locking and Unlocking**: Tokens are locked on the source chain and unlocked on the destination chain. - + ::image{src='/blockchain-basics/18-making-your-first-transaction-on-zksync/lock-unlock.png' style='width: 100%; height: auto;'} - **Minting and Burning**: Tokens are burned on the source chain and minted on the destination chain. The bridge protocol must control the token supply to manage this process. An example is [CCTV](https://www.circle.com/en/cross-chain-transfer-protocol) by the Circle team, where USDC is burned and minted to facilitate bridging. - + ::image{src='/blockchain-basics/18-making-your-first-transaction-on-zksync/burn-mint.png' style='width: 100%; height: auto;'} 3. **Get Sepolia ETH**: Use the [recommended faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) to obtain Sepolia ETH. With 0.05 Sepolia ETH, you're ready to transfer to zkSync Sepolia. diff --git a/courses/blockchain-basics/1-basics/9-making-your-first-transaction/+page.md b/courses/blockchain-basics/1-basics/9-making-your-first-transaction/+page.md index 5fcb0fcc2..e3ee88082 100644 --- a/courses/blockchain-basics/1-basics/9-making-your-first-transaction/+page.md +++ b/courses/blockchain-basics/1-basics/9-making-your-first-transaction/+page.md @@ -55,7 +55,7 @@ This recovery phrase (sometimes referred to as a mnemonic) is your master key, s From this point, you should be able to see your metamask interface. It should look something like this: - +::image{src='/blockchain-basics/05-first-transaction/first-transaction1.png' style='width: 75%; height: auto;'} You can Pin Metamask to the top of your browser for easy access to this view in future. @@ -76,7 +76,7 @@ In addition to this, we'll also be covering how to test and deploy on a _local_ By toggling the `show test networks` option, we can see which testnets come included by default. - +::image{src='/blockchain-basics/05-first-transaction/first-transaction2.png' style='width: 75%; height: auto;'} We're able to switch networks simply by clicking on any network on the available list. Try out Sepolia! @@ -90,28 +90,28 @@ In order to experience your first transaction, we're going to navigate to a `fau [**Sepolia Faucet**](https://faucets.chain.link/sepolia) - +::image{src='/blockchain-basics/05-first-transaction/first-transaction3.png' style='width: 75%; height: auto;'} From this page you can connect your wallet with the click of a button. Once clicked, agree to the terms of service and select `Metamask`. - +::image{src='/blockchain-basics/05-first-transaction/first-transaction4.png' style='width: 75%; height: auto;'} Your Metamask should pop up and give you the option to select your account, following by a confirmation to connect your wallet. - - +::image{src='/blockchain-basics/05-first-transaction/first-transaction5.png' style='width: 75%; height: auto;'} +::image{src='/blockchain-basics/05-first-transaction/first-transaction6.png' style='width: 75%; height: auto;'} In order to request testnet native tokens (like SepoliaEth) you'll need to verify your GitHub account. One that's done, you should be ready to send your request! - +::image{src='/blockchain-basics/05-first-transaction/first-transaction7.png' style='width: 75%; height: auto;'} After a brief delay we should see something like this! - +::image{src='/blockchain-basics/05-first-transaction/first-transaction8.png' style='width: 75%; height: auto;'} I encourage you to click the transaction hash, you'll be brought to Sepolia Etherscan and provided a tonne of information about the details of your transaction. Additionally, you should be able to open up your Metamask wallet and confirm you did indeed receive your requested Sepolia Eth! - +::image{src='/blockchain-basics/05-first-transaction/first-transaction9.png' style='width: 75%; height: auto;'} Try toggling your Metamask wallet between networks now, you'll notice that it's only on Sepolia that you've gained your test ETH. If you want to practice further, there are additional testnet blockchains with faucets available for you to try. @@ -125,7 +125,7 @@ Taking a brief look at some of the details of our transaction on Etherscan, we'r - Value - any funds included with the transaction - Gas - the cost of the transaction to execute, we'll be looking into gas more closely in the next lesson. - +::image{src='/blockchain-basics/05-first-transaction/first-transaction10.png' style='width: 75%; height: auto;'} ### Wrap Up diff --git a/courses/formal-verification/0-introduction/2-best-practices/+page.md b/courses/formal-verification/0-introduction/2-best-practices/+page.md index 36d485b58..1bc0ace57 100644 --- a/courses/formal-verification/0-introduction/2-best-practices/+page.md +++ b/courses/formal-verification/0-introduction/2-best-practices/+page.md @@ -19,13 +19,13 @@ Let's kick things off with some resources and best practices to succeed in this > ❗ **NOTE** > Each section's respective repo will have its own `discussions` tab. Please use this to raise questions, doubts or concerns! Please leverage `discussions` for technical questions as often as you can so others may learn from your question! - +::image{src='/formal-verification-0/best-practices/best-practices1.png' style='width: 100%; height: auto;'} Given the advanced nature of this course, I expect everyone to have some idea as to how to formulate and ask good questions. For those looking for a bit of guidance in this respect, the course repo contains guidelines you can follow in [**how-to-ask-a-question.md**](https://github.com/Cyfrin/assembly-evm-opcodes-and-formal-verification-course/blob/main/how-to-ask-a-question.md) and [**how-to-answer-a-question.md**](https://github.com/Cyfrin/assembly-evm-opcodes-and-formal-verification-course/blob/main/how-to-answer-a-question.md) This space moves quickly and things are always changing, because of this some recorded content may become inaccurate. Keep an eye out below each video for an `Updates` section if you come across any inconsistencies in the course content. If you don't find an update there, consider submitting feedback, or double checking your issue in GitHub discussions! 🙏 - +::image{src='/formal-verification-0/best-practices/best-practices2.png' style='width: 100%; height: auto;'} ### Tips @@ -34,7 +34,7 @@ This space moves quickly and things are always changing, because of this some re - **Pace Yourself:** Go at the speed that makes sense for you. Everyone is going to have their own speed, their own level of experience and their own circumstances that all add up to a unique pace of learning. What makes sense for one person may not make sense for you, and that's ok. Find consistency without it feeling forced. - **Customize Video Playback:** Updraft allows you to set the speed at which the video is played, so if I'm talking too quickly - slow me down! Addtionally, Updraft supports an ever growing list of closed caption languages, so customize your experience in a way that removes the friction from learning. - +::image{src='/formal-verification-0/best-practices/best-practices3.png' style='width: 100%; height: auto;'} > ❗ **NOTE** > If you would like to contribute subtitles for your native language, please reach out via [**GitHub Issues**](https://github.com/Cyfrin/assembly-evm-opcodes-and-formal-verification-course/issues), or [**Cyfrin Discord**](https://discord.gg/cyfrin) server. diff --git a/courses/formal-verification/1-horse-store/1-huff-yul-opcode/+page.md b/courses/formal-verification/1-horse-store/1-huff-yul-opcode/+page.md index a7bb263a0..6726f155d 100644 --- a/courses/formal-verification/1-horse-store/1-huff-yul-opcode/+page.md +++ b/courses/formal-verification/1-horse-store/1-huff-yul-opcode/+page.md @@ -27,7 +27,7 @@ Proceed with cleaning up the workspace by deleting `src/Counter.sol`, `test/Coun Create a new folder within `src` named `horseStoreV1` and within that folder create the file `HorseStore.sol` - +::image{src='/formal-verification-1/1-huff-yul-opcodes/huff-yul-opcodes-1.png' style='width: 100%; height: auto;'} It's a fairly simple contract, feel free to copy it from the [**GitHub**](https://github.com/Cyfrin/1-horse-store-s23/blob/main/src/horseStoreV1/HorseStore.sol) associated with this lesson, or copy it below: @@ -64,7 +64,7 @@ Success should grace your screen, and with it, confirmation of a job well done. Once built, you should be able to navigate to `out/HorseStore.sol/HorseStore.json`. I recommend utilizing your command pallet in VS Code to format the document and toggle word-wrap, to assist in readability. - +::image{src='/formal-verification-1/1-huff-yul-opcodes/huff-yul-opcodes-2.png' style='width: 100%; height: auto;'} This json is going to have a _lot_ of stuff in it, and much of it isn't important to us right now. Minimize the `abi` and locate the outputs `bytecode` and `deployedBytecode` This is going to be our focus in the coming lesson! diff --git a/courses/formal-verification/1-horse-store/10-stack-memory-and-storage/+page.md b/courses/formal-verification/1-horse-store/10-stack-memory-and-storage/+page.md index 59d58418e..508a32b12 100644 --- a/courses/formal-verification/1-horse-store/10-stack-memory-and-storage/+page.md +++ b/courses/formal-verification/1-horse-store/10-stack-memory-and-storage/+page.md @@ -13,7 +13,7 @@ Let's expand further on the concepts from the previous lesson because there are - Memory - Storage - +::image{src='/formal-verification-1/10-memory-and-storage/memory-and-storage-1.png' style='width: 75%; height: auto;'} As depicted above, unlike the stack, data can be stored and retrieved from any slot available. @@ -26,7 +26,7 @@ In addition to these, accessing data in `storage` is _much_ more expensive than I'll draw your attention to the SSTORE and MSTORE - +::image{src='/formal-verification-1/10-memory-and-storage/memory-and-storage-2.png' style='width: 75%; height: auto;'} We can see clearly to what extent I mean _"more expensive"_. The difference in gas when accessing data in `storage` is **massive.** diff --git a/courses/formal-verification/1-horse-store/11-push-and-add-opcode/+page.md b/courses/formal-verification/1-horse-store/11-push-and-add-opcode/+page.md index f35da7223..7cf7d9cbb 100644 --- a/courses/formal-verification/1-horse-store/11-push-and-add-opcode/+page.md +++ b/courses/formal-verification/1-horse-store/11-push-and-add-opcode/+page.md @@ -18,11 +18,11 @@ You may be asking **"How does data get added to the stack to begin with?"**. Thi We'll start by considering how to add a 1 byte object to the stack, for this we'd use the `PUSH1` op code. - +::image{src='/formal-verification-1/11-push-and-add-opcodes/push-and-add-opcodes-1.png' style='width: 75%; height: auto;'} If we were then to call the `ADD` op code, it's going to take the top 2 items on our stack, sum them and add this sum back to the top of our stack. - +::image{src='/formal-verification-1/11-push-and-add-opcodes/push-and-add-opcodes-2.png' style='width: 75%; height: auto;'} > **Remember:** Were there _three_ items in our stack, the `ADD` op code would only sum the top two items, the result would be pushed to the top of the stack, leaving the first item in our stack untouched. diff --git a/courses/formal-verification/1-horse-store/12-push-opcode/+page.md b/courses/formal-verification/1-horse-store/12-push-opcode/+page.md index 3eb636871..a8538c2f4 100644 --- a/courses/formal-verification/1-horse-store/12-push-opcode/+page.md +++ b/courses/formal-verification/1-horse-store/12-push-opcode/+page.md @@ -52,6 +52,6 @@ If you remember, our empty `MAIN` macro returned `60008060093d393df3` as the `Co Looking closely, we see `5F` appended to the end of our previous bytecode string. [**evm.codes**](https://www.evm.codes/?fork=shanghai) will show us that indeed, this hex represents PUSH0! - +::image{src='/formal-verification-1/12-push-opcode/push-opcode-1.png' style='width: 100%; height: auto;'} We did it! diff --git a/courses/formal-verification/1-horse-store/13-calldataload/+page.md b/courses/formal-verification/1-horse-store/13-calldataload/+page.md index ebf149d3e..b660aca25 100644 --- a/courses/formal-verification/1-horse-store/13-calldataload/+page.md +++ b/courses/formal-verification/1-horse-store/13-calldataload/+page.md @@ -14,7 +14,7 @@ Alright, eventually someone is going to send our contract some `calldata` and we Fortunately we have an op code just for this purpose! `calldataload` will allow us to load our `calldata` onto the stack. - +::image{src='/formal-verification-1/13-calldataload/calldataload-1.png' style='width: 100%; height: auto;'} So we can see the `calldataload` op code is going to take whatever is on top of our stack, and use it as the `bytes offset`. Since we want to capture our _whole_ function selector, we set this offset to 0 by using `PUSH0` as the operation prior to `calldataload`. diff --git a/courses/formal-verification/1-horse-store/14-shr/+page.md b/courses/formal-verification/1-horse-store/14-shr/+page.md index b6da35fe7..09984ef2b 100644 --- a/courses/formal-verification/1-horse-store/14-shr/+page.md +++ b/courses/formal-verification/1-horse-store/14-shr/+page.md @@ -20,7 +20,7 @@ In order to achieve this, there's an op code we can use! 😲 In [**evm.codes**](https://www.evm.codes/?fork=shanghai) search for `shr`. This stands for `Shift Right` and is precisely the tool we need. - +::image{src='/formal-verification-1/14-shr/shr-1.png' style='width: 100%; height: auto;'} In order to use this op code, we need 2 items on the stack diff --git a/courses/formal-verification/1-horse-store/15-evm-playground/+page.md b/courses/formal-verification/1-horse-store/15-evm-playground/+page.md index f81a45a61..4e775a426 100644 --- a/courses/formal-verification/1-horse-store/15-evm-playground/+page.md +++ b/courses/formal-verification/1-horse-store/15-evm-playground/+page.md @@ -18,21 +18,21 @@ Let's see how this works in the evm.codes playground! Recall what's required on our stack for our `shr` op code to function. `shr` takes `number of bits to shift` and the `32 bytes to shift`. These items need to be in our stack, in this order (top down). Our order of operations in the playground should look like this: - +::image{src='/formal-verification-1/15-evm-playground/evm-playground-1.png' style='width: 100%; height: auto;'} By clicking `Run` in the playground we can then step through each op code and see how it affects our **memory**, **stack**, **storage** and **return values**. Stepping through our first operation, we can see that 102 is added to our stack. - +::image{src='/formal-verification-1/15-evm-playground/evm-playground-2.png' style='width: 100%; height: auto;'} The second operation adds 4 to the top of our stack. - +::image{src='/formal-verification-1/15-evm-playground/evm-playground-3.png' style='width: 100%; height: auto;'} And our final step leaves us with `10` on our stack. - +::image{src='/formal-verification-1/15-evm-playground/evm-playground-4.png' style='width: 100%; height: auto;'} This is a hex value which we know we can convert using `cast --to-base 0x10 dec`. We indeed get 16! diff --git a/courses/formal-verification/1-horse-store/16-shr-calldata/+page.md b/courses/formal-verification/1-horse-store/16-shr-calldata/+page.md index 574e9150e..9e7a06757 100644 --- a/courses/formal-verification/1-horse-store/16-shr-calldata/+page.md +++ b/courses/formal-verification/1-horse-store/16-shr-calldata/+page.md @@ -39,7 +39,7 @@ With that determined, let's add what we need to our contract. And our stack should look like this: - +::image{src='/formal-verification-1/16-shr-calldata/shr-calldata-1.png' style='width: 100%; height: auto;'} Our final step to isolate the `function selector` from our received `call data` is going to be executing the `shr` operation, exciting! @@ -54,7 +54,7 @@ Our final step to isolate the `function selector` from our received `call data` The stack now: - +::image{src='/formal-verification-1/16-shr-calldata/shr-calldata-2.png' style='width: 100%; height: auto;'} This is great! We finally have our `function selector` on the stack and we can finally start to do some function dispatching! diff --git a/courses/formal-verification/1-horse-store/17-opcodes-recap/+page.md b/courses/formal-verification/1-horse-store/17-opcodes-recap/+page.md index 641b7454b..1538acc62 100644 --- a/courses/formal-verification/1-horse-store/17-opcodes-recap/+page.md +++ b/courses/formal-verification/1-horse-store/17-opcodes-recap/+page.md @@ -55,6 +55,6 @@ We can check how the byte code of our contract is doing so far with `huffc src/h We were also introduced to the evm.codes playground, wherein we can experiment with byte code and op code inputs to walk through each operation being executed. In the screenshot below, I've included the runtime byte code and we can see that it is indeed exactly what we've coded in our Huff contract! - +::image{src='/formal-verification-1/17-opcodes-recap/opcodes-recap-1.png' style='width: 100%; height: auto;'} In the next lesson we'll try our luck at function dispatching and routing the `call data` to where it needs to be. Let's go! diff --git a/courses/formal-verification/1-horse-store/19-opcode-eq/+page.md b/courses/formal-verification/1-horse-store/19-opcode-eq/+page.md index 764e9502b..5205f66a6 100644 --- a/courses/formal-verification/1-horse-store/19-opcode-eq/+page.md +++ b/courses/formal-verification/1-horse-store/19-opcode-eq/+page.md @@ -22,7 +22,7 @@ readNumberOfHorses = 0xe026c017 These selectors can be determines by running `cast sig "updateHorseNumber(uint256)"` and `cast sig readNumberOfHorses()` respectfully. Once we have these signatures, there's an op code we can use to check equality, the `EQ` op code! Let's review how it works in [**evm.codes**](https://www.evm.codes/?fork=shanghai) - +::image{src='/formal-verification-1/19-opcode-eq/opcode-eq-1.png' style='width: 75%; height: auto;'} As we can see, the `EQ` op code takes the top item on our stack and compares it to the next item in the stack. This op code will return `1` if the values compared are equal and `0` if they are not equal. diff --git a/courses/formal-verification/1-horse-store/20-jump-and-jumpi/+page.md b/courses/formal-verification/1-horse-store/20-jump-and-jumpi/+page.md index b889c22c8..3f5f66070 100644 --- a/courses/formal-verification/1-horse-store/20-jump-and-jumpi/+page.md +++ b/courses/formal-verification/1-horse-store/20-jump-and-jumpi/+page.md @@ -16,7 +16,7 @@ Our next step will be routing this call to the correct `Program Counter` for tha For our purposes, we're going to use `JUMPI` since we want to `JUMP` if our `function selector` comparison returns `True`. - +::image{src='/formal-verification-1/20-jump-and-jumpi/jump-and-jumpi-1.png' style='width: 75%; height: auto;'} We can see that `JUMPI` takes two stack inputs. The first is a `counter`, which is the new `program counter` our code execution will continue from. In our example, this is our function location, this is where we want our `call data` to be processed. The second input simply determines whether or not the `program counter` should be adjusted. If this value is 0, the current `program counter` is incremented, if the value is _anything else_ - the program counter will be set to whatever our first input is. diff --git a/courses/formal-verification/1-horse-store/21-jumpdest/+page.md b/courses/formal-verification/1-horse-store/21-jumpdest/+page.md index 6eb3c5203..1e2110fc0 100644 --- a/courses/formal-verification/1-horse-store/21-jumpdest/+page.md +++ b/courses/formal-verification/1-horse-store/21-jumpdest/+page.md @@ -15,7 +15,7 @@ huffc src/horseStoreV1/HorseStore.huff --bin-runtime Paste the returned runtime bytecode into the evm.codes playground and let's step through what's happening with our contract. - +::image{src='/formal-verification-1/21-jumpdest/jumpdest-1.png' style='width: 75%; height: auto;'} By adding the runtime bytecode to the playground, we're presented with almost exactly what we wrote in Huff, converted to pure op codes. Something that's going to stand out is the `JUMPDEST` code. To understand this op code, let's take one step back. @@ -34,7 +34,7 @@ So, looking again at our playground: - JUMPI - Jumps to our destination if the value returned by EQ is anything other than 0 - JUMPDEST - An empty stack at our jump destination - +::image{src='/formal-verification-1/21-jumpdest/jumpdest-2.png' style='width: 75%; height: auto;'} Currently of course, nothing is going to happen after we jump to our new destination. We haven't added any logic to our macro yet. I encourage you to experiment in the playground before moving on. diff --git a/courses/formal-verification/1-horse-store/22-dup1/+page.md b/courses/formal-verification/1-horse-store/22-dup1/+page.md index 05d44d0a7..edabf8e43 100644 --- a/courses/formal-verification/1-horse-store/22-dup1/+page.md +++ b/courses/formal-verification/1-horse-store/22-dup1/+page.md @@ -32,7 +32,7 @@ We could do another comparison, but if we haven't jumped anywhere, our stack is An op code is available to us which will help us achieve a cleaner and more gas efficient path to our goal, `DUP1` - +::image{src='/formal-verification-1/22-dup1/dup1-1.png' style='width: 75%; height: auto;'} `DUP1` simply takes the top item of the stack, copies it and adds both items back to the stack. By executing this op code directly after the first time we accessed the `call data`'s `function selector`, it would keep a copy of this selector for us to access, on the bottom of our stack. How does this look in our code? diff --git a/courses/formal-verification/1-horse-store/24-testing-jumpdest/+page.md b/courses/formal-verification/1-horse-store/24-testing-jumpdest/+page.md index 93f68239c..98425096a 100644 --- a/courses/formal-verification/1-horse-store/24-testing-jumpdest/+page.md +++ b/courses/formal-verification/1-horse-store/24-testing-jumpdest/+page.md @@ -17,14 +17,14 @@ huffc src/horseStoreV1/HorseStore.huff --bin-runtime Entering this bytecode into the evm.codes playground we should see our contract, broken down into it's individual op codes. Make note of the two distinct `JUMPDEST` codes at the bottom of the call list. - +::image{src='/formal-verification-1/24-testing-jumpdest/testing-jumpdest-1.png' style='width: 100%; height: auto;'} If we assure that the call data we're passing begins with the updateHorseNumber function selector, things should be business as usual, until reach our first `JUMPI` code. - +::image{src='/formal-verification-1/24-testing-jumpdest/testing-jumpdest-2.png' style='width: 50%; height: auto;'} We can see our first `JUMPI` code pertains to one of the JUMPDEST codes at the bottom of our list of operations. Because we're passing JUMPI this location, and a 1 (which represents a matching selector), we would expect our next step to jump our exectution to this location further in the code-bypassing the second check entirely. - +::image{src='/formal-verification-1/24-testing-jumpdest/testing-jumpdest-3.png' style='width: 50%; height: auto;'} And that's exactly what happens! Great work! 🎉I encourage you to experiment further before moving forward, try placing different contracts into the evm.codes playground and investigating how their op codes function and interact. The more experience you have with these concepts the better! diff --git a/courses/formal-verification/1-horse-store/25-revert/+page.md b/courses/formal-verification/1-horse-store/25-revert/+page.md index aaa5b2e31..23a91c20f 100644 --- a/courses/formal-verification/1-horse-store/25-revert/+page.md +++ b/courses/formal-verification/1-horse-store/25-revert/+page.md @@ -14,7 +14,7 @@ We could easily imagine a scenario where `call data` is sent to our contract, no We should protect against this by terminating the execution when no matches are found by our dispatcher. We can leverage the `REVERT` op code to do this. - +::image{src='/formal-verification-1/25-revert/revert-1.png' style='width: 50%; height: auto;'} The revert op code takes two stack inputs, the **byte offset** and **byte size**. Both of these are used to return data from memory (like an error code) when the REVERT operation is executed. We haven't dealt with memory and have no errors so we won't worry about this, we'll simply pass 0s. @@ -55,7 +55,7 @@ Our Huff contract with REVERT implemented should look something like this. Now, if we head back to the [**evm.codes playground**](https://www.evm.codes/playground) with our new runtime bytecode (`huffc src/horseStoreV1/HorseStore.huff --bin-runtime`), we can send some garbage `call data` and step through the operations to see how the contract responds. - +::image{src='/formal-verification-1/25-revert/revert-2.png' style='width: 50%; height: auto;'} It seems our revert works exactly as intended! Our revert code will terminate the execution if hit and return an error! diff --git a/courses/formal-verification/1-horse-store/28-sstore/+page.md b/courses/formal-verification/1-horse-store/28-sstore/+page.md index e943883a6..036fb2882 100644 --- a/courses/formal-verification/1-horse-store/28-sstore/+page.md +++ b/courses/formal-verification/1-horse-store/28-sstore/+page.md @@ -18,7 +18,7 @@ _Follow along with this video:_ These are the steps, determined in our previous lesson, that we need to take in order to add the necessary logic to our Huff macro. For our first step, we're going to leverage the `SSTORE` op code. - +::image{src='/formal-verification-1/28-sstore/sstore-1.png' style='width: 50%; height: auto;'} The stack inputs required are a **key** and a **value** diff --git a/courses/formal-verification/1-horse-store/29-free-storage-pointer/+page.md b/courses/formal-verification/1-horse-store/29-free-storage-pointer/+page.md index 5d9dd3d85..adb1468fb 100644 --- a/courses/formal-verification/1-horse-store/29-free-storage-pointer/+page.md +++ b/courses/formal-verification/1-horse-store/29-free-storage-pointer/+page.md @@ -44,6 +44,6 @@ What the line `#define constant NUMBER_OF_HORSES_STORAGE_SLOT = FREE_STORAGE_POI This is exactly how Solidity handles things as well! - +::image{src='/formal-verification-1/29-free-storage-pointer/free-storage-pointer-1.png' style='width: 50%; height: auto;'} With our above implementation, any time somebody looks at storage slot 0, that's where numberOfHorses will be found. Great work! diff --git a/courses/formal-verification/1-horse-store/32-testing-our-macro-in-evm-codes/+page.md b/courses/formal-verification/1-horse-store/32-testing-our-macro-in-evm-codes/+page.md index a50197f9e..290166968 100644 --- a/courses/formal-verification/1-horse-store/32-testing-our-macro-in-evm-codes/+page.md +++ b/courses/formal-verification/1-horse-store/32-testing-our-macro-in-evm-codes/+page.md @@ -18,7 +18,7 @@ Here's an example of valid call data to set our number of horses to 7 and the ca 0xcdfead2e0000000000000000000000000000000000000000000000000000000000000007 ``` - +::image{src='/formal-verification-1/32-testing-macro-evm-codes/testing-macro-evm-codes-1.png' style='width: 100%; height: auto;'} --- diff --git a/courses/formal-verification/1-horse-store/33-sload-mstore-return/+page.md b/courses/formal-verification/1-horse-store/33-sload-mstore-return/+page.md index 611b42bc4..8775b8df7 100644 --- a/courses/formal-verification/1-horse-store/33-sload-mstore-return/+page.md +++ b/courses/formal-verification/1-horse-store/33-sload-mstore-return/+page.md @@ -14,7 +14,7 @@ Having got this far the next steps we take should be pretty straight forward. Le There are 3 op codes we'll be working with do accomplish this. The first is `SLOAD` - +::image{src='/formal-verification-1/33-sload-mstore-return/sload-mstore-return-1.png' style='width: 100%; height: auto;'} Our SLOAD op code takes a single stack input - our key which points to the location of the storage slot we want to load from. This is a very gas intensive operation, be mindful! @@ -22,13 +22,13 @@ The output we expect from this op code is of course - the value stored at that l The other op code we'll be looking at, is the `return` op code. - +::image{src='/formal-verification-1/33-sload-mstore-return/sload-mstore-return-2.png' style='width: 100%; height: auto;'} `return` functions very similarly to `stop` in that is halts execution of our code. The difference here is that `return` is going to return requested data when it's executed. We can see in the above screenshot that `return` takes 2 stack inputs that allow us to define what data we want returned `offset` and `size`. It's important to notes, however, that the `return` op code returns data from _memory_, not storage. As an extra step, we need to load our data into memory first. To do this we'll be leveraging the `mstore` op code. - +::image{src='/formal-verification-1/33-sload-mstore-return/sload-mstore-return-3.png' style='width: 100%; height: auto;'} `mstore` functions just like `sstore`, which we used previously, except this op code is going to push to memory, no storage. diff --git a/courses/formal-verification/1-horse-store/35-testing-in-evm-codes/+page.md b/courses/formal-verification/1-horse-store/35-testing-in-evm-codes/+page.md index 2edfcbe8e..c8f5e03a0 100644 --- a/courses/formal-verification/1-horse-store/35-testing-in-evm-codes/+page.md +++ b/courses/formal-verification/1-horse-store/35-testing-in-evm-codes/+page.md @@ -24,7 +24,7 @@ Click run and step through the execution of this function call. The first steps Your contract state should look something like this if you've stepped through the `updateNumberOfHorses` execution (using 7 as a passed parameter): - +::image{src='/formal-verification-1/35-testing-in-evm-codes/testing-in-evm-codes1.png' style='width: 100%; height: auto;'} Let's step through the execution further and see how things perform. Looking at the op codes added to our contract following our `readNumberOfHorses()` jump destination (`JUMPDEST`) we should see: @@ -36,6 +36,6 @@ Let's step through the execution further and see how things perform. Looking at 6. PUSH0 - bytes offset of data accessed from memory, or where to begin reading from the data string - again in our case this is zero to capture everything 7. RETURN - returns the data from memory beginning at the offset on the top of the stack, the size of this data string is determined by our second item in the stack (32 bytes) - +::image{src='/formal-verification-1/35-testing-in-evm-codes/testing-in-evm-codes2.png' style='width: 100%; height: auto;'} That's really all there is to it! Breaking down the operations of a contract step by step makes each execution clear and easy to understand! diff --git a/courses/formal-verification/1-horse-store/39-foundry-opcode-debugger/+page.md b/courses/formal-verification/1-horse-store/39-foundry-opcode-debugger/+page.md index daff149ad..79e84df18 100644 --- a/courses/formal-verification/1-horse-store/39-foundry-opcode-debugger/+page.md +++ b/courses/formal-verification/1-horse-store/39-foundry-opcode-debugger/+page.md @@ -67,6 +67,6 @@ cast to-base 777 hex With the above, we can see that 0x309 should be expected to pop up as we walk through the op code execution in our debugger, and indeed it does. - +::image{src='/formal-verification-1/39-foundry-opcode-debugger/foundry-opcode-debugger1.png' style='width: 100%; height: auto;'} I can't encourage you enough to practice and experiment with this debugger. Being able to read through op code executions will be an invaluable low level skill for those serious about a deep understanding of the EVM. diff --git a/courses/formal-verification/1-horse-store/4-function-dispatching/+page.md b/courses/formal-verification/1-horse-store/4-function-dispatching/+page.md index ed34edc66..f2ea9d588 100644 --- a/courses/formal-verification/1-horse-store/4-function-dispatching/+page.md +++ b/courses/formal-verification/1-horse-store/4-function-dispatching/+page.md @@ -31,7 +31,7 @@ contract HorseStore { By deploying this contract in remix and calling the `updateNumberOfHorses()` function, we're provided an output that looks like this: - +::image{src='/formal-verification-1/4-function-dispatching/function-dispatching-1.png' style='width: 75%; height: auto;'} We're most interested in the `input` data. @@ -73,7 +73,7 @@ Behind the scenes, Solidity has a **function dispatcher** that matches the selec However, if writing in a lower-level language like Huff, you have to manually set up the dispatcher yourself to connect call data to functions. This gives more control but requires extra work. - +::image{src='/formal-verification-1/4-function-dispatching/function-dispatching-2.png' style='width: 75%; height: auto;'} ## Putting It Together diff --git a/courses/formal-verification/1-horse-store/42-solidity-opcodes-from-bytecode/+page.md b/courses/formal-verification/1-horse-store/42-solidity-opcodes-from-bytecode/+page.md index b85528cbc..704335322 100644 --- a/courses/formal-verification/1-horse-store/42-solidity-opcodes-from-bytecode/+page.md +++ b/courses/formal-verification/1-horse-store/42-solidity-opcodes-from-bytecode/+page.md @@ -14,7 +14,7 @@ Start by creating a new folder named `breakdowns`, we'll be compiling our op cod Navigate to `out/horseStoreV1/HorseStore.sol/HorseStore.json`. This is the ABI of our deployed HorseStore contract. Within this file you'll find a `bytecode` object with an `object` property. This represents our op codes for the contract in bytecode! - +::image{src='/formal-verification-1/42-solidity-opcodes-from-bytecode/solidity-opcodes-from-bytecode1.png' style='width: 100%; height: auto;'} Create a new file named `solc-breakdowns.c++`. This won't actually be a C++ file, but some of the syntax highlighting for C++ will make things easier on us. You can paste your bytecode to the top of this file as a comment for reference. diff --git a/courses/formal-verification/1-horse-store/44-msg.value-check/+page.md b/courses/formal-verification/1-horse-store/44-msg.value-check/+page.md index 0d082b553..10776a98f 100644 --- a/courses/formal-verification/1-horse-store/44-msg.value-check/+page.md +++ b/courses/formal-verification/1-horse-store/44-msg.value-check/+page.md @@ -164,11 +164,11 @@ I've added our comment notation to our op code list to keep track of what's on o We've seen many of these op codes before, but some of them are new. Each time a new op code is mentioned, I'll include a screenshot of it's details from [evm.codes](https://www.evm.codes/#34?fork=cancun). I encourage you to use this website like a dictionary as we attempt to define what's being executed. `CALLVALUE` adds to the stack, the value included with the current call. - +::image{src='/formal-verification-1/44-msg.value-check/msg_value-check1.png' style='width: 100%; height: auto;'} We then duplicate this value with `DUP1` and check if it's equal to zero with the conditional `ISZERO` - +::image{src='/formal-verification-1/44-msg.value-check/msg_value-check2.png' style='width: 100%; height: auto;'} Next it looks like we're setting up the executions response to this `ISZERO` conditional. We push a program counter/jump destination to the stack with PUSH1 0x0e then execute JUMPI. @@ -184,7 +184,7 @@ REVERT It's going to revert! You can even see this in our foundry script if you try to pass a value to our contract creation, in our test. It won't even let us compile! - +::image{src='/44-msg.value-check/msg_value-check3.png' style='width: 100%; height: auto;'} ### Summary diff --git a/courses/formal-verification/1-horse-store/45-CODECOPY/+page.md b/courses/formal-verification/1-horse-store/45-CODECOPY/+page.md index c764bf372..75657c7ba 100644 --- a/courses/formal-verification/1-horse-store/45-CODECOPY/+page.md +++ b/courses/formal-verification/1-horse-store/45-CODECOPY/+page.md @@ -166,7 +166,7 @@ The first several op codes are examples of things we've seen before. Without get This continues until we reach the `CODECOPY` operation. We'd mentioned it briefly before, but now we're going to see it in action. Let see what it does. - +::image{src='/formal-verification-1/45-CODECOPY/CODECOPY1.png' style='width: 100%; height: auto;'} Now, what's this doing in the context of our contract? @@ -208,7 +208,7 @@ INVALID // [] After copying our `runtime code` to memory we `PUSH0` and call `RETURN`. - +::image{src='/formal-verification-1/45-CODECOPY/CODECOPY2.png' style='width: 100%; height: auto;'} `RETURN` takes a bytes offset and a size to be returned from memory. We're passing `0x00` and `0xa5` which represents the whole size of the `runtime code` we've copied into memory. This is what our transaction is returning and is being copied to the blockchain! diff --git a/courses/formal-verification/1-horse-store/48-function-selector-size-check/+page.md b/courses/formal-verification/1-horse-store/48-function-selector-size-check/+page.md index 834a702bf..a28e45899 100644 --- a/courses/formal-verification/1-horse-store/48-function-selector-size-check/+page.md +++ b/courses/formal-verification/1-horse-store/48-function-selector-size-check/+page.md @@ -163,7 +163,7 @@ LT // Continuing from our `JUMPDEST` (this is if `msg.value == 0`), we are then clearing our stack with `POP`. Next we push `0x04` to our stack with `PUSH1`, we'll see why soon. Then we execute an op code we haven't seen before. `CALLDATASIZE`. - +::image{src='/formal-verification-1/48-function-selector-size-check/function-selector-size-check1.png' style='width: 100%; height: auto;'} We can see that this op code takes no stack input, but the stack output is the `byte size of the call data`. A couple examples: @@ -177,7 +177,7 @@ if call data = 0x05284a06 We then hit another new op code `LT` this stands for `less than`. - +::image{src='/formal-verification-1/48-function-selector-size-check/function-selector-size-check2.png' style='width: 100%; height: auto;'} The LT op code will return 1 if the top item of the stack is less than the second from top item in the stack. Functionally this is checking the the call data being received is long enough to satisfy the required length of a contracts function selector (4 bytes). diff --git a/courses/formal-verification/1-horse-store/50-setting-up-jumpdest-program-counters/+page.md b/courses/formal-verification/1-horse-store/50-setting-up-jumpdest-program-counters/+page.md index 767a58a52..4e08dd2a8 100644 --- a/courses/formal-verification/1-horse-store/50-setting-up-jumpdest-program-counters/+page.md +++ b/courses/formal-verification/1-horse-store/50-setting-up-jumpdest-program-counters/+page.md @@ -170,7 +170,7 @@ JUMP // [0x04, calldata_size, 0x3f, 0x43, func_selector]] Now, this looks like we're pushing a bunch of random things to the stack and then calling a `JUMP`. That's kind of true, but things will seem less random soon, haha! Let's look at JUMP since we've never seen it used before. - +::image{src='/formal-verification-1/50-setting-up-jumpdest-program-counters/setting-up-jumpdest-program-counters1.png' style='width: 100%; height: auto;'} `JUMP` takes the top item of our stack and continues our execution from that bytes offset location. This chunk of code represents our `updateHorseNumber jump dest 1`. When we reach the `JUMP` operation on this chunk. diff --git a/courses/formal-verification/1-horse-store/51-checking-if-call-data-is-big-enough-to-contain-a-uint256/+page.md b/courses/formal-verification/1-horse-store/51-checking-if-call-data-is-big-enough-to-contain-a-uint256/+page.md index 4388641b0..325bf8180 100644 --- a/courses/formal-verification/1-horse-store/51-checking-if-call-data-is-big-enough-to-contain-a-uint256/+page.md +++ b/courses/formal-verification/1-horse-store/51-checking-if-call-data-is-big-enough-to-contain-a-uint256/+page.md @@ -176,11 +176,11 @@ JUMPI // [0x00, 0x04, calldata_size, 0x3f, 0x43, func_selector] Our stack is getting a little crazy, but each step should be pretty clear to us, we `PUSH0`, then `PUSH1 0x20` (0x20 == 32, this is important to know!) Then we reach DUP3, which we can speculate about, but haven't actually seen. Here's what [evm.codes](https://www.evm.codes/#34?fork=cancun) has to say: - +::image{src='/formal-verification-1/51-Checking-If-Call-Data-Is-Big-Enough-To-Contain-A-Uint256/checking-call-data-size1.png' style='width: 100%; height: auto;'} In essence the DUP3 op code is taking the third item from the top of the stack and duplicating it, adding this copy to the top of the stack. Given this, what do you think DUP5 does? - +::image{src='/51-Checking-If-Call-Data-Is-Big-Enough-To-Contain-A-Uint256/checking-call-data-size2.png' style='width: 100%; height: auto;'} Shocking, I know. @@ -188,11 +188,11 @@ Next we see two more op codes we've not come across yet `SUB` and `SLT` `SUB` is quite simply - subtraction. It's going to take the top item of our stack and subtract from it the second from top item in our stack. - +::image{src='/51-Checking-If-Call-Data-Is-Big-Enough-To-Contain-A-Uint256/checking-call-data-size3.png' style='width: 100%; height: auto;'} `SLT` is `signed less than` and compares two signed integer values, returning 1 if the top item of our stack is less than the second from top item in our stack and returning 0 otherwise. - +::image{src='/51-Checking-If-Call-Data-Is-Big-Enough-To-Contain-A-Uint256/checking-call-data-size4.png' style='width: 100%; height: auto;'} We can see the steps, but what are these operations actually doing? diff --git a/courses/formal-verification/1-horse-store/52-sstoreing-our-value/+page.md b/courses/formal-verification/1-horse-store/52-sstoreing-our-value/+page.md index 1144ab73e..4b8f0b8d2 100644 --- a/courses/formal-verification/1-horse-store/52-sstoreing-our-value/+page.md +++ b/courses/formal-verification/1-horse-store/52-sstoreing-our-value/+page.md @@ -178,7 +178,7 @@ In our example, we're providing `0x04` (the size of our `function selector`) and Now we see a new op code, `SWAP2`! - +::image{src='/formal-verification-1/52-sstoreing-our-value/sstoreing-our-value1.png' style='width: 100%; height: auto;'} `SWAP2` simply exchanges the position with the top item of our stack, with the 3rd item from the top of our stack. You can imagine it like swapping our top item, with the second index from the top. diff --git a/courses/formal-verification/1-horse-store/6-huff-syntax-highlighting/+page.md b/courses/formal-verification/1-horse-store/6-huff-syntax-highlighting/+page.md index 9dac680d4..8b2aa3df4 100644 --- a/courses/formal-verification/1-horse-store/6-huff-syntax-highlighting/+page.md +++ b/courses/formal-verification/1-horse-store/6-huff-syntax-highlighting/+page.md @@ -17,7 +17,7 @@ If you'd like to take advantage of the syntax highlighting you see me using for - Click **Install**. ``` - +::image{src='/formal-verification-1/6-huff-syntax-highlighting/huff-syntax-highlighting-1.png' style='width: 75%; height: auto;'} This should add a much needed level of clarity while developing in Huff, and as Patrick says: "It'll give you these nice little highlightings." diff --git a/courses/formal-verification/1-horse-store/61-pure-yul/+page.md b/courses/formal-verification/1-horse-store/61-pure-yul/+page.md index 3c21cbb06..b24c6046d 100644 --- a/courses/formal-verification/1-horse-store/61-pure-yul/+page.md +++ b/courses/formal-verification/1-horse-store/61-pure-yul/+page.md @@ -41,7 +41,7 @@ object "HorseStoreYul" { Datacopy, dataoffset and datasize might seem unfamiliar, but these are function within Yul which assist it to access disparate parts of a Yul Object. - +::image{src='/formal-verification-1/61-pure-yul/pure-yul1.png' style='width: 100%; height: auto;'} We can see that in our circunstances the `datacopy` function is being used as equivalent to the `codecopy` op code, and what does `codecopy` do? Well, it's taking the size and offset of our `runtime code` (we haven't written the `runtime` yet!) and returning it to be copied to the blockchain! @@ -74,7 +74,7 @@ solc --strict-assembly --optimize --optimize-runs 20000 yul/HorseStoreYul.yul -- This is going to return the binary of our contract after compilation! - +::image{src='/formal-verification-1/61-pure-yul/pure-yul2.png' style='width: 100%; height: auto;'} ### Runtime Code diff --git a/courses/formal-verification/1-horse-store/64-feedHorse-macro/+page.md b/courses/formal-verification/1-horse-store/64-feedHorse-macro/+page.md index 4878f75ad..1b082b279 100644 --- a/courses/formal-verification/1-horse-store/64-feedHorse-macro/+page.md +++ b/courses/formal-verification/1-horse-store/64-feedHorse-macro/+page.md @@ -59,7 +59,7 @@ This is going to one of the most important functions we learn about in this sect We're going to need to access the block's timestamp, fortunately there's an op code that does just this `TIMESTAMP`. - +::image{src='/formal-verification-1/64-feedhorse-macro/feedhorse_macro1.png' style='width: 100%; height: auto;'} So, our function takes a `uint256 horseId`, we're going to want to add this to our stack as well. We know how to do this with the op code `calldataload`, which takes an offset, then adds 32 bytes of call data from the offset to our stack. diff --git a/courses/formal-verification/1-horse-store/65-mappings-and-arrays-in-evm-huff/+page.md b/courses/formal-verification/1-horse-store/65-mappings-and-arrays-in-evm-huff/+page.md index 97a8287d7..eac25fbc4 100644 --- a/courses/formal-verification/1-horse-store/65-mappings-and-arrays-in-evm-huff/+page.md +++ b/courses/formal-verification/1-horse-store/65-mappings-and-arrays-in-evm-huff/+page.md @@ -52,7 +52,7 @@ I'll keep a running reminder of our current total contract state at the top of e Now's a good time to go back and reference the Solidity Documentation. We should be reminded that mappings are handled using a very specific algorithm. - +::image{src='/formal-verification-1/65-mappings-and-arrays-in-evm-huff/mappings-and-arrays-in-evm-huff1.png' style='width: 100%; height: auto;'} We cover this in a little more detail in the Foundry Full Course on Updraft, so I won't go over it here, but lets look at how we accomplish this in Huff. diff --git a/courses/formal-verification/1-horse-store/68-a-quick-function-then-huffmate/+page.md b/courses/formal-verification/1-horse-store/68-a-quick-function-then-huffmate/+page.md index dc45e148f..48ab2cb97 100644 --- a/courses/formal-verification/1-horse-store/68-a-quick-function-then-huffmate/+page.md +++ b/courses/formal-verification/1-horse-store/68-a-quick-function-then-huffmate/+page.md @@ -859,7 +859,7 @@ We can finally begin defining our `MINT_HORSE()` macro: Next we'll need to access the msg.sender, as that's to whom the token is being minted. Fortunately we've an op code specifically to reference the `CALLER`. - +::image{src='/formal-verification-1/68-a-quick-function-then-huffmate/a-quick-function-then-huffmate1.png' style='width: 100%; height: auto;'} This op code will add the 20 byte address of the callers account to the top of our stack! diff --git a/courses/formal-verification/1-horse-store/70-huff-yul-and-solidity-gas-comparisons/+page.md b/courses/formal-verification/1-horse-store/70-huff-yul-and-solidity-gas-comparisons/+page.md index b4a47b92e..117ae2eed 100644 --- a/courses/formal-verification/1-horse-store/70-huff-yul-and-solidity-gas-comparisons/+page.md +++ b/courses/formal-verification/1-horse-store/70-huff-yul-and-solidity-gas-comparisons/+page.md @@ -15,7 +15,7 @@ forge snapshot This is going to run all of our tests, but keep a record of gas costs in a local file named `.gas-snapshot`. - +::image{src='/70-huff-yul-and-solidity-gas-comparison/huff-yul-and-solidity-gas-comparison1.png' style='width: 100%; height: auto;'} This output file lets us compare the gas costs of each test performed for each of our implementations. We can see things like: diff --git a/courses/formal-verification/1-horse-store/9-evm-the-stack/+page.md b/courses/formal-verification/1-horse-store/9-evm-the-stack/+page.md index 422f8d6fb..189601ea0 100644 --- a/courses/formal-verification/1-horse-store/9-evm-the-stack/+page.md +++ b/courses/formal-verification/1-horse-store/9-evm-the-stack/+page.md @@ -49,7 +49,7 @@ As developers, we usually let Solidity handle these complexities automatically. Let's examine this brilliant visual that prominent developer Pascal shared on Twitter: - +::image{src='/formal-verification-1/9-evm-the-stack/evm-the-stack-1.png' style='width: 75%; height: auto;'} For our purposes, our focus will be the areas **Stack**, **Memory**, and **Storage**. @@ -59,10 +59,10 @@ When we want to perform computions on data, we have to consider where the data w If we take a look at the `ADD` op code in [**evm.codes**](https://www.evm.codes/?fork=shanghai) we can see how this addition operation actually works. - +::image{src='/formal-verification-1/9-evm-the-stack/evm-the-stack-2.png' style='width: 75%; height: auto;'} `The stack` can quite literally be thought of as a stack. New operations must be placed on the top of the stack and processed or reallocated before operations lower in the stack can be performed. Hopefully this image will help clarifying the concept. - +::image{src='/formal-verification-1/9-evm-the-stack/evm-the-stack-3.png' style='width: 75%; height: auto;'} In the next lesson, we'll take a look at how `memory` and `storage` is handled differently from eachother. diff --git a/courses/formal-verification/3-gasbad/02-setup/+page.md b/courses/formal-verification/3-gasbad/02-setup/+page.md index e6eee56f6..8347cba0e 100644 --- a/courses/formal-verification/3-gasbad/02-setup/+page.md +++ b/courses/formal-verification/3-gasbad/02-setup/+page.md @@ -30,7 +30,7 @@ Your first step, once the repo is cloned locally, is to delete the `certora` fol Let's crack open the README to verify the setup steps recommended by the protocol. - +::image{src='/formal-verification-3/2-setup//setup1.png' style='width: 100%; height: auto;'} The protocol wants us to use the `make` command! This should largely set our workspace up for us by removing old modules, installing our dependences and building the project. @@ -39,7 +39,7 @@ The protocol wants us to use the `make` command! This should largely set our wor Lastly, before diving into the code base itself, we should see how the protocol's existing test suite looks. It seems they currently have BaseTest.t.sol which they are leveraging to compare their code bases and gas currently. Let's run it! - +::image{src='/formal-verification-3/2-setup//setup2.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/formal-verification/3-gasbad/03-emitting-logs-with-assembly-log-4/+page.md b/courses/formal-verification/3-gasbad/03-emitting-logs-with-assembly-log-4/+page.md index a9f8a5950..557107abb 100644 --- a/courses/formal-verification/3-gasbad/03-emitting-logs-with-assembly-log-4/+page.md +++ b/courses/formal-verification/3-gasbad/03-emitting-logs-with-assembly-log-4/+page.md @@ -48,7 +48,7 @@ function listItem(address nftAddress, uint256 tokenId, uint256 price) external { In the listItem function, our Assembly block is being used to emit an event, or emit logs. We can see how Log4 works through our handy reference in [**evm.codes**](https://www.evm.codes/). - +::image{src='/formal-verification-3/3-emitting-logs-with-assembly/emitting-logs-with-assembly-log-41.png' style='width: 100%; height: auto;'} This op code will append our log record with 4 topics and takes 6 stack input elements. Let's consider how this is being used in our contract. diff --git a/courses/formal-verification/3-gasbad/05-verifying-nftmock/+page.md b/courses/formal-verification/3-gasbad/05-verifying-nftmock/+page.md index 18a875ceb..0a5722d50 100644 --- a/courses/formal-verification/3-gasbad/05-verifying-nftmock/+page.md +++ b/courses/formal-verification/3-gasbad/05-verifying-nftmock/+page.md @@ -57,7 +57,7 @@ certoraRun ./certora/conf/NftMock.conf Running the above on our current spec is going to of course pass. The proof is a tautology and our spec isn't testing anything meaningful. - +::image{src='/formal-verification-3/5-verifying-nftmock/verifying-nftmock1.png' style='width: 100%; height: auto;'} `Certora` refers to situations like this as `vacuous` and we can actually add a check into our `conf` file to validate this sort of thing. @@ -75,7 +75,7 @@ Running the above on our current spec is going to of course pass. The proof is a By adding this flag and running the prover again... - +::image{src='/formal-verification-3/5-verifying-nftmock/verifying-nftmock2.png' style='width: 100%; height: auto;'} We should see an output like this in our terminal indicating that our spec is violating `vacuity`. diff --git a/courses/formal-verification/3-gasbad/06-total-supply-greater-than-0/+page.md b/courses/formal-verification/3-gasbad/06-total-supply-greater-than-0/+page.md index e9316e499..f60bca295 100644 --- a/courses/formal-verification/3-gasbad/06-total-supply-greater-than-0/+page.md +++ b/courses/formal-verification/3-gasbad/06-total-supply-greater-than-0/+page.md @@ -65,11 +65,11 @@ You may receive an error at this point when you run the prover as it may detect By sidestepping this loop being detected we should see things pass... - +::image{src='/formal-verification-3/6-total-supply-greater-than-0/total-supply-greater-than-01.png' style='width: 100%; height: auto;'} ...and our job passes without errors! Although, if we turn our `rule_sanity` back to `basic`, what do you think is going to happen? - +::image{src='/formal-verification-3/6-total-supply-greater-than-0/total-supply-greater-than-02.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/formal-verification/3-gasbad/07-proving-minting-nfts/+page.md b/courses/formal-verification/3-gasbad/07-proving-minting-nfts/+page.md index 46be37e86..6b4e9f14e 100644 --- a/courses/formal-verification/3-gasbad/07-proving-minting-nfts/+page.md +++ b/courses/formal-verification/3-gasbad/07-proving-minting-nfts/+page.md @@ -123,6 +123,6 @@ certoraRun ./certora/conf/NftMock.conf And, after a brief wait for the server/process, we should see a successfully verification. - +::image{src='/formal-verification-3/7-proving-minting-nfts/proving-minting-nfts1.png' style='width: 100%; height: auto;'} Good job! diff --git a/courses/formal-verification/3-gasbad/08-parametric-rules/+page.md b/courses/formal-verification/3-gasbad/08-parametric-rules/+page.md index e2724982c..50b98c0ed 100644 --- a/courses/formal-verification/3-gasbad/08-parametric-rules/+page.md +++ b/courses/formal-verification/3-gasbad/08-parametric-rules/+page.md @@ -51,7 +51,7 @@ Again, we expect this to fail, since the contract contains a mint function, but certoraRun ./certora/conf/NftMock.conf ``` - +::image{src='/formal-verification-3/8-parametric-rules/parametric-rules1.png' style='width: 100%; height: auto;'} As expected, we can see this fails, but by taking a closer look we can ascertain why and better understand the conclusion the prover came to. Intuitively we know that the `totalSupply` of `NftMock` is going to increase with `mint` function calls, but perhaps less intuitively the prover has pointed out that callback functions through `onERC721Received` can recursively call the `mint` function _also_ calling this assertion to fail! diff --git a/courses/formal-verification/3-gasbad/11-ghost-and-hooks/+page.md b/courses/formal-verification/3-gasbad/11-ghost-and-hooks/+page.md index 199a91158..a10d92064 100644 --- a/courses/formal-verification/3-gasbad/11-ghost-and-hooks/+page.md +++ b/courses/formal-verification/3-gasbad/11-ghost-and-hooks/+page.md @@ -29,7 +29,7 @@ This may be a little tricky, but it's entirely possible. However, we first need ### Ghost Variables - +::image{src='/formal-verification-3/11-ghosts-and-hooks/ghosts-and-hooks1.png' style='width: 100%; height: auto;'} At their core, `Ghost Variables` are variable which are declared specifically to be used by `Certora`. Declaring them this way allows them to act as extentions to contract state and their values will mimic the behaviour of contract storage (reverting and resetting when appropriate). @@ -47,7 +47,7 @@ ghost mathint listingUpdatesCount; ### Hooks - +::image{src='/formal-verification-3/11-ghosts-and-hooks/ghosts-and-hooks2.png' style='width: 100%; height: auto;'} [**`Hooks`**](https://docs.certora.com/en/latest/docs/cvl/hooks.html) allow us to specify an operation which triggers the `hook` and logic that is executed when the `hook` is triggered. For example - any time a particular variable is invoked with `SLOAD` - a `hook` could trigger something to happen. @@ -67,7 +67,7 @@ hook Sstore s_listings[KEY address nftAddress][KEY uint256 tokenId].price uint25 Ok, so our hook looks kinda gross, let me break it down. - +::image{src='/formal-verification-3/11-ghosts-and-hooks/ghosts-and-hooks3.png' style='width: 100%; height: auto;'} > ❗ **NOTE** > The STORAGE keyword was made unnecessarily in later updates to Certora diff --git a/courses/formal-verification/3-gasbad/12-init-state-and-axions/+page.md b/courses/formal-verification/3-gasbad/12-init-state-and-axions/+page.md index aa0726390..30738135d 100644 --- a/courses/formal-verification/3-gasbad/12-init-state-and-axions/+page.md +++ b/courses/formal-verification/3-gasbad/12-init-state-and-axions/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ Welcome back! We're almost ready to start verifying this protocol, but we've left out an important component. As things stand in `GasBad.spec` currently, nothing will work. Unlike Solidity, the ghost variables we've declared aren't initialized with any value. The `Certora Verification Language (CVL)` requires [**Initial State Axioms**](https://docs.certora.com/en/latest/docs/cvl/ghosts.html#initial-state-axioms) in order to assign starting points to our `listingUpdatesCount` and `log4Count` variables. - +::image{src='/formal-verification-3/12-init-state-and-axioms/init-state-and-axioms1.png' style='width: 100%; height: auto;'} --- diff --git a/courses/formal-verification/3-gasbad/13-analyzing-certora-errors/+page.md b/courses/formal-verification/3-gasbad/13-analyzing-certora-errors/+page.md index 3a043bb06..8fd30438f 100644 --- a/courses/formal-verification/3-gasbad/13-analyzing-certora-errors/+page.md +++ b/courses/formal-verification/3-gasbad/13-analyzing-certora-errors/+page.md @@ -14,13 +14,13 @@ Let's jump right in and start by running the prover. certoraRun ./certora/conf/GasBad.conf ``` - +::image{src='/formal-verification-3/13-analyzing-certora-errors/analyzing-certora-errors1.png' style='width: 100%; height: auto;'} Running our test results in a bunch of errors in every single contract we're proving! We'll need to dive a little deeper to determine if this is due to our invariant being broken. If we're updating storage without emitting an event, this is the kind of result we'd expect to see. Drill into the error for GasBadNftMarketplace for a better idea of what's happening here. - +::image{src='/formal-verification-3/13-analyzing-certora-errors/analyzing-certora-errors2.png' style='width: 100%; height: auto;'} Well, what's going on here? We initialized our ghost variables with a value, so what gives? @@ -33,11 +33,11 @@ In our particular circumstance, `Certora` had the variable start at 0, but was u Here's the thing: even if these values were initialized at `-1`, we wouldn't really expect this to be an issue, from the perspective of our invariant... let's look a bit deeper in the call trace to determine exactly what happened. - +::image{src='/formal-verification-3/13-analyzing-certora-errors/analyzing-certora-errors3.png' style='width: 100%; height: auto;'} We can see the calls in our trace which resulted in our hooks being invoked, we can also see the function call which triggered our HAVOC, let's look a little closer at that. - +::image{src='/formal-verification-3/13-analyzing-certora-errors/analyzing-certora-errors4.png' style='width: 100%; height: auto;'} Effectively what `Certora` is doing here is recognizing that an external call is being made to an unresolved `callee`. The prover takes this to mean that any response to this call could potentially call any function in our `GasBadNftMarketplace`. As a result of this uncertainty, `Certora` will randomize our variables (ghosts included) to account for potential state changes from the external call. diff --git a/courses/formal-verification/3-gasbad/14-persistent-ghosts/+page.md b/courses/formal-verification/3-gasbad/14-persistent-ghosts/+page.md index 42d28fd3b..0010906f9 100644 --- a/courses/formal-verification/3-gasbad/14-persistent-ghosts/+page.md +++ b/courses/formal-verification/3-gasbad/14-persistent-ghosts/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ So where does our `Certora` situation leave us? The call trace we were provided actually provides a clue we can use to side step this issue of our ghost variables being HAVOC'd. - +::image{src='/formal-verification-3/14-persistent-ghosts/persistent-ghosts1.png' style='width: 100%; height: auto;'} It can be seen, within the function call which resulted in our HAVOC, a flag which indicates: diff --git a/courses/formal-verification/3-gasbad/15-method-summaries-introduction/+page.md b/courses/formal-verification/3-gasbad/15-method-summaries-introduction/+page.md index 4c976e965..19fa86c6c 100644 --- a/courses/formal-verification/3-gasbad/15-method-summaries-introduction/+page.md +++ b/courses/formal-verification/3-gasbad/15-method-summaries-introduction/+page.md @@ -12,7 +12,7 @@ We've learnt that the `persistent` keyword could be leveraged in our situation t This is where `methods summaries` come into play. We've previously touched on the `methods block`, but we haven't really demonstrated how important this section can be. - +::image{src='/formal-verification-3/15-method-summaries-introduction/methods-summaries-introduction1.png' style='width: 100%; height: auto;'} As detailed in the [**Certora Docs**](https://docs.certora.com/en/latest/docs/cvl/methods.html), declarations in the methods block come in two flavours, `non-summary declarations` (these are like the ones we made in our `NftMock.conf`) and `summary declarations`. diff --git a/courses/formal-verification/3-gasbad/16-method-entries-introduction/+page.md b/courses/formal-verification/3-gasbad/16-method-entries-introduction/+page.md index efed4f96e..9f34de837 100644 --- a/courses/formal-verification/3-gasbad/16-method-entries-introduction/+page.md +++ b/courses/formal-verification/3-gasbad/16-method-entries-introduction/+page.md @@ -14,7 +14,7 @@ Our methods block til now has been fairly minimalistic, but this section has a l Exact entries are telling the prover 'here's a particular function, on a particular contract, test it'. This can be a little restrictive, but offers a lot of granular control when necessary. - +::image{src='/formal-verification-3/16-method-entries-introduction/method-entries-introduction1.png' style='width: 100%; height: auto;'} So, in our simple NftMock.spec example, we were effectively doing this: @@ -30,7 +30,7 @@ methods { In addition to `exact entries` however, we have other options available to us, such as [**`wildcard entries`**](https://docs.certora.com/en/latest/docs/cvl/methods.html#wildcard-entries). - +::image{src='/formal-verification-3/16-method-entries-introduction/method-entries-introduction2.png' style='width: 100%; height: auto;'} What `wildcard entries` allow us to do is abstract out which contract a given function is being called on. @@ -54,7 +54,7 @@ This tells `Certora` that any `totalSupply` function, found within any scoped fi Additionally, additionally - there are `catch-all entries`. These function almost opposite to wildcard entries. Catch-all entries allow us to specify that all function of a given contract are to behave the same way. - +::image{src='/formal-verification-3/16-method-entries-introduction/method-entries-introduction3.png' style='width: 100%; height: auto;'} Applied to our previous example, the syntax would look something like this: diff --git a/courses/formal-verification/3-gasbad/17-summary-declaration-examples/+page.md b/courses/formal-verification/3-gasbad/17-summary-declaration-examples/+page.md index 42cb561c3..48a7750cd 100644 --- a/courses/formal-verification/3-gasbad/17-summary-declaration-examples/+page.md +++ b/courses/formal-verification/3-gasbad/17-summary-declaration-examples/+page.md @@ -14,19 +14,19 @@ So far we've seen an example of a `view summary` in our demonstrations of the `A `View summaries` themselves are broken into a few different flavours: - +::image{src='/formal-verification-3/17-summary-declaration-examples/summary-declaration-examples1.png' style='width: 100%; height: auto;'} We also have `HAVOC summaries` available to us, which allow us to control, with greater specificity, how the prover responds to particular function calls. > ❗ **NOTE** > `HAVOC'd` verifications may result in undesirable levels of restriction with regards to the soundness and validity of your proof. Use things like `HAVOC_ALL` with restraint and purpose. - +::image{src='/formal-verification-3/17-summary-declaration-examples/summary-declaration-examples2.png' style='width: 100%; height: auto;'} Lastly, for the scope of this course, and most applicably to our GasBad solution, we have `DISPATCHER summaries`. A `DISPATCHER summary` set to true tells the prover that a given function can only execute logic as defined by another contract within our scope. This restricts the behaviour of the function calls in the prover to something predicatable and thus validatable. - +::image{src='/formal-verification-3/17-summary-declaration-examples/summary-declaration-examples3.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/formal-verification/3-gasbad/18-summary-implementations/+page.md b/courses/formal-verification/3-gasbad/18-summary-implementations/+page.md index 324ba6635..039f95466 100644 --- a/courses/formal-verification/3-gasbad/18-summary-implementations/+page.md +++ b/courses/formal-verification/3-gasbad/18-summary-implementations/+page.md @@ -42,11 +42,11 @@ If "true", Certora will look through its list of known contracts for functions t If we look closely as our error in Certora we can see something labelled `AUTO summary`. - +::image{src='/formal-verification-3/18-summary-implementations/summary-implementations1.png' style='width: 100%; height: auto;'} [**Auto summaries**](https://docs.certora.com/en/latest/docs/cvl/methods.html#auto-summaries) effectively represent the Certora prover just trying to figure out what should be happening when not explicitly told. It's specific affect will vary dependent on the type of call being performed. - +::image{src='/formal-verification-3/18-summary-implementations/summary-implementations2.png' style='width: 100%; height: auto;'} Our `safeTransferFrom` scenario would be classified as `other calls`, as a result `HAVOC_ECF` is employed, and this is why we see our function HAVOCing in our test. @@ -140,11 +140,11 @@ invariant anytime_mapping_updated_emit_event() --- - +::image{src='/formal-verification-3/18-summary-implementations/summary-implementations3.png' style='width: 100%; height: auto;'} Uh oh, we're still seeing an issue. Let's drill into the call trace to determine what's happening. If we navigate through the call trace Certora provides to the safeTranferFrom call that was previously failing, we can see a few interesting things. - +::image{src='/formal-verification-3/18-summary-implementations/summary-implementations4.png' style='width: 100%; height: auto;'} It looks like our `safeTransferFrom` function is being called from `NftMock`'s implementation of it, like we'd expect. This function is subsequently calling `_checkOnERC721Received`, and ultimately `onERC721Received`! @@ -183,11 +183,11 @@ function _.onERC721Received(address, address, uint256, bytes) external => CONSTA Let's try running the prover again, now that we've added this additional method block function. - +::image{src='/formal-verification-3/18-summary-implementations/summary-implementations5.png' style='width: 100%; height: auto;'} Well, at least our number of violations are falling. We're also getting lots of practice at debugging these call traces! Let's see what's happening this time. - +::image{src='/formal-verification-3/18-summary-implementations/summary-implementations6.png' style='width: 100%; height: auto;'} Of course this would HAVOC, `call` can _literally_ do anything! As we've seen, if a situation arises which `Certora` can't predict outcomes of, it's going to let slip its dogs of war (cry HAVOC), breaking our invariant. diff --git a/courses/formal-verification/3-gasbad/19-optimistic-fallback-prover-args/+page.md b/courses/formal-verification/3-gasbad/19-optimistic-fallback-prover-args/+page.md index 93e89005b..f8c204893 100644 --- a/courses/formal-verification/3-gasbad/19-optimistic-fallback-prover-args/+page.md +++ b/courses/formal-verification/3-gasbad/19-optimistic-fallback-prover-args/+page.md @@ -14,7 +14,7 @@ Introducing: [**Prover Arguments**](https://docs.certora.com/en/latest/docs/prov Prover arguments (prover_args) are CLI (or conf file) options which can be used to fine tune the behaviour of the prover. I greatly encourage you to read through the options available on the Certora Docs. For our purposes, we want to consider the `-optimisticFallback` option is going to be useful. - +::image{src='/formal-verification-3/19-optimistic-fallback-prover-args/optimistic-fallback-prover-args1.png' style='width: 100%; height: auto;'} Let's apply this argument to our `GasBad.conf`. diff --git a/courses/formal-verification/3-gasbad/20-vacuity/+page.md b/courses/formal-verification/3-gasbad/20-vacuity/+page.md index ee4fc8737..fb3298722 100644 --- a/courses/formal-verification/3-gasbad/20-vacuity/+page.md +++ b/courses/formal-verification/3-gasbad/20-vacuity/+page.md @@ -13,7 +13,7 @@ _Follow along with this video:_ In lieu of updating adjusting this behaviour, let's simply define vacuity and better understand it's impact and how to avoid it in our testing. - +::image{src='/formal-verification-3/20-vacuity/vacuity1.png' style='width: 100%; height: auto;'} Simply put, vacuity defines a situation in which our assertion is effectively unchecked, because we aren't supplying the prover an input that satisfies our spec. @@ -51,6 +51,6 @@ methods { With this change in place, we should be able to run the prover once more... - +::image{src='/formal-verification-3/20-vacuity/vacuity2.png' style='width: 100%; height: auto;'} 🥳 diff --git a/courses/formal-verification/3-gasbad/22-equivalence-checking-solidity-reference/+page.md b/courses/formal-verification/3-gasbad/22-equivalence-checking-solidity-reference/+page.md index 96530f495..4643d287f 100644 --- a/courses/formal-verification/3-gasbad/22-equivalence-checking-solidity-reference/+page.md +++ b/courses/formal-verification/3-gasbad/22-equivalence-checking-solidity-reference/+page.md @@ -21,7 +21,7 @@ Our whole reason for using `Certora`, and formally verifying this repo is - we'v This is going to be an example of a `Parametric Rule`, which we learnt about previously. - +::image{src='/formal-verification-3/22-equivalence-checking-solidity-reference/equivalence-checking-solidity-reference1.png' style='width: 100%; height: auto;'} ```js rule calling_any_function_should_result_in_each_contract_having_the_same_state(method f){} diff --git a/courses/formal-verification/3-gasbad/23-method-filtering/+page.md b/courses/formal-verification/3-gasbad/23-method-filtering/+page.md index 7b9a922bc..31f2583e3 100644 --- a/courses/formal-verification/3-gasbad/23-method-filtering/+page.md +++ b/courses/formal-verification/3-gasbad/23-method-filtering/+page.md @@ -20,7 +20,7 @@ rule calling_any_function_should_result_in_each_contract_having_the_same_state(m Now, in this configuration, `method f` would represent any function within the scoped codebase which can be called with any calldata in any environment. We can assert a little more control over to which methods our parametric rule applies by implementing filters. - +::image{src='/formal-verification-3/23-method-filtering/method-filtering1.png' style='width: 100%; height: auto;'} This methodology should be similar to employing a require statement such as: diff --git a/courses/formal-verification/3-gasbad/24-using/+page.md b/courses/formal-verification/3-gasbad/24-using/+page.md index aadcdde6e..09e9b28de 100644 --- a/courses/formal-verification/3-gasbad/24-using/+page.md +++ b/courses/formal-verification/3-gasbad/24-using/+page.md @@ -20,7 +20,7 @@ rule calling_any_function_should_result_in_each_contract_having_the_same_state(m ...we are calling `method f` on _whichever contract is currently being verfied_. Remember that functionally `f(e, args) == currentContract.f(e, args)`. With that said, we need a way to reference the specific contracts we mean for our rule to compare, and this is where the `using` keyword comes in. - +::image{src='/formal-verification-3/24-using/using1.png' style='width: 100%; height: auto;'} By declaring these variables at the top of our spec file, we can use them to reference particular contracts within our verification scope. diff --git a/courses/formal-verification/3-gasbad/25-finishing-the-rule/+page.md b/courses/formal-verification/3-gasbad/25-finishing-the-rule/+page.md index a4d7c71f0..720f0f0f0 100644 --- a/courses/formal-verification/3-gasbad/25-finishing-the-rule/+page.md +++ b/courses/formal-verification/3-gasbad/25-finishing-the-rule/+page.md @@ -19,7 +19,7 @@ rule calling_any_function_should_result_in_each_contract_having_the_same_state(m With things as we've set them up, we'd expect our rule to call the same method on each of our in scope contracts ... except the [**Certora Documentation**](https://docs.certora.com/en/latest/docs/cvl/rules.html#parametric-rules) says this is wrong 😅. - +::image{src='/formal-verification-3/25-finishing-the-rule/finishing-the-rule1.png' style='width: 100%; height: auto;'} So, this means we'll need to use a different variable for the method called on each contract. The catch is, we need to assure the **same** method is called on each, this is where we'll use a require to ensure the selectors of both arbitrary methods match. @@ -173,7 +173,7 @@ rule calling_any_function_should_result_in_each_contract_having_the_same_state(m --- - +::image{src='/formal-verification-3/25-finishing-the-rule/finishing-the-rule2.png' style='width: 100%; height: auto;'} Uh oh, what's happening here? The error is telling us we have a missing environment variable, even though we tagged these functions as `envfree`... @@ -225,11 +225,11 @@ rule calling_any_function_should_result_in_each_contract_having_the_same_state(m With this adjustment in place, we can run the prover again. - +::image{src='/formal-verification-3/25-finishing-the-rule/finishing-the-rule3.png' style='width: 100%; height: auto;'} It looks like it found a bunch of violations, but the CLI output doesn't provide many details, we should dig deeper into the calls and investigate. If we turn the the `Certora` UI, we can see that a huge number of our function calls failed their `sanity` check! - +::image{src='/formal-verification-3/25-finishing-the-rule/finishing-the-rule4.png' style='width: 100%; height: auto;'} This is actually a product of how we're handling the functions in `GasBad.spec`. @@ -244,7 +244,7 @@ require(gasBadMarketplace.getListing(e, listingAddr, tokenId).seller == Because we're using `require` statements, instead of a `filter block`, `Certora` recognizes calls that don't satisfy these requires as unsound and thus flags them as failing our `sanity` check. If we look closely, we can see the calls with were successfully verified were those in which the function signatures match between our implementations, while those without matches fail this `sanity` check. - +::image{src='/formal-verification-3/25-finishing-the-rule/finishing-the-rule5.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/foundry/1-foundry-simple-storage/12-deploy-a-smart-contract-locally-using-ganache/+page.md b/courses/foundry/1-foundry-simple-storage/12-deploy-a-smart-contract-locally-using-ganache/+page.md index 6efb0efc3..c2a1cdc71 100644 --- a/courses/foundry/1-foundry-simple-storage/12-deploy-a-smart-contract-locally-using-ganache/+page.md +++ b/courses/foundry/1-foundry-simple-storage/12-deploy-a-smart-contract-locally-using-ganache/+page.md @@ -16,7 +16,7 @@ Anvil is a local testnet node shipped with Foundry. You can use it for testing y To run Anvil you simply have to type `anvil` in the terminal. - +::image{src='../../../../static/foundry-simply-storage/10-deploy-a-smart-contract-locally-using-ganache/Image1.PNG' style='width: 75%; height: auto;'} You now have access to 10 test addresses funded with 10_000 ETH each, with their associated private keys. diff --git a/courses/foundry/1-foundry-simple-storage/14-deploy-a-smart-contract-locally-using-forge/+page.md b/courses/foundry/1-foundry-simple-storage/14-deploy-a-smart-contract-locally-using-forge/+page.md index 91fddcb8b..5f560fce0 100644 --- a/courses/foundry/1-foundry-simple-storage/14-deploy-a-smart-contract-locally-using-forge/+page.md +++ b/courses/foundry/1-foundry-simple-storage/14-deploy-a-smart-contract-locally-using-forge/+page.md @@ -50,7 +50,7 @@ You will be asked to enter a private key, please paste one of the private keys a Voila! - +::image{src='../../../../static/foundry-simply-storage/12-deploy-a-smart-contract-locally-using-forge/Image1.PNG' style='width: 75%; height: auto;'} You can go to Ganache and check the `Blocks` and `Transactions` tabs to see more info about what you just did. diff --git a/courses/foundry/1-foundry-simple-storage/21-deploying-to-a-testnet/+page.md b/courses/foundry/1-foundry-simple-storage/21-deploying-to-a-testnet/+page.md index 71b49238d..d89c24df0 100644 --- a/courses/foundry/1-foundry-simple-storage/21-deploying-to-a-testnet/+page.md +++ b/courses/foundry/1-foundry-simple-storage/21-deploying-to-a-testnet/+page.md @@ -20,7 +20,7 @@ _To create one, we could run our own blockchain node, but let's be honest — ma One promising option is using Alchemy - a free NaaS platform that we can send the transactions to. This procedure resides within the _Deploying to Testnet or Mainnnet_ section in the full course repo of the Foundry. - +::image{src='/foundry/19-testnet-deploy/testnet1.png' style='width: 100%; height: auto;'} To access the Alchemy platform, we simply click on the aforementioned function. On the platform, we sign up (I used Google sign-in for this demo). diff --git a/courses/foundry/1-foundry-simple-storage/22-verify-a-smart-contract-on-etherscan/+page.md b/courses/foundry/1-foundry-simple-storage/22-verify-a-smart-contract-on-etherscan/+page.md index 4cdbeecb2..6d7f7c282 100644 --- a/courses/foundry/1-foundry-simple-storage/22-verify-a-smart-contract-on-etherscan/+page.md +++ b/courses/foundry/1-foundry-simple-storage/22-verify-a-smart-contract-on-etherscan/+page.md @@ -12,7 +12,7 @@ Soooo ... we just deployed our smart contract on Sepolia, let's check it out! We go [here](https://sepolia.etherscan.io/address/0x1093560Fe9029c4fB9044AbF2fC94288970D98Db#code) click on `Contract` and find this: - +::image{src='../../../../static/foundry-simply-storage/21-verify-a-smart-contract-on-etherscan/Image1.PNG' style='width: 75%; height: auto;'} This bytecode looks horrendous. We need to do something to improve the readability (which currently is non-existent). diff --git a/courses/foundry/1-foundry-simple-storage/24-foundry-zksync/+page.md b/courses/foundry/1-foundry-simple-storage/24-foundry-zksync/+page.md index e26f07325..b88fbea39 100644 --- a/courses/foundry/1-foundry-simple-storage/24-foundry-zksync/+page.md +++ b/courses/foundry/1-foundry-simple-storage/24-foundry-zksync/+page.md @@ -16,7 +16,7 @@ To get started with zkSync, we will follow these three steps: 2. 🧑‍💻 Compile the Solidity contract with the `--zksync` flag 3. 🔄🏠 Reinstall the original Vanilla Foundry -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Installing `foundry-zksync` will override any existing Foundry binaries, such as `forge` and `cast`. The GitHub resources for this course contain a link to the [Foundry zkSync repository](https://github.com/Cyfrin/foundry-full-course-cu?tab=readme-ov-file#compiling-to-zksync-in-foundry-zksync). `foundry-zksync` is a fork of Foundry tailored for the zkSync environment. The [repository](https://github.com/matter-labs/foundry-zksync) includes quick install instructions to help you set up the tool. diff --git a/courses/foundry/1-foundry-simple-storage/26-zksync-local-node/+page.md b/courses/foundry/1-foundry-simple-storage/26-zksync-local-node/+page.md index 5e93e1dca..80a21f1fd 100644 --- a/courses/foundry/1-foundry-simple-storage/26-zksync-local-node/+page.md +++ b/courses/foundry/1-foundry-simple-storage/26-zksync-local-node/+page.md @@ -8,7 +8,7 @@ _Follow along with the video_ ### Introduction -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > This lesson is optional. If you encounter difficulties installing or understanding the required tools, feel free to proceed to the next section and continue using Anvil to test your smart contract locally. In the previous lessons, we learned about deploying smart contracts with the `forge create` and `forge script` commands on our **local Anvil chain**. In this lesson, we will set up and run a **zkSync local environment**. @@ -25,7 +25,7 @@ To deploy locally on a zkSync local chain, you'll need additional tools: Docker, To start your local zkSync node, run `npx zksync-cli dev start`. This command spins up a zkSync node in Docker and runs it in the background. Verify the process is running with `docker ps`. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > If Docker isn’t running, the `npx zksync-cli dev start` command will fail. Ensure Docker is running before attempting to start the zkSync node again. ### Deployment diff --git a/courses/foundry/1-foundry-simple-storage/27-zksync-local-deploy/+page.md b/courses/foundry/1-foundry-simple-storage/27-zksync-local-deploy/+page.md index 2fae94c30..08b5baaf6 100644 --- a/courses/foundry/1-foundry-simple-storage/27-zksync-local-deploy/+page.md +++ b/courses/foundry/1-foundry-simple-storage/27-zksync-local-deploy/+page.md @@ -18,12 +18,12 @@ forge create src/SimpleStorage.sol:SimpleStorage --rpc_url --private_k Here, `` represents zkSync node address, such as `http://127.0.0.1:8011`. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Including private keys directly in commands is not a safe practice. This command instructs Foundry to locate the `SimpleStorage` contract in the `src/SimpleStorage.sol` file and deploy it. Upon execution, the contract compiles and deploys successfully. The output will display details such as the deployer, the deployed contract address, and the transaction hash. - +::image{src='/foundry-simply-storage/27-zksync-local-deploy/deployment-successful.png' style='width: 50%; height: auto;'} Using the `--legacy` flag is recommended for deploying simple contracts, while more complex codebases may require different approaches. Attempting to deploy without the `--legacy` flag might result in errors like `failed to serialize transaction, address to address is null`, which will be covered in future lessons. diff --git a/courses/foundry/1-foundry-simple-storage/28-tx-types/+page.md b/courses/foundry/1-foundry-simple-storage/28-tx-types/+page.md index af6346cb7..bb0bbf9d7 100644 --- a/courses/foundry/1-foundry-simple-storage/28-tx-types/+page.md +++ b/courses/foundry/1-foundry-simple-storage/28-tx-types/+page.md @@ -18,12 +18,12 @@ By examining both the `run-latest.json` file in these folders, we can observe di The EVM and ZK Sync ecosystems support multiple transaction types to accommodate various Ethereum Improvement Proposals (EIPs). Initially, Ethereum had only one transaction type (`0x0` legacy), but as the ecosystem evolved, multiple types were introduced through various EIPs. Subsequent types include type 1, which introduces an _access list_ of addresses and keys, and type 2, also known as [EIP 1559](https://eips.ethereum.org/EIPS/eip-1559) transactions. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > This `0x2` type is the current default type for the EVM. Additionally, ZK Sync introduces its [unique transaction type](https://docs.zksync.io/zk-stack/concepts/transaction-lifecycle#eip-712-0x71), the type `113` (`0x71` in hex), which can enable features like [account abstraction](https://docs.zksync.io/build/developer-reference/account-abstraction/). -> 💡 **TIP**
+> 💡 **TIP**:br > The `forge script` command will work in some scenarios, but it’s not entirely clear where it might fail. For the purpose of this course, we will assume scripting does not work while working with Sync. ### Resources diff --git a/courses/foundry/1-foundry-simple-storage/29-why-l2/+page.md b/courses/foundry/1-foundry-simple-storage/29-why-l2/+page.md index 5864b1eb8..a4deee598 100644 --- a/courses/foundry/1-foundry-simple-storage/29-why-l2/+page.md +++ b/courses/foundry/1-foundry-simple-storage/29-why-l2/+page.md @@ -26,5 +26,5 @@ Deploying even a minimal contract like `SimpleStorage` on Ethereum can be expens Deploying to ZK Sync Sepolia is similar to deploying to a ZK Sync local node. You can retrieve a ZK Sync Sepolia RPC URL from [Alchemy](https://www.alchemy.com/) by creating a new app based on the ZK Sepolia network. Then, you can proceed to add the `ZKSYNC_RPC_URL` to your `.env` configuration. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > To understand the cost benefits of Layer 2 solutions, visit [L2Fees.info](https://l2fees.info) and compare the significant cost differences between sending a transaction on Ethereum and ZK Sync Era. diff --git a/courses/foundry/1-foundry-simple-storage/30-alchemy-mempool/+page.md b/courses/foundry/1-foundry-simple-storage/30-alchemy-mempool/+page.md index bb80251b6..61986c94d 100644 --- a/courses/foundry/1-foundry-simple-storage/30-alchemy-mempool/+page.md +++ b/courses/foundry/1-foundry-simple-storage/30-alchemy-mempool/+page.md @@ -55,7 +55,7 @@ Once your application is up and running, you will have access to the application If you observe a lower success rate for your transactions, go to the "Recent Invalid Request" tab. This will list all unsuccessful requests along with the reasons for their failure, making it easier for you to debug and fix issues. - +::image{src='/foundry/22-alchemy/alchemy1.png' style='width: 100%; height: auto;'} ## Mempool Watcher diff --git a/courses/foundry/1-foundry-simple-storage/31-summary-congratulations/+page.md b/courses/foundry/1-foundry-simple-storage/31-summary-congratulations/+page.md index e1c9e0ff4..049d80615 100644 --- a/courses/foundry/1-foundry-simple-storage/31-summary-congratulations/+page.md +++ b/courses/foundry/1-foundry-simple-storage/31-summary-congratulations/+page.md @@ -36,7 +36,7 @@ Moreover, the knowledge on how to auto format contracts with `Forge format` was forge format my_contract.sol ``` - +::image{src='/foundry/23-summary/summary1.png' style='width: 100%; height: auto;'} ## Looking Ahead @@ -44,4 +44,4 @@ With these tools in your web development arsenal, you've performed exceptionally Take a breather. Remember, breaks enhance productivity. Till next time, continue to strive for greatness in every line of code you write! - +::image{src='/foundry/23-summary/summary2.png' style='width: 100%; height: auto;'} diff --git a/courses/foundry/1-foundry-simple-storage/6-foundry-install/+page.md b/courses/foundry/1-foundry-simple-storage/6-foundry-install/+page.md index f0731d282..329355c91 100644 --- a/courses/foundry/1-foundry-simple-storage/6-foundry-install/+page.md +++ b/courses/foundry/1-foundry-simple-storage/6-foundry-install/+page.md @@ -24,7 +24,7 @@ When commands pile up in your terminal, things can get a little overwhelming. Cl ### Understanding the Trash Can and the X - +::image{src='/foundry/5-foundryinstall/foundryinstall1.png' style='width: 100%; height: auto;'} The trash can and the X buttons in your terminal perform distinct functions. Hitting `X` simply hides your terminal but retains all the previous lines of code. On the other hand, trashing it essentially deletes whatever is running in it. To open up a clean terminal, hit the trash can and then pull it back using `Toggle` or `Terminal > New Terminal`. @@ -82,6 +82,6 @@ cd ~echo 'source /home/user/.bashrc' >> ~/.bash_profile And there we have it! Congratulations on installing Foundry and prepping your terminal to work seamlessly with it. Remember, hitting snags during installation is normal, especially if you're new to this. Don't hesitate to engage with the course community via GitHub if you run into issues. - +::image{src='/foundry/5-foundryinstall/foundryinstall2.png' style='width: 100%; height: auto;'} Here's to many hassle-free coding sessions with Foundry! diff --git a/courses/foundry/1-foundry-simple-storage/8-create-a-new-foundry-project/+page.md b/courses/foundry/1-foundry-simple-storage/8-create-a-new-foundry-project/+page.md index 0b65515f8..4b30bfbb5 100644 --- a/courses/foundry/1-foundry-simple-storage/8-create-a-new-foundry-project/+page.md +++ b/courses/foundry/1-foundry-simple-storage/8-create-a-new-foundry-project/+page.md @@ -61,7 +61,7 @@ git config --global user.name "yourUsername" And that's it, your folder should look as follows: - +::image{src='../../../../static/foundry-simply-storage/7-create-a-new-foundry-project/Image1.PNG' style='width: 75%; height: auto;'} **But what does all this mean?** diff --git a/courses/foundry/2-foundry-fund-me/1-introduction/+page.md b/courses/foundry/2-foundry-fund-me/1-introduction/+page.md index db6f49ebc..72ad84be4 100644 --- a/courses/foundry/2-foundry-fund-me/1-introduction/+page.md +++ b/courses/foundry/2-foundry-fund-me/1-introduction/+page.md @@ -8,7 +8,7 @@ _Follow along with the video_ Welcome to the Fund Me section of this Foundry course! To get started, you can visit the [Github repository](https://github.com/Cyfrin/foundry-fund-me-cu) associated with this section. By the end of this course, you'll be able to push your first codebase to Github 🎉. -> 💡 **TIP**
+> 💡 **TIP**:br > Being active on a version control system like Github or [Radicle](https://radicle.xyz/) is essential for participating in the web3 ecosystem 👥. In this section, we'll refer to the `FundMe` contract we built in the previous section. Additionally, we will explore storage using the [`FunWithStorage`](https://github.com/Cyfrin/foundry-fund-me-cu/blob/main/src/exampleContracts/FunWithStorage.sol) contract and interact with it using `cast`. diff --git a/courses/foundry/2-foundry-fund-me/22-zksync-devops/+page.md b/courses/foundry/2-foundry-fund-me/22-zksync-devops/+page.md index 40ffb623e..e8a6b8455 100644 --- a/courses/foundry/2-foundry-fund-me/22-zksync-devops/+page.md +++ b/courses/foundry/2-foundry-fund-me/22-zksync-devops/+page.md @@ -60,5 +60,5 @@ For more details on these modifiers, refer to the [foundry-devops repo](https:// Some tests may fail depending on the Foundry version. The `FoundryZkSyncChecker` package assists in executing functions based on the Foundry version. The `onlyFoundryZkSync` modifier allows tests to run only if `foundryup--zksync` is active, while `onlyVanillaFoundry` works only if `foundryup` is active. -> 🗒️ **Note**
+> 🗒️ **Note**:br > Ensure `ffi = true` is enabled in the `foundry.toml` file. diff --git a/courses/foundry/3-html-fund-me/2-setup/+page.md b/courses/foundry/3-html-fund-me/2-setup/+page.md index e11406418..2b8ac939b 100644 --- a/courses/foundry/3-html-fund-me/2-setup/+page.md +++ b/courses/foundry/3-html-fund-me/2-setup/+page.md @@ -46,17 +46,17 @@ code html-fund-me-f23 In order to spin up a local front end, we're going to use an extension called [**Live Server**](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer). Once installed you can simply press the `Go Live` button in the bottom right. - +::image{src='../../../../static/html-fundme/1-setup/live-server.png' style='width: 75%; height: auto;'} Once installed, you can simply press the 'Go Live' button in the bottom right of your VS Code. - +::image{src='../../../../static//html-fundme/1-setup/html-fund-me1.png' style='width: 75%; height: auto;'} And with that you should have this simple front end open in a browser. - +::image{src='../../../../static//html-fundme/1-setup/html-fund-me2.png' style='width: 75%; height: auto;'} -
+:br We'll be using this to glean a deeper understanding of what exactly is happening when we're interacting with websites in the coming lessons. ### Questions to Consider diff --git a/courses/foundry/3-html-fund-me/3-metamask/+page.md b/courses/foundry/3-html-fund-me/3-metamask/+page.md index 3335e5c0a..3b70cd20b 100644 --- a/courses/foundry/3-html-fund-me/3-metamask/+page.md +++ b/courses/foundry/3-html-fund-me/3-metamask/+page.md @@ -12,7 +12,7 @@ The first concept we need to grasp when working with a website in Web3 is that o We can gain more insight into how this works by right-clicking our `FundMe` website and selecting `inspect`. You can also open this panel by pressing F12. - +::image{src='/html-fundme/2-metamask/metamask1.png' style='width: 75%; height: auto;'} Navigate to the console tab of this panel. This tab contains a live JavaScript shell which houses a tonne of information about the browser we have open. Among this data is a JavaScript object, `window`. @@ -20,7 +20,7 @@ By typing `window` and hitting enter the console will display this object and al We should see something like this: - +::image{src='/html-fundme/2-metamask/metamask2.png' style='width: 75%; height: auto;'} As seen in the image, there are some properties of this object which are not there by default, one of which is `window.ethereum`. It's through this property that a front end is able to interact with our wallet and it's accounts. @@ -80,7 +80,7 @@ This grabs the element of the webpage by the `id` we set and then uses the `onCl Clicking on the `Connect` button on our `html-fund-me` front end, should trigger our Metamask to pop up. From there we can select an account and click connect. - +::image{src='/html-fundme/2-metamask/metamask3.png' style='width: 75%; height: auto;'} You'll know this works if your `Connect` button changes to `Connected` and an address is printed to your browser console. @@ -88,7 +88,7 @@ Now you're ready to interact! The functions on our front-end example should look Let's try calling `getBalance` and see how it works - if you're chain is currently set to Ethereum, you might actually get a balance. - +::image{src='/html-fundme/2-metamask/metamask4.png' style='width: 75%; height: auto;'} When the `getBalance` buttons is clicked, this is the function we're calling on our front-end. @@ -116,7 +116,7 @@ As before, we're checking for the existence of `window.ethereum` and then .. def What `ethers.BrowserProvider(window.ethereum)` is doing, is deriving the providers Metamask is injecting into our `window.ethereum` object. The providers are the RPC URLs associated with the networks in our Metamask account. - +::image{src='/html-fundme/2-metamask/metamask5.png' style='width: 75%; height: auto;'} When we call functions on our front-end. We're effectively making API calls via the RPC URL to the blockchain. @@ -134,13 +134,13 @@ This will compile and deploy our FundMe project onto our locally running blockch Return to Metamask, and within your network selector choose `Add Network`. - +::image{src='/html-fundme/2-metamask/metamask6.png' style='width: 75%; height: auto;'} Select `Add a network manually` linked at the bottom of the served page. In the subsequent page, inter your local network information as follows and click `Save`. - +::image{src='/html-fundme/2-metamask/metamask5.png' style='width: 75%; height: auto;'} Next, we need to add one of our `anvil` accounts to the wallet! @@ -148,7 +148,7 @@ Click the account displayed at the top of your Metamask and select `Add an accou You'll be prompted to `add a new account`, `import an account`, or `add a hardware wallet`. Select `import an account` and enter your previously copied mock private key into the field provided. - +::image{src='/html-fundme/2-metamask/metamask7.png' style='width: 75%; height: auto;'} ALRIGHT. With all the set up done, we should be able to select our `anvil` chain in Metamask, then select the account we just added and click the `connect` button. diff --git a/courses/foundry/3-html-fund-me/4-function-selectors/+page.md b/courses/foundry/3-html-fund-me/4-function-selectors/+page.md index 12d6a89ca..3e0b59d5e 100644 --- a/courses/foundry/3-html-fund-me/4-function-selectors/+page.md +++ b/courses/foundry/3-html-fund-me/4-function-selectors/+page.md @@ -10,11 +10,11 @@ _Follow along the course with this video._ Continuing from the last lesson, when we call the `fund` function our Metamask is going to pop up with a bunch of information about the transaction. - +::image{src='/html-fundme/3-function-selector/function-selector1.png' style='width: 75%; height: auto;'} By clicking the `Hex` tab, we can confirm the raw data for this transaction and exactly which function is being called. - +::image{src='/html-fundme/3-function-selector/function-selector2.png' style='width: 75%; height: auto;'} We'll go into `function selectors` a lot more later, but the important thing to understand is that when a Solidity contract is compiled, our functions are converted into a low-level bytecode called a `function selector`. @@ -45,7 +45,7 @@ function fund(uint256 amount) public payable { If we were to call this function, the information Metamask gives us is a little different. - +::image{src='/html-fundme/3-function-selector/function-selector3.png' style='width: 75%; height: auto;'} In this instance, we can use the command `cast --calldata-decode ` to provide us the parameters being passed in this function call! diff --git a/courses/foundry/3-html-fund-me/5-summary/+page.md b/courses/foundry/3-html-fund-me/5-summary/+page.md index 7a38b41d6..f270a1a5a 100644 --- a/courses/foundry/3-html-fund-me/5-summary/+page.md +++ b/courses/foundry/3-html-fund-me/5-summary/+page.md @@ -37,7 +37,7 @@ const signer = provider.getSigner(); Once a wallet is connected, it's important to remember that the browser sends transactions _to_ our wallet for signing/confirmation. The wallet does _not_ provide private key information to the browser application. - +::image{src='/html-fundme/2-metamask/metamask5.png' style='width: 75%; height: auto;'} We also learnt a basic way to verify the function calls being sent to our wallet through the use of `function selectors` and decoding `calldata`. We'll go over this in more detail later! diff --git a/courses/foundry/4-smart-contract-lottery/1-introduction/+page.md b/courses/foundry/4-smart-contract-lottery/1-introduction/+page.md index 45b83858f..094ad33e7 100644 --- a/courses/foundry/4-smart-contract-lottery/1-introduction/+page.md +++ b/courses/foundry/4-smart-contract-lottery/1-introduction/+page.md @@ -8,7 +8,7 @@ _Follow along with the video_ Welcome to Section 9 of this course! You can code along by following the [GitHub repo](https://github.com/Cyfrin/foundry-smart-contract-lottery-cu) associated with this section. This project will be a valuable addition to your portfolio, as we'll develop a **Verifiably Random Lottery Smart Contract** that contains a lot of best coding practices. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > We won't be deploying this to ZkSync because of current integration constraints. In this lesson, we will cover **events**, **true random numbers**, **modules**, and **automation**. You can preview the final project by cloning the repository and checking the `makefile`, which lists all the specific versions of dependencies needed to compile our contract. diff --git a/courses/foundry/4-smart-contract-lottery/20-deploy-script/+page.md b/courses/foundry/4-smart-contract-lottery/20-deploy-script/+page.md index 52a0f8716..8f4759bef 100644 --- a/courses/foundry/4-smart-contract-lottery/20-deploy-script/+page.md +++ b/courses/foundry/4-smart-contract-lottery/20-deploy-script/+page.md @@ -14,7 +14,7 @@ import {Script} from "forge-std/Script.sol"; import {Raffle} from "../src/Raffle.sol"; ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > There are two ways to import files in Solidity: using a direct path or a relative path. In this example, we are using a relative path, where the `Raffle.sol` file is inside the `src` directory but one level up (`..`) from the current file's location. ### The `deployContract` Function @@ -89,7 +89,7 @@ abstract contract CodeConstants { These values can be used inside the `HelperConfig` constructor: -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > We are choosing the use of **constants** over magic numbers ```js diff --git a/courses/foundry/4-smart-contract-lottery/32-even-more-tests/+page.md b/courses/foundry/4-smart-contract-lottery/32-even-more-tests/+page.md index 95a299479..8f030507b 100644 --- a/courses/foundry/4-smart-contract-lottery/32-even-more-tests/+page.md +++ b/courses/foundry/4-smart-contract-lottery/32-even-more-tests/+page.md @@ -10,7 +10,7 @@ _Follow along with the video_ In this lesson we are going to build a couple more tests. If we check our code coverage with `forge coverage`, the terminal will show that we are only at around 53% coverage for the `Raffle.sol` contract. Code coverage refers to the percentage of lines of code that have been tested. -> 💡 **TIP**
+> 💡 **TIP**:br > Achieving 100% coverage isn't always required, but it is a recommended target. ### `checkUpkeep` tests diff --git a/courses/foundry/4-smart-contract-lottery/33-coverage-report/+page.md b/courses/foundry/4-smart-contract-lottery/33-coverage-report/+page.md index db513141e..4bdd871ff 100644 --- a/courses/foundry/4-smart-contract-lottery/33-coverage-report/+page.md +++ b/courses/foundry/4-smart-contract-lottery/33-coverage-report/+page.md @@ -21,5 +21,5 @@ We should improve our test suite by writing additional tests. Here are some spec - [testCheckUpkeepReturnsFalseIfEnoughTimeHasntPassed](https://github.com/Cyfrin/foundry-smart-contract-lottery-cu/blob/083ebe5843573edfaa52fb002613b87d36d0d466/test/unit/RaffleTest.t.sol#L140) - [testCheckUpkeepReturnsTrueWhenParametersGood](https://github.com/Cyfrin/foundry-smart-contract-lottery-cu/blob/083ebe5843573edfaa52fb002613b87d36d0d466/test/unit/RaffleTest.t.sol#L153C14-L153C58) -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > You don't need to submit a pull request or make any course-related updates. This exercise is for your benefit to increase your testing skills. diff --git a/courses/foundry/4-smart-contract-lottery/9-implementing-vrf-fulfil/+page.md b/courses/foundry/4-smart-contract-lottery/9-implementing-vrf-fulfil/+page.md index c8da4e53f..0d180d143 100644 --- a/courses/foundry/4-smart-contract-lottery/9-implementing-vrf-fulfil/+page.md +++ b/courses/foundry/4-smart-contract-lottery/9-implementing-vrf-fulfil/+page.md @@ -16,7 +16,7 @@ function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) i - After a certain number of block confirmations, the Chainlink Node will generate a random number and call the `VRFConsumerBaseV2Plus::rawFulfillRandomWords` function. This function validates the caller address and then invokes the `fulfillRandomWords` function in our `Raffle` contract. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Since `VRFConsumerBaseV2Plus::fulfillRandomWords` is marked as `virtual`, we need to **override** it in its child contract. This requires defining the actions to take when the random number is returned, such as selecting a winner and distributing the prize. Here’s how you override the `fulfillRandomWords` function: diff --git a/courses/security/1-review/1-tooling-requisites/+page.md b/courses/security/1-review/1-tooling-requisites/+page.md index b6e1c2f60..4341fce0e 100644 --- a/courses/security/1-review/1-tooling-requisites/+page.md +++ b/courses/security/1-review/1-tooling-requisites/+page.md @@ -32,7 +32,7 @@ For Linux and Mac users, you can simply stick with the environments you're alrea AI tools like [**Phind**](https://www.phind.com/) or [**ChatGPT**](https://www.chat.openai.com) aid in seeking answers when things get tough. One nifty feature **Phind** offers is web searching; you can query "_install Foundry for the ETH ecosystem_", and the tool will surf the web, compile an answer, and offer you a digestible solution for your query! -block fee +::image{src='/security-section-1/1-tooling/tooling1.PNG' style='width: 100%; height: auto;' alt='block fee'} ## Web3 Is About Community diff --git a/courses/security/1-review/10-abi-encode/+page.md b/courses/security/1-review/10-abi-encode/+page.md index ad6dc56cf..a747fec08 100644 --- a/courses/security/1-review/10-abi-encode/+page.md +++ b/courses/security/1-review/10-abi-encode/+page.md @@ -46,7 +46,7 @@ Before getting to deep with encoding, let's take a step back to understand what' ### Compilation Breakdown -block fee +::image{src='/security-section-1/10-encoding/encoding1.png' style='width: 100%; height: auto;' alt='block fee'} As seen in the image above, when we compile a smart contract, the solidity compiler is returning two things `contract.abi` and `contract.bin`. The `abi` you likely remember from previous lessons. @@ -98,11 +98,11 @@ Read more about [**Non-standard Packed Mode**](https://docs.soliditylang.org/en/ The other side to this whole equation is that we also have the ability to _`decode`_ things. -block fee +::image{src='/security-section-1/10-encoding/encoding2.png' style='width: 100%; height: auto;' alt='block fee'} and finally .. we can even `multiEncode` and `multiDecode`. -## block fee +## ::image{src='/security-section-1/10-encoding/encoding3.png' style='width: 100%; height: auto;' alt='block fee'} # Conclusion diff --git a/courses/security/1-review/11-encoding-function/+page.md b/courses/security/1-review/11-encoding-function/+page.md index 4d3ee7b13..d89f914f0 100644 --- a/courses/security/1-review/11-encoding-function/+page.md +++ b/courses/security/1-review/11-encoding-function/+page.md @@ -14,7 +14,7 @@ We know the EVM is looking for this encoded information, this binary _stuff_. An
- block fee + ::image{src='/security-section-1/11-encoding-function/encoding-function2.png' style='width: 95%; height: auto;' alt='block fee'}
Remember the properties of a Transaction
@@ -40,7 +40,7 @@ This encoded function call in the data field is how the EVM, or any EVM compatib ### Direct Function Calls -block fee +::image{src='/security-section-1/11-encoding-function/encoding-function1.png' style='width: 100%; height: auto;' alt='block fee'} With our understanding of ABI encoding, the possibilities expand. We're now able to populate the data field of our transactions directly with the binary or hex code corresponding to the desired function call. Remember, when you initially compile your transaction, `data` was a field that existed? This is where that comes into play. diff --git a/courses/security/1-review/12-upgradeable-contracts/+page.md b/courses/security/1-review/12-upgradeable-contracts/+page.md index d54046b22..d3c39e606 100644 --- a/courses/security/1-review/12-upgradeable-contracts/+page.md +++ b/courses/security/1-review/12-upgradeable-contracts/+page.md @@ -57,7 +57,7 @@ Within our `Proxy.sol` contract, we've got the `_delegate()` function. This func The `_delegate()` function, then sends that data over to some `implementation` contract. -block fee +::image{src='/security-section-1/12-upgradeable/upgrades2.png' style='width: 100%; height: auto;' alt='block fee'} Looking at `SmallProxy.sol` you can see you have these two functions: @@ -148,7 +148,7 @@ function setImplementation(address implementationB); When we then pass the same data as before to our proxy contract, we can indeed see this is reaching `implementationB` and we're having returned `newValue +2`! -block fee +::image{src='/security-section-1/12-upgradeable/upgrades3.png' style='width: 100%; height: auto;' alt='block fee'} --- diff --git a/courses/security/1-review/14-fork-tests/+page.md b/courses/security/1-review/14-fork-tests/+page.md index ab69694f0..4a2a962a9 100644 --- a/courses/security/1-review/14-fork-tests/+page.md +++ b/courses/security/1-review/14-fork-tests/+page.md @@ -47,7 +47,7 @@ Before signing off, I'd encourage you to join the [Cyfrin Discord](https://disco In addition to this, check out the [**Discussions on GitHub**](https://github.com/Cyfrin/security-and-auditing-full-course-s23/discussions) - this is a phenomenal place to get support and have your questions answered in a way that will be indexed by search engines and AI in an effort to improve the experience for people coming behind us. -block fee +::image{src='/security-section-1/14-fork-tests/forking1.png' style='width: 100%; height: auto;' alt='block fee'} Congratulations on finishing the refresher! Take a break, you greatly deserve it for getting this far! diff --git a/courses/security/1-review/2-solidity-requisites/+page.md b/courses/security/1-review/2-solidity-requisites/+page.md index 8178d53f1..835fe5fb6 100644 --- a/courses/security/1-review/2-solidity-requisites/+page.md +++ b/courses/security/1-review/2-solidity-requisites/+page.md @@ -19,7 +19,7 @@ All of the basic Solidity, variable types, contract structure etc should be seco You should also be familiar with the working environments of Foundry, or your framework of choice. You should understand how to initialize a project in your framework and navigate it's working tree.
-block fee +::image{src='/security-section-1/2-solidity-req/solidity-prerequisites1.PNG' style='width: 40%; height: auto;' alt='block fee'}
Commands like these should ring lots of bells. diff --git a/courses/security/1-review/3-fuzzing-and-invariants/+page.md b/courses/security/1-review/3-fuzzing-and-invariants/+page.md index 0dc39f322..7d4d0d6d2 100644 --- a/courses/security/1-review/3-fuzzing-and-invariants/+page.md +++ b/courses/security/1-review/3-fuzzing-and-invariants/+page.md @@ -77,7 +77,7 @@ Fuzzing also comes in flavours, the above being an example of `stateless fuzzing This is important for situations like our `doStuff` function -block fee +::image{src='/security-section-1/3-fuzz-test/fuzz2.png' style='width: 100%; height: auto;' alt='block fee'} A stateful fuzz test would instead utilize the same contract we just triggered and call another function on it, creating an interlocking sequence of functions throughout a single run. Achieving this in Foundry requires using the `invariant` keyword and a bit of setup: diff --git a/courses/security/1-review/6-what-is-erc721/+page.md b/courses/security/1-review/6-what-is-erc721/+page.md index 77ebc373a..56cf749a1 100644 --- a/courses/security/1-review/6-what-is-erc721/+page.md +++ b/courses/security/1-review/6-what-is-erc721/+page.md @@ -22,8 +22,8 @@ Although NFTs are mostly associated with art, they extend beyond that. They can
- block fee - block fee + ::image{src='/security-section-1/6-erc721s/erc721s1.png' style='width: 20%; height: auto;' alt='block fee'} + ::image{src='/security-section-1/6-erc721s/erc721s2.png' style='width: 21.7%; height: auto;' alt='block fee'}
An NFT example from Milady
diff --git a/courses/security/1-review/7-advanced-solidity-prerequisites/+page.md b/courses/security/1-review/7-advanced-solidity-prerequisites/+page.md index a9066aef7..05050dabe 100644 --- a/courses/security/1-review/7-advanced-solidity-prerequisites/+page.md +++ b/courses/security/1-review/7-advanced-solidity-prerequisites/+page.md @@ -18,7 +18,7 @@ It's worth noting, however, that constants or immutable variables do not occupy To illustrate: -block fee +::image{src='/security-section-1/7-advanced-solidity/sol2.png' style='width: 100%; height: auto;' alt='block fee'} ### Hands-on Learning with Code diff --git a/courses/security/1-review/8-storage/+page.md b/courses/security/1-review/8-storage/+page.md index 782eb070c..d6eb10713 100644 --- a/courses/security/1-review/8-storage/+page.md +++ b/courses/security/1-review/8-storage/+page.md @@ -12,7 +12,7 @@ In this lesson, we are going to discuss some important aspects related to variab First and foremost, we need to familiarize ourselves with the concept of `Storage`. In Solidity, when we refer to variables that are global or those that persist over time, we are actually referring to variables that exist in `Storage`. -block fee +::image{src='/security-section-1/8-storage/storage1.png' style='width: 100%; height: auto;' alt='block fee'} Think of `Storage` as a huge array or list that contains all the variables we create in Solidity. When we declare a variable in a contract—say a contract named `fundamentalStorage`—to be a certain value, such as `favoriteNumber`, we're essentially demanding this variable to persist. This persistence is obtained via `Storage`. diff --git a/courses/security/1-review/9-fallback-and-receive/+page.md b/courses/security/1-review/9-fallback-and-receive/+page.md index c671541dc..28c03be67 100644 --- a/courses/security/1-review/9-fallback-and-receive/+page.md +++ b/courses/security/1-review/9-fallback-and-receive/+page.md @@ -15,7 +15,7 @@ These two specific functions - `fallback` and `receive` - enable a contract to a So, how do they function? Here's the core logic to give you a better understanding:
- block fee + ::image{src='/security-section-1/9-fallback-receive/fallback-receive1.png' style='width: 30%; height: auto;' alt='block fee'}
To put it simply, consider the case of sending ETH to a smart contract without any data. In such an instance, the `receive` function would be called, resorting to `fallback` if the `receive` function does not exist. diff --git a/courses/security/2-audit/2-the-audit/+page.md b/courses/security/2-audit/2-the-audit/+page.md index ab7266056..03aebc813 100644 --- a/courses/security/2-audit/2-the-audit/+page.md +++ b/courses/security/2-audit/2-the-audit/+page.md @@ -43,7 +43,7 @@ Your ultimate aim should be to make the protocol more secure. Therefore, ensure Remember, the goal of conducting contract audits isn't simply to tick a box. It's about ensuring the security and smooth running of the smart contract at all stages of its lifecycle. The more audits you conduct, the better you become at identifying potential security issues.
- +::image{src='/security-section-2/2-the-audit/the-audit1.png' style='width: 75%; height: auto;'}
## Embedding Security Audits in Development Lifecycle diff --git a/courses/security/2-audit/3-rekt-test/+page.md b/courses/security/2-audit/3-rekt-test/+page.md index 8f2b9d088..bac5e21b6 100644 --- a/courses/security/2-audit/3-rekt-test/+page.md +++ b/courses/security/2-audit/3-rekt-test/+page.md @@ -14,7 +14,7 @@ The concept that once you've had an audit done, you're ready to ship - is wrong. The Rekt Test is highly important as it poses a set of questions to gauge your protocol's preparedness for an audit. This tool forces you to think about security measures from a more proactive angle. Should your protocols fail to answer these questions, the chances are that they're not audit-ready. - +::image{src='/security-section-2/3-rekt/rekt1.png' style='width: 100%; height: auto;'} The questions touch on several aspects like documentation, security roles, security tools, and protective measures, among others. Here's a curated list: diff --git a/courses/security/2-audit/4-tools/+page.md b/courses/security/2-audit/4-tools/+page.md index 98e8030c1..302db2dac 100644 --- a/courses/security/2-audit/4-tools/+page.md +++ b/courses/security/2-audit/4-tools/+page.md @@ -28,7 +28,7 @@ Throughout this course, we'll work heavily with Slither and Aderyn, you'll becom Next we have Fuzz testing, which really comes in two flavours, `fuzz testing` and `stateful fuzz testing`. - +::image{src='/security-section-2/4-tools/tools2.png' style='width: 100%; height: auto;'} A few other types of testing we _won't_ be covering are `differential test` and `chaos tests`, but in an effort to further you security journey, you always want to be looking for new looks and expanding your knowledge, so you may want to check them out. diff --git a/courses/security/3-first-audit/1-first-review/+page.md b/courses/security/3-first-audit/1-first-review/+page.md index cfd4e5ca6..466974771 100644 --- a/courses/security/3-first-audit/1-first-review/+page.md +++ b/courses/security/3-first-audit/1-first-review/+page.md @@ -16,7 +16,7 @@ For out first audit we're immersing ourselves into a scenario where we're auditi In later lessons we'll also go through the process of submission findings in a competive scenario like `CodeHawks` - +::image{src='/security-section-3/1-review/firstaudit1.png' style='width: 100%; height: auto;'} ### The End Goal diff --git a/courses/security/3-first-audit/10-exploit-public-data/+page.md b/courses/security/3-first-audit/10-exploit-public-data/+page.md index 7c28c6747..90d9f6ed6 100644 --- a/courses/security/3-first-audit/10-exploit-public-data/+page.md +++ b/courses/security/3-first-audit/10-exploit-public-data/+page.md @@ -40,7 +40,7 @@ We see a problem on the very next line. This function _doesn't take_ a parameter Let's take a look at the function itself. - +::image{src='/security-section-3/10-exploit-public-data/public-data1.png' style='width: 100%; height: auto;'} The function looks great! Adhering to the required access control, we can be sure only the owner can call this function. @@ -56,7 +56,7 @@ I'll give you a hint: `State Variables`.
The Vulnerability - + ::image{src='/security-section-3/10-exploit-public-data/public-data2.png' style='width: 100%; height: auto;'} We've uncovered a major flaw in the business logic of this protocol. It's best we make a note of this. diff --git a/courses/security/3-first-audit/12-protocol-tests/+page.md b/courses/security/3-first-audit/12-protocol-tests/+page.md index d6883f505..2f275f917 100644 --- a/courses/security/3-first-audit/12-protocol-tests/+page.md +++ b/courses/security/3-first-audit/12-protocol-tests/+page.md @@ -6,7 +6,7 @@ _Follow along with this video:_ --- - +::image{src='/security-section-3/12-protocol-tests/protocol-tests1.png' style='width: 100%; height: auto;'} As security researchers our job is to ultimatly do what's necessary to make a protocol more secure. While we've thoroughly examined everything within scope of `PasswordStore` there can be some value in expanding our recon. @@ -27,7 +27,7 @@ The above will run all current tests, to check `coverage` we'll use: forge coverage ``` - +::image{src='/security-section-3/12-protocol-tests/protocol-tests2.png' style='width: 100%; height: auto;'} Wow! Our coverage looks great...right? It's important to note that coverage may be a vanity metric and not truly representative of what's being tested for. If we look closely at the tests included, we can see the a major vulnerability we found (`Access Control`) wasn't tested for at all. diff --git a/courses/security/3-first-audit/15-description/+page.md b/courses/security/3-first-audit/15-description/+page.md index 0eaa2f509..61521e192 100644 --- a/courses/security/3-first-audit/15-description/+page.md +++ b/courses/security/3-first-audit/15-description/+page.md @@ -40,7 +40,7 @@ I show one such method of reading any data off chain below. This looks good, but we can do even better. The bigger a codebase, the more our variables and references are going to get lost. We can fight this with a little bit of markdown formatting and standardizing our naming conventions. - +::image{src='/security-section-3/15-description/description1.png' style='width: 100%; height: auto;'} Consider the above adjustments to our references in the description. By wrapping the variable and function name in backticks we're able to highlight them. Additionally we're prepended the names with reference to the contract in which they're found. diff --git a/courses/security/3-first-audit/16-proof-of-code/+page.md b/courses/security/3-first-audit/16-proof-of-code/+page.md index a65e6ad7f..5acd9c0e3 100644 --- a/courses/security/3-first-audit/16-proof-of-code/+page.md +++ b/courses/security/3-first-audit/16-proof-of-code/+page.md @@ -44,7 +44,7 @@ make deploy Foundry allows us to check the storage of a deployed contract with a very simple `cast` command. For this we'll need to recall to which storage slot the `s_password` variable is assigned. - +::image{src='/security-section-3/16-proof-of-code/proof-of-code1.png' style='width: 100%; height: auto;'} With this consideration we can run the command `cast storage
` like this (_your address may be different_). @@ -75,17 +75,17 @@ And we've done it. In a few quick commands we've shown that the data our client
Finding Report ### [S-#] Storing the password on-chain makes it visible to anyone and no longer private -
-
+:br +:br **Description:** All data stored on chain is public and visible to anyone. The `PasswordStore::s_password` variable is intended to be hidden and only accessible by the owner through the `PasswordStore::getPassword` function. -
-
+:br +:br I show one such method of reading any data off chain below. -
-
+:br +:br **Impact:** Anyone is able to read the private password, severaly breaking the functionality of the protocol. -
-
+:br +:br **Proof of Concept:**The below test case shows how anyone could read the password directly from the blockchain. We use foundry's cast tool to read directly from the storage of the contract, without being the owner. Create a locally running chain @@ -114,7 +114,7 @@ And get an output of: myPassword -
+:br **Recommended Mitigation:**
diff --git a/courses/security/3-first-audit/17-recommended-mitigation/+page.md b/courses/security/3-first-audit/17-recommended-mitigation/+page.md index a3c29d648..54a94609c 100644 --- a/courses/security/3-first-audit/17-recommended-mitigation/+page.md +++ b/courses/security/3-first-audit/17-recommended-mitigation/+page.md @@ -75,22 +75,22 @@ Here's our report now:
Finding Report -
+:br ### [S-#] Storing the password on-chain makes it visible to anyone and no longer private -
-
+:br +:br **Description:** All data stored on chain is public and visible to anyone. The `PasswordStore::s_password` variable is intended to be hidden and only accessible by the owner through the `PasswordStore::getPassword` function. -
-
+:br +:br I show one such method of reading any data off chain below. -
-
+:br +:br **Impact:** Anyone is able to read the private password, severaly breaking the functionality of the protocol. -
-
+:br +:br **Proof of Concept:** The below test case shows how anyone could read the password directly from the blockchain. We use foundry's cast tool to read directly from the storage of the contract, without being the owner. -
-
+:br +:br Create a locally running chain @@ -104,10 +104,10 @@ Run the storage tool cast storage 1 --rpc-url http://127.0.0.1:8545 -
+:br *We use 1 because that's the storage slot of `PasswordStore::s_password`.* -
-
+:br +:br You'll get an output that looks like this: 0x6d7950617373776f726400000000000000000000000000000000000000000014 @@ -120,7 +120,7 @@ And get an output of: myPassword -
+:br **Recommended Mitigation:** Due to this, the overall architecture of the contract should be rethought. One could encrypt the password off-chain, and then store the encrypted password on-chain. This would require the user to remember another password off-chain to decrypt the stored password. However, you're also likely want to remove the view function as you wouldn't want the user to accidentally send a transaction with this decryption key.
diff --git a/courses/security/3-first-audit/2-etherscan/+page.md b/courses/security/3-first-audit/2-etherscan/+page.md index af75e76e7..8809cc046 100644 --- a/courses/security/3-first-audit/2-etherscan/+page.md +++ b/courses/security/3-first-audit/2-etherscan/+page.md @@ -40,7 +40,7 @@ You should recall the [**Rekt Test**](https://blog.trailofbits.com/2023/08/14/ca How does your client's protocol stand up against these questions? - +::image{src='/security-section-2/3-rekt/rekt1.png' style='width: 100%; height: auto;'} If all they've provided you is an Etherscan link - the answer is poorly. diff --git a/courses/security/3-first-audit/20-missing-access-controls-proof-of-code/+page.md b/courses/security/3-first-audit/20-missing-access-controls-proof-of-code/+page.md index 63cc80bf6..cc109497c 100644 --- a/courses/security/3-first-audit/20-missing-access-controls-proof-of-code/+page.md +++ b/courses/security/3-first-audit/20-missing-access-controls-proof-of-code/+page.md @@ -52,7 +52,7 @@ Let's write a `fuzz test` to check if in fact addresses other than the owner are Foundry will pass this function random addresses to see if the assert holds, based on the number of runs we've configured. - +::image{src='/security-section-3/20-missing-access-controls-proof-of-code/access-control1.png' style='width: 100%; height: auto;'} We can see that through 256 runs, our fuzz test passed! So indeed any address was able to call our `setPassword` function!. diff --git a/courses/security/3-first-audit/23-quick-primer-on-what-we-are-learning-next/+page.md b/courses/security/3-first-audit/23-quick-primer-on-what-we-are-learning-next/+page.md index b6518d05d..359707911 100644 --- a/courses/security/3-first-audit/23-quick-primer-on-what-we-are-learning-next/+page.md +++ b/courses/security/3-first-audit/23-quick-primer-on-what-we-are-learning-next/+page.md @@ -16,4 +16,4 @@ Secondly, we need to convert our `findings.md` - a markdown file - into a profes Let's get started with `determining a finding's severity`. - +::image{src='/security-section-3/23-quick-primer/primer1.png' style='width: 100%; height: auto;'} diff --git a/courses/security/3-first-audit/25-assesing-highs/+page.md b/courses/security/3-first-audit/25-assesing-highs/+page.md index a8ed13060..1feac3584 100644 --- a/courses/security/3-first-audit/25-assesing-highs/+page.md +++ b/courses/security/3-first-audit/25-assesing-highs/+page.md @@ -62,7 +62,7 @@ The answer is - _very likely_. There's nothing stopping any malicious actor from Applying our assessment to our finding title should look like this: - +::image{src='/security-section-3/25-assessing-highs/severity1.png' style='width: 100%; height: auto;'} > Pro-tip: We should try to arrange our findings in our report from High -> Low and from Worst -> Least Offenders diff --git a/courses/security/3-first-audit/27-timeboxing/+page.md b/courses/security/3-first-audit/27-timeboxing/+page.md index 05468b0d8..992e0ae9a 100644 --- a/courses/security/3-first-audit/27-timeboxing/+page.md +++ b/courses/security/3-first-audit/27-timeboxing/+page.md @@ -14,10 +14,10 @@ Take a moment to consider what you would do in a `live audit` situation. Conside
The Answer -
+:br Maybe. -
-
+:br +:br Honestly, we can always look at one more line of code. We can always further scrutinize a repo. At some point however, we have to say "I'm done." diff --git a/courses/security/3-first-audit/28-making-a-pdf/+page.md b/courses/security/3-first-audit/28-making-a-pdf/+page.md index ab7eb8426..8da8fdd8a 100644 --- a/courses/security/3-first-audit/28-making-a-pdf/+page.md +++ b/courses/security/3-first-audit/28-making-a-pdf/+page.md @@ -97,7 +97,7 @@ Ok, this wasn't easy and there are admittedly a tonned of potential pitfalls alo - The file may be hidden - files prepended with `.` are often hidden. You can reveal all files in a directory with the command `ls -a` - The file may be elsewhere - navigate back in directories (`cd ..`) until you reach one that looks like this - + ::image{src='/security-section-3/28-making-a-pdf/making-a-pdf1.png' style='width: 75%; height: auto;'} ...from here navigate to `usr/share/pandoc/data/templates`. In here you will find existing templates and this is where `eisvogel.latex` should be added. diff --git a/courses/security/3-first-audit/29-building-your-portfolio/+page.md b/courses/security/3-first-audit/29-building-your-portfolio/+page.md index f899e62ff..b6c6d002f 100644 --- a/courses/security/3-first-audit/29-building-your-portfolio/+page.md +++ b/courses/security/3-first-audit/29-building-your-portfolio/+page.md @@ -14,13 +14,13 @@ Now that we've done all this amazing work, we absolutely need to show it off. Th Create a new repository on your GitHub profile. Name it whatever you'd like. I'm going to name mine `updraft-security-portfolio`. - +::image{src='/security-section-3/29-building-your-portfolio/portfolio1.png' style='width: 75%; height: auto;'} --- Next, select `upload an existing file`. - +::image{src='/security-section-3/29-building-your-portfolio/portfolio2.png' style='width: 75%; height: auto;'} Now, rename your report something appropriate. It's important to date your audit reports! I'll name mine `2023-12-19 PasswordStore Audit Report`. @@ -28,7 +28,7 @@ Now, rename your report something appropriate. It's important to date your audit Drag and drop your PDF into the available space on GitHub. In VS Code you can `right-click` your PDF and select `Reveal in File Explorer` or `Reveal in Finder` for PC and Mac respectively. - +::image{src='/security-section-3/29-building-your-portfolio/portfolio3.png' style='width: 75%; height: auto;'} --- diff --git a/courses/security/3-first-audit/3-details/+page.md b/courses/security/3-first-audit/3-details/+page.md index e346b3b16..ba8bfac02 100644 --- a/courses/security/3-first-audit/3-details/+page.md +++ b/courses/security/3-first-audit/3-details/+page.md @@ -40,7 +40,7 @@ Once our client has provided answers to the above and provided an updated codeba Your client should have provided you a commit hash. By navigating to the GitHub Repo's commit history, you can used the first `7 characters` of the commit hash to find the exact version of the repo to focus on. We'll be going over cloning this locally later in the course. - +::image{src='/security-section-3/3-details/details2.png' style='width: 100%; height: auto;'} Let's go through the client's submitted details. diff --git a/courses/security/3-first-audit/4-cloc/+page.md b/courses/security/3-first-audit/4-cloc/+page.md index ccb6f2793..dd30a0da6 100644 --- a/courses/security/3-first-audit/4-cloc/+page.md +++ b/courses/security/3-first-audit/4-cloc/+page.md @@ -46,7 +46,7 @@ cloc ./src/ This is what the output might look like: - +::image{src='/security-section-3/4-cloc/cloc1.png' style='width: 100%; height: auto;'} ### The Importance of Knowing Your Codebase Size diff --git a/courses/security/3-first-audit/6-process-tincho/+page.md b/courses/security/3-first-audit/6-process-tincho/+page.md index 0d1bdf1f3..9d8a10288 100644 --- a/courses/security/3-first-audit/6-process-tincho/+page.md +++ b/courses/security/3-first-audit/6-process-tincho/+page.md @@ -47,7 +47,7 @@ It's recommended to start with the smaller and more manageable contracts and bui There's a point in an audit where your frame of mind should switch to an adversarial one. You should be thinking _"How can I break this..."_ - +::image{src='/security-section-3/6-the-tincho/tincho1.png' style='width: 100%; height: auto;'} Given even simple functions like above, we should be asking ourselves diff --git a/courses/security/3-first-audit/7-context/+page.md b/courses/security/3-first-audit/7-context/+page.md index c4f632e78..025a3beec 100644 --- a/courses/security/3-first-audit/7-context/+page.md +++ b/courses/security/3-first-audit/7-context/+page.md @@ -22,7 +22,7 @@ If we're following `The Tincho` method, our first step is going to be reading th _Quick tip: Check if an extension must be installed for Vs Code if it's not working for you._ - +::image{src='/security-section-3/7-context/context2.png' style='width: 100%; height: auto;'} Already, we should be thinking about potential attack vectors with the information we've gleaned. @@ -36,17 +36,17 @@ Following Tincho's advice our next step will be to organize the files of the pro 1. Download and install the [**Solidity Metrics**](https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-metrics) extension for VS Code. - +::image{src='/security-section-3/7-context/context3.png' style='width: 100%; height: auto;'} 2. Once installed, you can right-click the appropriate folders to run the tool on and select `Solidity: Metrics` from the context menu. > _Pro-tip: If your repo has more than one applicable folder, you can CTRL + Click to select multiple simultaneously._ - +::image{src='/security-section-3/7-context/context4.png' style='width: 100%; height: auto;'} After generating the report, navigate to the command palette and locate 'export this metrics report'. Once exported, you'll have HTML access to the report for future reference. - +::image{src='/security-section-3/7-context/context5.png' style='width: 100%; height: auto;'} Applying Tincho's methodology to this process, we can: @@ -57,7 +57,7 @@ Applying Tincho's methodology to this process, we can: Some aspects I'll draw your attention to in this metrics report are the `Inhertance Graph`, `The Call Graph`, and `The Contracts Summary`. It's not super obvious with such a simple protocol, but these are going to provide valuable insight down the line. Familiarize yourself with them now (way at the bottom). - +::image{src='/security-section-3/7-context/context6.png' style='width: 100%; height: auto;'} Understanding your codebase and its functionalities is the first step towards securing it. diff --git a/courses/security/3-first-audit/8-understanding-the-code/+page.md b/courses/security/3-first-audit/8-understanding-the-code/+page.md index fb7544d6b..59477752e 100644 --- a/courses/security/3-first-audit/8-understanding-the-code/+page.md +++ b/courses/security/3-first-audit/8-understanding-the-code/+page.md @@ -38,7 +38,7 @@ pragma solidity 0.8.18; // Q: Is this the correct compiler version? Formatting our in-line comments in a reliable way will allow us to easily come back to these areas later by leveraging search. - +::image{src='/security-section-3/8-understanding-code/understanding1.png' style='width: 100%; height: auto;'} ### Taking Notes @@ -63,7 +63,7 @@ The intended functionality is pretty clear. Maybe we want to jot this down in ou Let's consider things upto our constructor. - +::image{src='/security-section-3/8-understanding-code/understanding2.png' style='width: 100%; height: auto;'} Everything looks great so far, the client is using some clear standard naming conventions. diff --git a/courses/security/4-puppy-raffle/1-introduction/+page.md b/courses/security/4-puppy-raffle/1-introduction/+page.md index a5c41e511..c1599a667 100644 --- a/courses/security/4-puppy-raffle/1-introduction/+page.md +++ b/courses/security/4-puppy-raffle/1-introduction/+page.md @@ -18,7 +18,7 @@ CodeHawks First Flights offer an excellent platform for budding smart contract s If you are a beginner, they are a perfect opportunity to get live auditing experience and build upon the things you've learnt in a practical setting. For experienced auditors, they serve as a chance to engage in the community and itterate on your established skills. - +::image{src='/security-section-4/1-introduction/introduction1.png' style='width: 100%; height: auto;'} We'll be going over how to submit an awesome competitive finding in this section. diff --git a/courses/security/4-puppy-raffle/10-sc-exploits-minimized/+page.md b/courses/security/4-puppy-raffle/10-sc-exploits-minimized/+page.md index 6ab968850..68a649095 100644 --- a/courses/security/4-puppy-raffle/10-sc-exploits-minimized/+page.md +++ b/courses/security/4-puppy-raffle/10-sc-exploits-minimized/+page.md @@ -21,7 +21,7 @@ This code above is going to cause something called a Denial of Service or DOS. In order to get a better understanding of this bug, let's look at a _minimized_ example of it. If you reference the [**sc-exploits-minimized**](https://github.com/Cyfrin/sc-exploits-minimized) repo, half way down you should see something like what's pictured below. - +::image{src='/security-section-4/10-sc-exploits-minimized/sc-exploits-minimized1.png' style='width: 75%; height: auto;'} This is an amazing resource to test your skills in general and familiarize yourself with common exploits. Addionally the `src` folder of `sc-exploits-minimized` contains minimalistic examples of a variety of vulnerabilities as well. diff --git a/courses/security/4-puppy-raffle/11-exploit-denial-of-service/+page.md b/courses/security/4-puppy-raffle/11-exploit-denial-of-service/+page.md index 620c541e4..a340de361 100644 --- a/courses/security/4-puppy-raffle/11-exploit-denial-of-service/+page.md +++ b/courses/security/4-puppy-raffle/11-exploit-denial-of-service/+page.md @@ -44,7 +44,7 @@ We can see this in action by deploying our contract on Remix and comparing the g Here's what it looks like for the first four people calling the `enter` function. - +::image{src='/security-section-4/11-exploit-denial-of-service/exploit-denial-of-service1.png' style='width: 75%; height: auto;'} This kind of behavior raises questions about fairness and ultimately is going to lead to a `denial of service` in that it will become impractical for anyone to interact with this function, because gas costs will be too high. @@ -105,7 +105,7 @@ assert(gasCostB > gasCostA); If we run this test with `forge test --mt test_denialOfService -vvv` we see that the test indeed passes and we get a print out corroborating the vulnerability! - +::image{src='/security-section-4/11-exploit-denial-of-service/exploit-denial-of-service2.png' style='width: 75%; height: auto;'} I challenge you to play with this test a little bit and customize it. See if you can adjust it to print out the gas costs with 1000 entrants! diff --git a/courses/security/4-puppy-raffle/13-dos-poc/+page.md b/courses/security/4-puppy-raffle/13-dos-poc/+page.md index e0388acda..aea8cd85b 100644 --- a/courses/security/4-puppy-raffle/13-dos-poc/+page.md +++ b/courses/security/4-puppy-raffle/13-dos-poc/+page.md @@ -96,7 +96,7 @@ function testDenialOfService() public { Running the command `forge test --mt testDenialOfService -vvv` should give us an output like this: - +::image{src='/security-section-4/13-dos-poc/dos-poc1.png' style='width: 75%; height: auto;'} Now let's do the same thing for the second 100 players! We'll need to add something like this to our test. @@ -119,7 +119,7 @@ assert(gasUsedFirst < gasUsedSecond); If we rerun our test we can see.. Our test passes! The second 100 players are paying _a LOT_ more and are at a significant disadvantage! - +::image{src='/security-section-4/13-dos-poc/dos-poc2.png' style='width: 75%; height: auto;'}
diff --git a/courses/security/4-puppy-raffle/14-dos-reporting/+page.md b/courses/security/4-puppy-raffle/14-dos-reporting/+page.md index f24482030..9a3b017a7 100644 --- a/courses/security/4-puppy-raffle/14-dos-reporting/+page.md +++ b/courses/security/4-puppy-raffle/14-dos-reporting/+page.md @@ -219,7 +219,7 @@ function testDenialOfService() public { ```
-
+:br **Recommended Mitigations:** diff --git a/courses/security/4-puppy-raffle/15-dos-mitigation/+page.md b/courses/security/4-puppy-raffle/15-dos-mitigation/+page.md index dc3e19f6a..2b203f56f 100644 --- a/courses/security/4-puppy-raffle/15-dos-mitigation/+page.md +++ b/courses/security/4-puppy-raffle/15-dos-mitigation/+page.md @@ -126,7 +126,7 @@ function testDenialOfService() public { ``` -
+:br **Recommended Mitigations:** There are a few recommended mitigations. diff --git a/courses/security/4-puppy-raffle/16-exploit-business-logic-edge-case/+page.md b/courses/security/4-puppy-raffle/16-exploit-business-logic-edge-case/+page.md index c9322df7b..116ef1415 100644 --- a/courses/security/4-puppy-raffle/16-exploit-business-logic-edge-case/+page.md +++ b/courses/security/4-puppy-raffle/16-exploit-business-logic-edge-case/+page.md @@ -53,7 +53,7 @@ I think we may have stumbled upon our next bug. The logic here has a problem. Ca
The Problem -
+:br When looking at this function, we have to ask _"Why is this returning zero?"_ diff --git a/courses/security/4-puppy-raffle/18-exploit-reentrancy/+page.md b/courses/security/4-puppy-raffle/18-exploit-reentrancy/+page.md index a4fb39d58..f1e342b60 100644 --- a/courses/security/4-puppy-raffle/18-exploit-reentrancy/+page.md +++ b/courses/security/4-puppy-raffle/18-exploit-reentrancy/+page.md @@ -16,7 +16,7 @@ slither . Looking through the output, we can see `Slither` is in fact detecting things in our `refund` function! - +::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy1.png' style='width: 75%; height: auto;'} ### What is a re-entrancy attack and how does it work? @@ -53,17 +53,17 @@ User A (balance 10 ether) can deposit funds 1. deposit{value: 10 ether} -
userBalance[UserA] = 10 ether -
contract balance = 10 ether -
User A balance = 0 ether + :bruserBalance[UserA] = 10 ether + :brcontract balance = 10 ether + :brUser A balance = 0 ether And then withdraw them 2. withdrawBalance -
userBalance[UserA] = 0 ether -
contract balance = 0 ether -
User A balance = 10 ether + :bruserBalance[UserA] = 0 ether + :brcontract balance = 0 ether + :brUser A balance = 10 ether The order of operations is reeally important in these situations. In our `withdrawBalance` function, we see that the function is making an external call _before_ updating the state of the contract. @@ -126,7 +126,7 @@ It calls the `withdrawBalance` function again! Because our previous `withdrawBal Let's look at this all put together. - +::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy2.png' style='width: 75%; height: auto;'} ### Wrap Up @@ -134,6 +134,6 @@ Re-entrancy is a a big deal and it's very impactful when it happens. We're reall At it's most minimalistic, re-entrancy generates a loop that continually drains funds from a protocol. - +::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy3.png' style='width: 75%; height: auto;'} Our next lesson is going to be a hands on example of this vulnerability in Remix. Let's see what this exploit is like in action. diff --git a/courses/security/4-puppy-raffle/19-reentrancy-remix-example/+page.md b/courses/security/4-puppy-raffle/19-reentrancy-remix-example/+page.md index d21606bc7..a038ed0a6 100644 --- a/courses/security/4-puppy-raffle/19-reentrancy-remix-example/+page.md +++ b/courses/security/4-puppy-raffle/19-reentrancy-remix-example/+page.md @@ -54,15 +54,15 @@ Once you're in remix with the re-entrancy examples open, begin by compiling and _Be sure to deploy both contracts, first `ReentrancyVictim` then `ReentrancyAttacker`_ - +::image{src='/security-section-4/19-reentrancy-remix/reentrancy-remix1.png' style='width: 75%; height: auto;'} Both contracts should have 0 balance. Begin by having a sucker deposit 5 ether into `ReentrancyVictim` contract. - +::image{src='/security-section-4/19-reentrancy-remix/reentrancy-remix2.png' style='width: 75%; height: auto;'} Now, change the account/wallet you're calling functions from (near the top). Our `ReentrancyAttacker::attack` function requires at least 1 ether. Once that's set and our attack function is called... - +::image{src='/security-section-4/19-reentrancy-remix/reentrancy-remix3.png' style='width: 75%; height: auto;'} The attacker has made off with all of the protocol's ETH! diff --git a/courses/security/4-puppy-raffle/20-reentrancy-mitigation/+page.md b/courses/security/4-puppy-raffle/20-reentrancy-mitigation/+page.md index 56d12d1d1..ba93510d1 100644 --- a/courses/security/4-puppy-raffle/20-reentrancy-mitigation/+page.md +++ b/courses/security/4-puppy-raffle/20-reentrancy-mitigation/+page.md @@ -45,15 +45,15 @@ Our function has no checks, but simply by reordering things this way, with our e First, let's make sure we've re-ordered things in our contract. - +::image{src='/security-section-4/20-reentrancy-mitigation/reentrancy-mitigation1.png' style='width: 75%; height: auto;'} Now fund your victim contract and try calling the `attack` function with a second wallet address, as we did before. - +::image{src='/security-section-4/20-reentrancy-mitigation/reentrancy-mitigation2.png' style='width: 75%; height: auto;'} It reverts! So, what's happening here? - +::image{src='/security-section-4/20-reentrancy-mitigation/reentrancy-mitigation3.png' style='width: 75%; height: auto;'} ### Alternative Mitigation diff --git a/courses/security/4-puppy-raffle/22-reentrancy-recap/+page.md b/courses/security/4-puppy-raffle/22-reentrancy-recap/+page.md index f709f5d83..6ad65f503 100644 --- a/courses/security/4-puppy-raffle/22-reentrancy-recap/+page.md +++ b/courses/security/4-puppy-raffle/22-reentrancy-recap/+page.md @@ -10,13 +10,13 @@ _Follow along with this video:_ At it's most minimalistic, a re-entrancy attack looks like this: - +::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy3.png' style='width: 75%; height: auto;'} A reentrancy attack occurs when an attacker takes advantage of the recursive calling capability of a contract. By repeatedly calling a function within a contract, the attacker can withdraw funds or manipulate contract state before the initial function call is resolved, often leading to the theft of funds or other unintended consequences. As a more indepth reference: - +::image{src='/security-section-4/18-exploit-reentrancy/exploit-reentrancy2.png' style='width: 75%; height: auto;'} We learnt that re-entrancy is a _very_ common attack vector and walked through how to indentify and reproduce the vulnerability both in [**Remix**](https://remix.ethereum.org/#url=https://github.com/Cyfrin/sc-exploits-minimized/blob/main/src/reentrancy/Reentrancy.sol&lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.20+commit.a1b79de6.js) and locally as well as how to test for them. @@ -75,7 +75,7 @@ contract ReentrancyTest is Test { ```
-
+:br Additionally, we learnt that `static analysis` tools like `Slither` can even catch this vulnerability (though not always)! @@ -109,7 +109,7 @@ function withdrawBalance() public { ``` -
+:br Lastly, we learnt how this problem still plagues us today. Through this [**repo**](https://github.com/pcaversaccio/reentrancy-attacks) managed by Pascal et al, we can see a horrifying list, 7 years long, of just this single attack vector. We also uncovered a case study in [**The DAO hack**](https://medium.com/@zhongqiangc/smart-contract-reentrancy-thedao-f2da1d25180c) and saw just how severe this issue can be. diff --git a/courses/security/4-puppy-raffle/23-reentrancy-poc/+page.md b/courses/security/4-puppy-raffle/23-reentrancy-poc/+page.md index 7f2b164a3..0d348f04d 100644 --- a/courses/security/4-puppy-raffle/23-reentrancy-poc/+page.md +++ b/courses/security/4-puppy-raffle/23-reentrancy-poc/+page.md @@ -128,7 +128,7 @@ contract ReentrancyAttacker { ``` -
+:br Alright, let's add this logic to our test. First we'll create an instance of the attacker contract and an attacker address, funding it with 1 ether. @@ -199,11 +199,11 @@ function test_reentrancyRefund() public { ``` -
+:br All we need to do now is run this test with the command `forge test --mt test_reentrancyRefund -vvv` and we should receive... - +::image{src='/security-section-4/23-reentrancy-poc/reentrancy-poc1.png' style='width: 75%; height: auto;'} ### Wrap Up diff --git a/courses/security/4-puppy-raffle/25-exploit-weak-randomness/+page.md b/courses/security/4-puppy-raffle/25-exploit-weak-randomness/+page.md index b6a5f4031..f5316c82c 100644 --- a/courses/security/4-puppy-raffle/25-exploit-weak-randomness/+page.md +++ b/courses/security/4-puppy-raffle/25-exploit-weak-randomness/+page.md @@ -18,11 +18,11 @@ slither . Running slither as above we can see it's output contains the following: - +::image{src='/security-section-4/25-exploit-weak-randomness/weak-randomness1.png' style='width: 75%; height: auto;'} So what is this detector telling us - that `PuppyRaffle.sol` is using weak PRNG or Pseudo Random Number Generation. We can navigate to the [**link provided**](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) for more information and a simplified example of this vulnerability. - +::image{src='/security-section-4/25-exploit-weak-randomness/weak-randomness2.png' style='width: 75%; height: auto;'} Beyond what's outlined here as a concern - that miners can influence global variables favorable - there's a lot more _weirdness_ that goes into random numbers on-chain. diff --git a/courses/security/4-puppy-raffle/26-weak-randomness-multiple-issues/+page.md b/courses/security/4-puppy-raffle/26-weak-randomness-multiple-issues/+page.md index 1011a2732..c1ddf6ce6 100644 --- a/courses/security/4-puppy-raffle/26-weak-randomness-multiple-issues/+page.md +++ b/courses/security/4-puppy-raffle/26-weak-randomness-multiple-issues/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ Let's look at a few ways that randomness, as we've seen in `PuppyRaffle` and our [**sc-exploits-minimized**](https://github.com/Cyfrin/sc-exploits-minimized) examples, can be manipulated. - +::image{src='/security-section-4/26-weak-randomness-issues/randomness-issues1.png' style='width: 75%; height: auto;'} ### block.timestamp diff --git a/courses/security/4-puppy-raffle/27-weak-randomness-case-study/+page.md b/courses/security/4-puppy-raffle/27-weak-randomness-case-study/+page.md index e209f9351..49ae72fa7 100644 --- a/courses/security/4-puppy-raffle/27-weak-randomness-case-study/+page.md +++ b/courses/security/4-puppy-raffle/27-weak-randomness-case-study/+page.md @@ -95,7 +95,7 @@ The second line is where an assertion is made that the minted NFT has the desire The attacking contract called this mint function and reverted for over 6 hours. Spending ~$20,000/hour in gas until they minted the rare NFT they wanted Meebit #16647. The NFT possessed a Visitor trait and sold for ~$700,000. - +::image{src='/security-section-4/27-weak-randomness-case-study/meebit1.png' style='width: 75%; height: auto;'} ### Wrap Up diff --git a/courses/security/4-puppy-raffle/29-exploit-integer-overflow/+page.md b/courses/security/4-puppy-raffle/29-exploit-integer-overflow/+page.md index 70c7b8542..c0c6dff3e 100644 --- a/courses/security/4-puppy-raffle/29-exploit-integer-overflow/+page.md +++ b/courses/security/4-puppy-raffle/29-exploit-integer-overflow/+page.md @@ -62,15 +62,15 @@ We've provide a [**Remix example**](https://remix.ethereum.org/#url=https://gith By compiling and deploying our `Overflow.sol` contract, we should be met with this: - +::image{src='/security-section-4/29-exploit-integer-overflow/overflow1.png' style='width: 75%; height: auto;'} The max value of a uint8 is 255. Our `count` variable starts at 0, so let's just pick a number to start with, say 200. - +::image{src='/security-section-4/29-exploit-integer-overflow/overflow2.png' style='width: 75%; height: auto;'} Calling increment updates our `count` variable. No problem so far. Now let's add 60 to our number. `count` should total 260, but what do you think we'll get? - +::image{src='/security-section-4/29-exploit-integer-overflow/overflow3.png' style='width: 75%; height: auto;'} We get 4! This is because our integer is hitting the cap of 255, and then wrapping back to 0. @@ -110,7 +110,7 @@ contract PrecisionLoss { } ``` - +::image{src='/security-section-4/29-exploit-integer-overflow/overflow4.png' style='width: 75%; height: auto;'} At its root, this is because Solidity doesn't support float point numbers. Any time we're performing a division operation, we need to be aware of this potential loss of precision. diff --git a/courses/security/4-puppy-raffle/3-phase-1-scoping/+page.md b/courses/security/4-puppy-raffle/3-phase-1-scoping/+page.md index 32974d106..602798077 100644 --- a/courses/security/4-puppy-raffle/3-phase-1-scoping/+page.md +++ b/courses/security/4-puppy-raffle/3-phase-1-scoping/+page.md @@ -12,7 +12,7 @@ Now that you've **definitely** tried reviewing the codebase on your own, let's s Take a look at the [**Puppy Raffle Repo**](https://github.com/Cyfrin/4-puppy-raffle-audit)'s README - +::image{src='static/security-section-4/3-phase-1-scoping/phase-1-scoping1.png' style='width: 50%; height: auto;'} ### README Overview @@ -41,7 +41,7 @@ make Once we've run our `make` command, we should check out the protocol tests. I like to start by running `forge coverage` to see what kind of baseline we're starting with. - +::image{src='/security-section-4/3-phase-1-scoping/phase-1-scoping2.png' style='width: 50%; height: auto;'} Thing's don't look great. @@ -62,7 +62,7 @@ We also see exactly which contracts are under review. Moving on, we should take notice of the **Compatibilities** section. - +::image{src='/security-section-4/3-phase-1-scoping/phase-1-scoping3.png' style='width: 50%; height: auto;'} That Solc version is strange - definitely make note of it. diff --git a/courses/security/4-puppy-raffle/31-unsafe-casting/+page.md b/courses/security/4-puppy-raffle/31-unsafe-casting/+page.md index c95ea9235..c796a1d4d 100644 --- a/courses/security/4-puppy-raffle/31-unsafe-casting/+page.md +++ b/courses/security/4-puppy-raffle/31-unsafe-casting/+page.md @@ -23,7 +23,7 @@ Type: uint We've also learnt that adding any to this number is going to wrap around to 0 again, but what happens if we try to cast a larger number into this smaller container? - +::image{src='/security-section-4/31-unsafe-casting/unsafe-casting1.png' style='width: 75%; height: auto;'} We can see above that when `20e18` is cast as a `uint64` the returned value is actually the difference between `type(uint64).max` and `20e18`. diff --git a/courses/security/4-puppy-raffle/33-exploit-mishandling-of-eth/+page.md b/courses/security/4-puppy-raffle/33-exploit-mishandling-of-eth/+page.md index 1c4a6b3c7..0816f46d1 100644 --- a/courses/security/4-puppy-raffle/33-exploit-mishandling-of-eth/+page.md +++ b/courses/security/4-puppy-raffle/33-exploit-mishandling-of-eth/+page.md @@ -34,7 +34,7 @@ function testCantSendMoneyToRaffle() public { } ``` - +::image{src='/security-section-4/33-exploit-mishandling-eth/exploit-mishandling-eth1.png' style='width: 75%; height: auto;'} Running this test, we discover ... it passes! So we're done, right? Everything's secure? diff --git a/courses/security/4-puppy-raffle/34-mishandling-of-eth-minimized/+page.md b/courses/security/4-puppy-raffle/34-mishandling-of-eth-minimized/+page.md index 76653446c..824bcf280 100644 --- a/courses/security/4-puppy-raffle/34-mishandling-of-eth-minimized/+page.md +++ b/courses/security/4-puppy-raffle/34-mishandling-of-eth-minimized/+page.md @@ -20,7 +20,7 @@ We've done this a few times, so we should be familiar with the process - go ahea You'll likely be met with this message, `selfdestruct` is being heavily considered for deprecation, but for now this vulnerability still exists, so we can ignore this message for now. - +::image{src='/security-section-4/34-mishandling-eth-minimized/mishandling-eth-minimized1.png' style='width: 50%; height: auto;'}
SelfDestructMe.sol @@ -61,7 +61,7 @@ contract SelfDestructMe { ```
-
+:br `SelfDestructMe.sol` is a fairly straightforward contract at a glance, experiment with the basic functions of the contract as you wish. @@ -69,7 +69,7 @@ A user is able to deposit funds, which updates their balance as well as the `tot I've deposited 1 Ether to the contract, here. - +::image{src='/security-section-4/34-mishandling-eth-minimized/mishandling-eth-minimized2.png' style='width: 50%; height: auto;'} The issue comes from this line: @@ -83,17 +83,17 @@ This is **_false_**. Go ahead and deploy the `AttackSelfDestructMe.sol` contract. The constructor requires an attack target, so be sure to copy the address for `SelfDestructMe.sol` and pass it to your deploy. Give the contract a balance during deployment as well. - +::image{src='/security-section-4/34-mishandling-eth-minimized/mishandling-eth-minimized3.png' style='width: 50%; height: auto;'} Now, when the attack function is called, `selfdestruct` will be triggered, and we expect to see our 5 Ether forced onto `SelfDestructMe.sol`. And, that's exactly what we see: - +::image{src='/security-section-4/34-mishandling-eth-minimized/mishandling-eth-minimized4.png' style='width: 50%; height: auto;'} Lastly, try calling the `withdraw` function on `SelfDestructMe.sol`. It reverts! The contract's accounting has been broken and it's balance is now stuck! - +::image{src='/security-section-4/34-mishandling-eth-minimized/mishandling-eth-minimized5.png' style='width: 75%; height: auto;'} ### Wrap Up diff --git a/courses/security/4-puppy-raffle/36-recon-continued-3/+page.md b/courses/security/4-puppy-raffle/36-recon-continued-3/+page.md index 1e0d9f703..e3cba9f8a 100644 --- a/courses/security/4-puppy-raffle/36-recon-continued-3/+page.md +++ b/courses/security/4-puppy-raffle/36-recon-continued-3/+page.md @@ -55,7 +55,7 @@ function _isActivePlayer() internal view returns (bool) { Now, we haven't seen this referenced anywhere before now, we may want to simply investigate when this function is being used. - +::image{src='/security-section-4/36-recon-continued-3/recon-continued1.png' style='width:75%; height:auto;'} Ironically, it seems this function isn't being used anywhere in our protocol! diff --git a/courses/security/4-puppy-raffle/38-info-and-gas-findings/+page.md b/courses/security/4-puppy-raffle/38-info-and-gas-findings/+page.md index 16322bf41..f6332adf5 100644 --- a/courses/security/4-puppy-raffle/38-info-and-gas-findings/+page.md +++ b/courses/security/4-puppy-raffle/38-info-and-gas-findings/+page.md @@ -18,7 +18,7 @@ Let's review a few recommendations we could make to improve the code for this pr The first thing we notice, at the very top of this repo are the naming conventions used for storage variables. - +::image{src='/security-section-4/38-info-and-gas/info-and-gas1.png' style='width: 75%; height: auto;'} A convention I like to use for storage variables is the `s_variableName` convention! So this may be an informational finding we would want to submit. @@ -62,7 +62,7 @@ This section of the OpenZepplin repo is kept updated with known security vulnera By clicking on one of the advisories, we get a detailed breakdown including the affected versions. - +::image{src='/security-section-4/38-info-and-gas/info-and-gas2.png' style='width: 75%; height: auto;'} ### Gas diff --git a/courses/security/4-puppy-raffle/4-tooling-slither/+page.md b/courses/security/4-puppy-raffle/4-tooling-slither/+page.md index 7faf12b88..2a355a51f 100644 --- a/courses/security/4-puppy-raffle/4-tooling-slither/+page.md +++ b/courses/security/4-puppy-raffle/4-tooling-slither/+page.md @@ -12,7 +12,7 @@ Auditing smart contracts is an arduous yet essential task in the blockchain real ### Static Analysis - Boosting Your Auditing Efficiency - +::image{src='/security-section-4/4-tooling-slither/tooling-slither1.png' style='width: 75%; height: auto;'} Static analysis is a method where code is checked for potential issues without actually executing it. Essentially, it's a way to "debug" your code by looking for specific keywords in a certain order or pattern. @@ -32,7 +32,7 @@ This document lists _all_ the vulnerabilities that Slither is checking for and r For example: - +::image{src='/security-section-4/4-tooling-slither/tooling-slither2.png' style='width: 75%; height: auto;'} This could have helped us with PasswordStore! It's easy to see how valuable these tools can be in making our work easier and more efficient. @@ -70,7 +70,7 @@ The output color codes potential issues: Here's an example of what some of these look like: - +::image{src='/security-section-4/4-tooling-slither/tooling-slither3.png' style='width: 75%; height: auto;'} ### Wrap Up diff --git a/courses/security/4-puppy-raffle/40-slither-walkthrough/+page.md b/courses/security/4-puppy-raffle/40-slither-walkthrough/+page.md index af1c21f49..aa0131e03 100644 --- a/courses/security/4-puppy-raffle/40-slither-walkthrough/+page.md +++ b/courses/security/4-puppy-raffle/40-slither-walkthrough/+page.md @@ -22,7 +22,7 @@ Start by running `slither .` just as before and let's dive into the output start ### Slither Highs - +::image{src='/security-section-4/40-slither-walkthrough/slither-walkthrough1.png' style='width: 75%; height: auto;'} **1. Sends Eth to Arbitrary User** @@ -73,7 +73,7 @@ uint256(keccak256(abi.encodePacked(msg.sender, block.timestamp, block.difficulty ### Slither Mediums - +::image{src='/security-section-4/40-slither-walkthrough/slither-walkthrough2.png' style='width: 75%; height: auto;'} **1. Performs a Multiplication on the Result of a Division** @@ -146,7 +146,7 @@ You can remove these warning from your `Slither` report by navigating to the res ### Slither Lows - +::image{src='/security-section-4/40-slither-walkthrough/slither-walkthrough3.png' style='width: 75%; height: auto;'} **1. Lacks a Zero Check** diff --git a/courses/security/4-puppy-raffle/42-test-coverage/+page.md b/courses/security/4-puppy-raffle/42-test-coverage/+page.md index b916a7ef5..7da712f21 100644 --- a/courses/security/4-puppy-raffle/42-test-coverage/+page.md +++ b/courses/security/4-puppy-raffle/42-test-coverage/+page.md @@ -25,7 +25,7 @@ Test coverage is up next, this should be easy. > **Remember:** you can check test coverage with the command `forge coverage`. - +::image{src='/security-section-4/42-test-coverage/test-coverage1.png' style='width: 100%; height: auto;'} This is ... pretty bad. In the context of a competitive audit, this may be less important, but in a private audit we should absolutely be calling this out as an informational. Assuring a repo has an adequate test coverage helps a protocol avoid overlooking areas of their code. diff --git a/courses/security/4-puppy-raffle/43-phase-4-reporting-primer/+page.md b/courses/security/4-puppy-raffle/43-phase-4-reporting-primer/+page.md index 92b30893a..efaed5899 100644 --- a/courses/security/4-puppy-raffle/43-phase-4-reporting-primer/+page.md +++ b/courses/security/4-puppy-raffle/43-phase-4-reporting-primer/+page.md @@ -16,7 +16,7 @@ In audits and especially in bug bounties, it is your obligation to convince the BUT. Before we walkthrough another report, I want to introduce you to competitive audits. We're going to go over what they are, how they differ from private audits and how to submit a finding for them. - +::image{src='/security-section-4/43-reporting-primer/reporting-primer1.svg' style='width: 75%; height: auto;'} --- diff --git a/courses/security/4-puppy-raffle/44-what-is-a-competitive-audit/+page.md b/courses/security/4-puppy-raffle/44-what-is-a-competitive-audit/+page.md index 07ddbacff..9d15cc2a9 100644 --- a/courses/security/4-puppy-raffle/44-what-is-a-competitive-audit/+page.md +++ b/courses/security/4-puppy-raffle/44-what-is-a-competitive-audit/+page.md @@ -20,7 +20,7 @@ In a competitive audit, you're competing to find _bugs_, you're paid if you find We can see how these payouts work by looking at the [**CodeHawks Docs**](https://docs.codehawks.com/). Findings rewards are ultimately broken down into shares and severity, where the system rewards finding more unique, difficult to find bugs. - +::image{src='/security-section-4/44-what-is-a-competitive-audit/competitive-audit1.png' style='width: 75%; height: auto;'} You can also find examples of scenarios and calculations on the [**CodeHawks Docs**](https://docs.codehawks.com/hawks-auditors/payouts). @@ -28,7 +28,7 @@ You can also find examples of scenarios and calculations on the [**CodeHawks Doc The quality of competitive audits has been found to be - incredible. To use a past contest on CodeHawks as an example, the Beedle-Fi audit resulted in a staggering number of findings. - +::image{src='/security-section-4/44-what-is-a-competitive-audit/competitive-audit2.png' style='width: 75%; height: auto;'} Security reviews of this nature consistently find more bugs that private reviews _and_ they serve as the perfect platforms to gain experience and build your security researcher career. diff --git a/courses/security/4-puppy-raffle/45-codehawks/+page.md b/courses/security/4-puppy-raffle/45-codehawks/+page.md index df178a516..ca74fc55e 100644 --- a/courses/security/4-puppy-raffle/45-codehawks/+page.md +++ b/courses/security/4-puppy-raffle/45-codehawks/+page.md @@ -16,11 +16,11 @@ Don't hesitate to jump in and get as much experience actually going through thes Your first step, of course will be to sign up to CodeHawks and create an account. You can begin by clicking `Become a Hawk` on the [**CodeHawks Homepage**](https://www.codehawks.com/) - +::image{src='/security-section-4/45-codehawks/codehawks1.png' style='width: 75%; height: auto;'} Connect the browser wallet of your choice when prompted and then fill out your profile information. - +::image{src='/security-section-4/45-codehawks/codehawks2.png' style='width: 75%; height: auto;'} > **Note:** CodeHawks pays out on Arbitrum in USDC, so ensure you're using an EVM compatible wallet to receive rewards! diff --git a/courses/security/4-puppy-raffle/46-submitting-a-competitive-audit-finding/+page.md b/courses/security/4-puppy-raffle/46-submitting-a-competitive-audit-finding/+page.md index c32ec3509..87c8e808b 100644 --- a/courses/security/4-puppy-raffle/46-submitting-a-competitive-audit-finding/+page.md +++ b/courses/security/4-puppy-raffle/46-submitting-a-competitive-audit-finding/+page.md @@ -14,7 +14,7 @@ We've come a long way in this guide, and now it's time to learn how to submit yo Navigate to an active CodeHawks First Flight and click the link `Submit a Finding`. - +::image{src='/security-section-4/46-submitting-competitive-finding/submitting-competitive-finding1.png' style='width: 75%; height: auto;'} Some of this should seem very familiar. We can enter a title and choose an appropriate severity. @@ -23,7 +23,7 @@ Some of this should seem very familiar. We can enter a title and choose an appro For `Relevant GitHub Links`, we're meant to provide a link, not just to the code base/contract, but to the specific lines we've identified as problematic. Using our DoS Vulnerability from `PuppyRaffle.sol` as an example, we can link directly to the loop in our `enterRaffle` function by right-clicking the line in GitHub and chooosing `copy permalink`. - +::image{src='/security-section-4/46-submitting-competitive-finding/submitting-competitive-finding2.png' style='width: 75%; height: auto;'} Take some time to view the README of the First Flight you're looking at. You'll find important information for the contest available such as: @@ -45,7 +45,7 @@ Once you're satisfied with how things look, click `Submit Finding`. This should Something to always strive for is quality in the write ups you submit. In competitive audits submitting a finding that is a duplicate with other auditors is common. Platforms will reward an attention to submission quality by choosing a `selected report`. This reports represent the best quality write up for a given vulnerability and these reports receive _bonus payouts_. - +::image{src='/security-section-4/46-submitting-competitive-finding/submitting-competitive-finding4.png' style='width: 75%; height: auto;'} ### Wrap Up diff --git a/courses/security/4-puppy-raffle/5-tooling-aderyn/+page.md b/courses/security/4-puppy-raffle/5-tooling-aderyn/+page.md index 534f5e33a..76ee0240e 100644 --- a/courses/security/4-puppy-raffle/5-tooling-aderyn/+page.md +++ b/courses/security/4-puppy-raffle/5-tooling-aderyn/+page.md @@ -18,7 +18,7 @@ Before we can use `Aderyn`, we'll need to first install `Rust`. Like `Slither`, Once `Rust` has been installed, you can run the command `cargo install Aderyn`. This will install our tool. - +::image{src='/security-section-4/5-tooling-aderyn/tooling-aderyn1.png' style='width: 75%; height: auto;'} > **Note:** If you've already installed Aderyn, this command will also update you to the current version. Your terminal will advise if the tool is already installed. diff --git a/courses/security/4-puppy-raffle/6-tooling-solidity-visual-developer/+page.md b/courses/security/4-puppy-raffle/6-tooling-solidity-visual-developer/+page.md index 002e0da5b..701b131ce 100644 --- a/courses/security/4-puppy-raffle/6-tooling-solidity-visual-developer/+page.md +++ b/courses/security/4-puppy-raffle/6-tooling-solidity-visual-developer/+page.md @@ -22,19 +22,19 @@ We also went over `Solidity Metrics` earlier, but let's take another look as `Pu Scrolling to the bottom of the `Solidity: Metrics` report, take a look at the `Inheritence Graph` - +::image{src='/security-section-4/6-tooling-svd/tooling-svd1.png' style='width: 75%; height: auto;'} From this illustration we can see that the contract `PuppyRaffle` is of types `ERC721` and `Ownable`. A little further down we see a `Call Graph` - +::image{src='/security-section-4/6-tooling-svd/tooling-svd2.png' style='width: 75%; height: auto;'} This provides us a clear reference of which functions are being called by which other functions! And finally `Solidity: Metrics` gives us a `Contract Summary` - +::image{src='/security-section-4/6-tooling-svd/tooling-svd3.png' style='width: 75%; height: auto;'} This is incredibly valuable. It provides is a clear breakdown of `Internal` vs `External functions` as well as identifies which functions are `payable` and can `modify state`! @@ -44,7 +44,7 @@ There's another tool I'll briefly mention - some developers swear by it. It's th In addition to providing very similar reporting as Solidity Metrics, the inheritence graph is interactive and it provides syntax highlighting in your code based on variable types. - +::image{src='/security-section-4/6-tooling-svd/tooling-svd4.png' style='width: 75%; height: auto;'} Check it out if you feel it would be useful for adding some clarity to your development and security reviews! diff --git a/courses/security/4-puppy-raffle/63-adding-the-audit-to-our-portfolio/+page.md b/courses/security/4-puppy-raffle/63-adding-the-audit-to-our-portfolio/+page.md index c7739489d..04caada2b 100644 --- a/courses/security/4-puppy-raffle/63-adding-the-audit-to-our-portfolio/+page.md +++ b/courses/security/4-puppy-raffle/63-adding-the-audit-to-our-portfolio/+page.md @@ -14,7 +14,7 @@ First step, let's add what we need to our `audit-data` folder. Boilerplating things is something you should get used to. This involves reusing assets and templating processes so that it's quick to get started. Here, we can grab our logo from our previous `PasswordStore` repo, and our formatted report template can be copied from [**`audit-report-templating`**](https://github.com/Cyfrin/audit-report-templating) repo into a new file we name `report-formatted.md` within our `audit-data` folder. - +::image{src='/security-section-4/63-pdf-report/pdf-report1.png' style='width: 75%; height: auto;'} With this template in place, we can just begin filling it out. Start by adding your name and details to customize the report. @@ -805,7 +805,7 @@ Similarly to the previous PDF generating lesson, I'll include some common pitfal - The file may be hidden - files prepended with `.` are often hidden. You can reveal all files in a directory with the command `ls -a` - The file may be elsewhere - navigate back in directories (`cd ..`) until you reach one that looks like this - + ::image{src='/security-section-3/28-making-a-pdf/making-a-pdf1.png' style='width: 75%; height: auto;'} ...from here navigate to `usr/share/pandoc/data/templates`. In here you will find existing templates and this is where `eisvogel.latex` should be added. diff --git a/courses/security/4-puppy-raffle/64-exercises/+page.md b/courses/security/4-puppy-raffle/64-exercises/+page.md index b4d54be23..2b0b31603 100644 --- a/courses/security/4-puppy-raffle/64-exercises/+page.md +++ b/courses/security/4-puppy-raffle/64-exercises/+page.md @@ -22,7 +22,7 @@ Ethernaut, is amazing. It's effectively a compilation of CTFs (capture the flags You _are_ expected to know a little bit of JavaScript for some of the functionality of `Ethernaut`, but with a little work you can deploy the instanced contracts and interact with them through `Foundry` or `Etherscan` as well. - +::image{src='/security-section-4/64-exercises/exercises1.png' style='width: 75%; height: auto;'} ### Damn Vulnerable DeFi @@ -34,7 +34,7 @@ Unfortunately DVD is _also_ written in `Hardhat`, so some JavaScript knowledge g What you can do, if you're not comfortable with `Hardhat` would be to copy the contracts that Damn Vulnerable Defi provides you into a Forge project and just try to break it locally. Each challenge in DVD provides you with your objectives. - +::image{src='/security-section-4/64-exercises/exercises2.png' style='width: 75%; height: auto;'} ### Case Studies diff --git a/courses/security/4-puppy-raffle/65-solodit/+page.md b/courses/security/4-puppy-raffle/65-solodit/+page.md index c166cd604..e43c747ae 100644 --- a/courses/security/4-puppy-raffle/65-solodit/+page.md +++ b/courses/security/4-puppy-raffle/65-solodit/+page.md @@ -18,11 +18,11 @@ Thus [**Solodit**](https://solodit.xyz/) was born. [**Solodit**](https://solodit Once logged in you should see something like this, a clean UI through which you can search and filter by anything you'd like. - +::image{src='/security-section-4/65-solodit/solodit1.png' style='width: 75%; height: auto;'} By navigating to the [**`Audits` menu**](https://solodit.xyz/audit), we can even see live and upcoming audit competitions as well as learn about types of audits such as the Multi-Phase Audit. - +::image{src='/security-section-4/65-solodit/solodit2.png' style='width: 75%; height: auto;'} In addition to this, Solodit aggregates open `bug bounties` as well as `leaderboard` positions across multiple auditing platforms. diff --git a/courses/security/4-puppy-raffle/7-recon-reading-docs/+page.md b/courses/security/4-puppy-raffle/7-recon-reading-docs/+page.md index dd0f66b23..bc45576a8 100644 --- a/courses/security/4-puppy-raffle/7-recon-reading-docs/+page.md +++ b/courses/security/4-puppy-raffle/7-recon-reading-docs/+page.md @@ -16,8 +16,8 @@ What we've been provided is a little sparse - but read through the README of [** About Puppy Raffle

- -
+::image{src='/security-section-4/7-recon-reading-docs/reading-docs1.svg' style='width: 75%; height: auto;'} +:br # Puppy Raffle diff --git a/courses/security/4-puppy-raffle/8-recon-reading-the-code/+page.md b/courses/security/4-puppy-raffle/8-recon-reading-the-code/+page.md index 624e7fe48..0a11540f4 100644 --- a/courses/security/4-puppy-raffle/8-recon-reading-the-code/+page.md +++ b/courses/security/4-puppy-raffle/8-recon-reading-the-code/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ What I like to do when first assessing a codebase is to start at the `main entry point`. Sometimes this area of a protocol may be a little unclear, but using Solidity: Metrics can help us out a lot. - +::image{src='/security-section-4/7-recon-reading-docs/reading-docs2.png' style='width: 75%; height: auto;'} Pay special attention to the functions marked `public` or `external`. Especially those which `modify state` or are `payable`. These are going to be certain potential attack vectors. diff --git a/courses/security/5-tswap/1-introduction/+page.md b/courses/security/5-tswap/1-introduction/+page.md index dddd6958b..4aed91844 100644 --- a/courses/security/5-tswap/1-introduction/+page.md +++ b/courses/security/5-tswap/1-introduction/+page.md @@ -14,7 +14,7 @@ We've got a _lot_ more to cover. Before we move on at all, you should take a look at this section on the [**GitHub Repo**](https://github.com/Cyfrin/security-and-auditing-full-course-s23?tab=readme-ov-file#-section-5-invariants--intro-to-defi--tswap-audit). You'll see right away - DO NOT LOOK AT THE CONTRACTS. - +::image{src='/security-section-5/1-introduction/intro1.png' style='width: 100%; height: auto;'} We're going to be learning how to use some advanced tooling and advanced testing concepts such as fuzzing, invariant testing and more. diff --git a/courses/security/5-tswap/10-stateful-and-stateless-fuzzing/+page.md b/courses/security/5-tswap/10-stateful-and-stateless-fuzzing/+page.md index 19906e525..91e8d3aff 100644 --- a/courses/security/5-tswap/10-stateful-and-stateless-fuzzing/+page.md +++ b/courses/security/5-tswap/10-stateful-and-stateless-fuzzing/+page.md @@ -64,7 +64,7 @@ function testIAlwaysGetZero(uint256 data) public { Instead of using a unit test to assess a single situation, we can leverage fuzz testing to test a wide range of scenarios for us. When we run this test now with `forge test --mt testIAlwaysGetZero` We can see that we do actually catch the broken invariant when `2` is passed as data to our function! - +::image{src='/security-section-5/10-stateful-and-stateless-fuzzing/stateful-and-stateless-fuzzing1.png' style='width: 100%; height: auto;'} Great job, Foundry! @@ -74,7 +74,7 @@ I do need to mention that it's not technically choosing random data it's `semi-r Now, an important concept to understand when running fuzz tests is that of `runs`. - +::image{src='/security-section-5/10-stateful-and-stateless-fuzzing/stateful-and-stateless-fuzzing2.png' style='width: 100%; height: auto;'} In the successful test above, I've highlighted the number of runs the fuzzer performed. This represents the number of times random inputs were passed to our test function. @@ -87,7 +87,7 @@ runs=1000 The resulting test: - +::image{src='/security-section-5/10-stateful-and-stateless-fuzzing/stateful-and-stateless-fuzzing3.png' style='width: 100%; height: auto;'} Higher runs will take longer to run, but will give your functions a more thorough coverage of potential cases. That's all there is to stateless fuzzing! @@ -164,7 +164,7 @@ function invariant_testAlwaysReturnsZero() public { And that's all that's required. Now Foundry will pass data to random functions (in this case our single function) over and over again while carrying state changes over from each run. - +::image{src='/security-section-5/10-stateful-and-stateless-fuzzing/stateful-and-stateless-fuzzing4.png' style='width: 100%; height: auto;'} In the screenshot above, the Foundry Fuzzer is passing 7 to our `doStuff` function (this is actually a coincidence lol), this is setting our `hiddenValue` to `7` because of: diff --git a/courses/security/5-tswap/12-stateless-fuzzing/+page.md b/courses/security/5-tswap/12-stateless-fuzzing/+page.md index 59022465f..2066b4d56 100644 --- a/courses/security/5-tswap/12-stateless-fuzzing/+page.md +++ b/courses/security/5-tswap/12-stateless-fuzzing/+page.md @@ -99,7 +99,7 @@ If things have been set up well we should be able to run the following command t forge test --mt testFuzzCatchesStateless -vvvv ``` - +::image{src='/security-section-5/12-stateless-fuzzing/stateless-fuzzing1.png' style='width: 100%; height: auto;'} We can see it doesn't take much for Foundry's Fuzzer to catch our edge case! When the argument `2` is passed, our function returns `0` _breaking our invariant_. diff --git a/courses/security/5-tswap/13-where-stateless-fuzzing-fails/+page.md b/courses/security/5-tswap/13-where-stateless-fuzzing-fails/+page.md index 5c3eb9750..d0d5304cc 100644 --- a/courses/security/5-tswap/13-where-stateless-fuzzing-fails/+page.md +++ b/courses/security/5-tswap/13-where-stateless-fuzzing-fails/+page.md @@ -62,7 +62,7 @@ contract StatefulFuzzCatchesTest is Test { A set up just like before, no curveballs. Let's see if it can catch the bug. - +::image{src='/security-section-5/13-where-stateless-fuzzing-fails/where-stateless-fuzzing-fails1.png' style='width: 100%; height: auto;'} No such luck. @@ -119,7 +119,7 @@ Let's run the test. forge test --mt statefulFuzz_catchesInvariant ``` - +::image{src='/security-section-5/13-where-stateless-fuzzing-fails/where-stateless-fuzzing-fails2.png' style='width: 100%; height: auto;'} Well, it catches something... but a closer look at the trace output reveals that it's actually reverting due to `arithmetic underflow or overflow (0x11)]`. This is good to know, but it's not actually breaking our invariant. This is where `fail_on_revert` comes into play. @@ -132,6 +132,6 @@ depth = 32 fail_on_revert = false ``` - +::image{src='/security-section-5/13-where-stateless-fuzzing-fails/where-stateless-fuzzing-fails3.png' style='width: 100%; height: auto;'} In this case our stateful fuzz test works perfectly! The trace indicates that our fuzzer passed `0` to the `changeValue` function, and then subsequently passed `0` to `doMoreMathAgain` resulting in a return of `0`, breaking our invariant! diff --git a/courses/security/5-tswap/14-fuzzing-where-method-1-fails/+page.md b/courses/security/5-tswap/14-fuzzing-where-method-1-fails/+page.md index fb6a5e363..e98e0ab32 100644 --- a/courses/security/5-tswap/14-fuzzing-where-method-1-fails/+page.md +++ b/courses/security/5-tswap/14-fuzzing-where-method-1-fails/+page.md @@ -169,7 +169,7 @@ function testStartingAmount() public view { } ``` - +::image{src='/security-section-5/14-fuzzing-where-method-1-fails/fuzzing-where-method-1-fails1.png' style='width: 100%; height: auto;'} Perfect! @@ -198,7 +198,7 @@ In this function, we're assuring the the fuzz tests will end with our `user` wit Let's run it with `forge test --mt statefulFuzz_TestInvariantBreaks` - +::image{src='/security-section-5/14-fuzzing-where-method-1-fails/fuzzing-where-method-1-fails2.png' style='width: 100%; height: auto;'} It passes! Boom, safe and secure, right? Wrong. @@ -206,7 +206,7 @@ Look closely and we see `2048` calls were made by our test, but `2048` of them r If we navigate back to our `foundry.toml` and set `fail_on_revert` to `true`, we can run our test again as `forge test --mt statefulFuzz_TestInvariantBreaks -vvvv` to gain some insight. - +::image{src='/security-section-5/14-fuzzing-where-method-1-fails/fuzzing-where-method-1-fails3.png' style='width: 100%; height: auto;'} Ah! It's reverting with the error `HandlerStatefulFuzzCatches__UnsupportedToken()`. Of course! Our fuzz test is calling `depositToken` with random addresses, but we only have 2 supported tokens! diff --git a/courses/security/5-tswap/15-stateful-fuzzing-method-2/+page.md b/courses/security/5-tswap/15-stateful-fuzzing-method-2/+page.md index 14855cdf0..ae01f03cb 100644 --- a/courses/security/5-tswap/15-stateful-fuzzing-method-2/+page.md +++ b/courses/security/5-tswap/15-stateful-fuzzing-method-2/+page.md @@ -252,7 +252,7 @@ targetSelector(FuzzSelector({ addr: address(handler), selectors: selectors })); By running our tests through our handler we're able to trade randomness for much more sensible testing scenarios which don't revert. - +::image{src='/security-section-5/15-stateful-fuzzing-method-2/stateful-fuzzing-method-21.png' style='width: 100%; height: auto;'} ### The Test diff --git a/courses/security/5-tswap/16-Debugging-Fuzz-Sequences/+page.md b/courses/security/5-tswap/16-Debugging-Fuzz-Sequences/+page.md index ef9fb44aa..3e2aac3c3 100644 --- a/courses/security/5-tswap/16-Debugging-Fuzz-Sequences/+page.md +++ b/courses/security/5-tswap/16-Debugging-Fuzz-Sequences/+page.md @@ -12,7 +12,7 @@ Alright! The moment of truth, let's run our test with: forge test --mt statefulFuzz_testInvariantBreaksHandler ``` - +::image{src='/security-section-5/16-debugging-fuzz-sequences/debugging-fuzz-sequences1.png' style='width: 100%; height: auto;'} Oh no! Something went wrong. We can see `assertion violated` in the output, but there's not a lot of information. In situations like this, we should leverage the `-vvvv` flag. @@ -54,7 +54,7 @@ function setUp() public { Now let's try it. - +::image{src='/security-section-5/16-debugging-fuzz-sequences/debugging-fuzz-sequences2.png' style='width: 100%; height: auto;'} Alright! It looks like we may have found something! We're seeing an error of `ERC20InsufficientBalance` when calling `withdrawToken` on `yeildERC20`. That's odd. Let's look at the `withdrawToken` function again. diff --git a/courses/security/5-tswap/2-phase-1-scoping/+page.md b/courses/security/5-tswap/2-phase-1-scoping/+page.md index 880b236a1..003b3c4b1 100644 --- a/courses/security/5-tswap/2-phase-1-scoping/+page.md +++ b/courses/security/5-tswap/2-phase-1-scoping/+page.md @@ -25,7 +25,7 @@ Let's outline some of the important parts of this onboarding checklist together. ### Extensive Onboarding Form - +::image{src='/security-section-5/2-phase-1-scoping/scoping1.png' style='width: 100%; height: auto;'} When we first look at this document we'll likely see some familiar things to start. We ask for some basic protocol information including: @@ -46,15 +46,15 @@ Notice that TSwap's SLOC is 374 - this is nearly DOUBLE what Puppy Raffle was an We should also note that the coverage reported here is ... abysmal. - +::image{src='/security-section-5/2-phase-1-scoping/scoping2.png' style='width: 100%; height: auto;'} The next sections I won't go over in great detail now, but read through these questions and their importance will beecome clear as we go through the protocol. - +::image{src='/security-section-5/2-phase-1-scoping/scoping3.png' style='width: 100%; height: auto;'} Remember that communication with these protocols is paramount, the clearer the line of communication you have with a protocol, the better. - +::image{src='/security-section-5/2-phase-1-scoping/scoping4.png' style='width: 100%; height: auto;'} The next sections touch on known issues and previous audit reports. This information is incredibly valuable in allowing a security researcher in focusing their efforts. @@ -64,7 +64,7 @@ We can see that the protocol, in this instance, hasn't provided us any additiona The last section of the Extensive Onboarding Form we should be familiar with, protocols should have answers to questions found on the Rekt Test, which of course includes a Post Deployment Plan. - +::image{src='/security-section-5/2-phase-1-scoping/scoping5.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/security/5-tswap/20-constant-product-formula-explained/+page.md b/courses/security/5-tswap/20-constant-product-formula-explained/+page.md index 934707316..42d4c4e46 100644 --- a/courses/security/5-tswap/20-constant-product-formula-explained/+page.md +++ b/courses/security/5-tswap/20-constant-product-formula-explained/+page.md @@ -51,7 +51,7 @@ With that said, this is definitely an invariant we can test. The ratio between t Remember back to `How an AMM works`. - +::image{src='/security-section-5/20-constant-product-formula-explained/constant-product-formula-explained1.png' style='width: 100%; height: auto;'} Writing an assert for `x * y = (x + ∆x) * (y − ∆y)` can be difficult, but what we can do is write one that defines that any change in token balance must follow some formula. @@ -71,19 +71,19 @@ Let's go through the math to understand how we derive this invariant. We begin with this formula: - +::image{src='/security-section-5/20-constant-product-formula-explained/constant-product-formula-explained2.png' style='width: 100%; height: auto;'} We should be able to leverage some basic algebra to get where we need. First we're going to apply the concept of FOIL (First Outside Inside Last) to multiply our binomials. - +::image{src='/security-section-5/20-constant-product-formula-explained/constant-product-formula-explained3.png' style='width: 100%; height: auto;'} Next the equation will need to be simplified with the following steps. - +::image{src='/security-section-5/20-constant-product-formula-explained/constant-product-formula-explained4.png' style='width: 100%; height: auto;'} In the next step we're going to introduce a new term `β` and define it as `∆y/y`. Which will allow us to simplify things further. - +::image{src='/security-section-5/20-constant-product-formula-explained/constant-product-formula-explained5.png' style='width: 100%; height: auto;'} And that's it, we can go back to the TSwap documentation to confirm: `∆x = (β/(1-β)) * x` diff --git a/courses/security/5-tswap/23-handler-swap-function/+page.md b/courses/security/5-tswap/23-handler-swap-function/+page.md index 54d29487f..6e440b81c 100644 --- a/courses/security/5-tswap/23-handler-swap-function/+page.md +++ b/courses/security/5-tswap/23-handler-swap-function/+page.md @@ -36,7 +36,7 @@ function getInputAmountBasedOnOutput( First, let's begin with the formula our invariant was derived from `x * y = (x + ∆x) * (y - ∆y)`, we can substitute `∆y` for outputAmount since this will reflect by how much our poolToken is changing: - +::image{src='/security-section-5/23-handler-swap-function/handler-swap-function1.png' style='width: 100%; height: auto;'} From the equation `x*outputAmount = ∆x(y - outputAmount)` we just need to substitute a few known variables. @@ -44,7 +44,7 @@ From the equation `x*outputAmount = ∆x(y - outputAmount)` we just need to sub - `∆x` is going to be the `inputAmount` as discussed earlier, the input of poolTokens to get the desired output in `weth`. - `y` will be our `outputReserves`, just as x was a reflection of total poolTokens, this will be a reflection of total `weth` tokens. - +::image{src='/security-section-5/23-handler-swap-function/handler-swap-function2.png' style='width: 100%; height: auto;'} Our resulting formula is remarkably similar to what's returned by the `getInputAmountBasedOnOutput`function (10000 and 997 are related to fees, we can ignore them for now): diff --git a/courses/security/5-tswap/25-debugging-the-fuzzer/+page.md b/courses/security/5-tswap/25-debugging-the-fuzzer/+page.md index 0f417955e..9afa7cad0 100644 --- a/courses/security/5-tswap/25-debugging-the-fuzzer/+page.md +++ b/courses/security/5-tswap/25-debugging-the-fuzzer/+page.md @@ -18,11 +18,11 @@ Begin by running our test, passing the verbose flag to acquire a trace in our ou forge test --mt statefulFuzz_constantProductFormulaStaysTheSameY --vvvv ``` - +::image{src='/security-section-5/25-debugging-the-fuzzer/debugging-the-fuzzer1.png' style='width: 100%; height: auto;'} Fail, as expected. This output tells us the exception occuring during a call of the `deposit` function, but we can scroll up in the trace output to gain more infomation. - +::image{src='/security-section-5/25-debugging-the-fuzzer/debugging-the-fuzzer2.png' style='width: 100%; height: auto;'} Ok, this gives us more detail to assess. It's clear that we're receiving a custom error when calling deposit, this is due to passing `0` as an argument: @@ -68,7 +68,7 @@ function swapPoolTokenForWethBasedOnOutputWeth(uint256 outputWeth) public { Now we can try our test again. - +::image{src='/security-section-5/25-debugging-the-fuzzer/debugging-the-fuzzer3.png' style='width: 100%; height: auto;'} Ok, a new error! New errors mean progress. If we scroll up on this one, we can see that our assertion is actually acting strangely! @@ -106,7 +106,7 @@ Then run it again! > **Note:** debugging our fuzz sequences is a truly iteritive process. The errors you receive, and how many of them, may actually be different if you have different errors in your code. Use the steps and skills shown here to debug any error you receive the same way. - +::image{src='/security-section-5/25-debugging-the-fuzzer/debugging-the-fuzzer4.png' style='width: 100%; height: auto;'} Boom. diff --git a/courses/security/5-tswap/26-one-last-huzzah/+page.md b/courses/security/5-tswap/26-one-last-huzzah/+page.md index 527256b57..08f86c6f8 100644 --- a/courses/security/5-tswap/26-one-last-huzzah/+page.md +++ b/courses/security/5-tswap/26-one-last-huzzah/+page.md @@ -28,13 +28,13 @@ Run the test and let's see what we get. forge test --mt statefulFuzz_constantProductFormulaStaysTheSameX -vvvv ``` - +::image{src='/security-section-5/26-one-last-huzzah/one-last-huzzah1.png' style='width: 100%; height: auto;'} It errors! We can see that our expectedDeltaX and our actualDeltaX are wildly different. _What could possibly be going on here?_ > **Protip:** Rather than scrolling through all the function calls in our test's trace, often the steps that lead to our issue can be found in the most recent function exectution near the bottom. - +::image{src='/security-section-5/26-one-last-huzzah/one-last-huzzah2.png' style='width: 100%; height: auto;'} I've highlighted the most recent execution in the image above. We notice immediately that the function being called is `swapPoolTokenForWethBasedOnOutputWeth`. Things seems fairly unremarkable until we reach the actual swap of tokens when `swapExactOutput` is called. We would expect this function to execute two transfers, one from the `swapper` to `TSwapPool` and another from `TSwapPool` to the `swapper`. diff --git a/courses/security/5-tswap/29-t-swap-manual-review-slither/+page.md b/courses/security/5-tswap/29-t-swap-manual-review-slither/+page.md index d7316e78f..901e042fb 100644 --- a/courses/security/5-tswap/29-t-swap-manual-review-slither/+page.md +++ b/courses/security/5-tswap/29-t-swap-manual-review-slither/+page.md @@ -10,7 +10,7 @@ The first step in our manual review should be to run some of our static analyzer The protocol team has already configured a make command for `Slither`, using this config, so let's just run `make slither` and assess the output. - +::image{src='/security-section-5/29-t-swap-manual-review-slither/t-swap-manual-review-slither1.png' style='width: 100%; height: auto;'} ### Red Detectors diff --git a/courses/security/5-tswap/30-manual-review-aderyn/+page.md b/courses/security/5-tswap/30-manual-review-aderyn/+page.md index ee3921baf..3b7b11579 100644 --- a/courses/security/5-tswap/30-manual-review-aderyn/+page.md +++ b/courses/security/5-tswap/30-manual-review-aderyn/+page.md @@ -12,7 +12,6 @@ First thing's first, assure your version of `Aderyn` is up-to-date with `cargo i We should be able to run the command `aderyn .` and receive an output in the default form of `report.md`. - Let's have a peek at what `Aderyn` found! diff --git a/courses/security/5-tswap/33-using-the-compiler-as-static-analysis-tool/+page.md b/courses/security/5-tswap/33-using-the-compiler-as-static-analysis-tool/+page.md index b5f7e8023..09a2b7812 100644 --- a/courses/security/5-tswap/33-using-the-compiler-as-static-analysis-tool/+page.md +++ b/courses/security/5-tswap/33-using-the-compiler-as-static-analysis-tool/+page.md @@ -24,7 +24,7 @@ We should take the time to read and understand the provided NATSPEC for this fun An assessment of the function's paramaters in our IDE points to an issue our compiler identified earlier... - +::image{src='/security-section-5/33-using-the-compiler-as-static-analysis-tool/using-the-compiler-as-static-analysis-tool1.png' style='width: 100%; height: auto;'} We can see this, and other issues pointed out by our compiler again by running `forge build`. @@ -51,11 +51,11 @@ function deposit( Wow, we identified a potential high just through a compiler output! We should definitely check some of the other warnings. - +::image{src='/security-section-5/33-using-the-compiler-as-static-analysis-tool/using-the-compiler-as-static-analysis-tool2.png' style='width: 100%; height: auto;'} `poolTokenReserves`, as pointed out by the compiler, is on line 107. It looks like this variable may have been held over from when the function was calculating things locally, but this logic has since been replaced with a function which is handling all the math. - +::image{src='/security-section-5/33-using-the-compiler-as-static-analysis-tool/using-the-compiler-as-static-analysis-tool3.png' style='width: 100%; height: auto;'} Ultimately `poolTokenReserves` is a waste of gas and we can make a note of it as well. diff --git a/courses/security/5-tswap/4-what-is-a-dex/+page.md b/courses/security/5-tswap/4-what-is-a-dex/+page.md index 3d3444f8d..c4de0d2ba 100644 --- a/courses/security/5-tswap/4-what-is-a-dex/+page.md +++ b/courses/security/5-tswap/4-what-is-a-dex/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ At it's highest level of abstraction what TSwap aims to do is: Allow users a permissionless way to swap assets between eachother at a fair price. - +::image{src='/security-section-5/4-what-is-a-dex/what-is-a-dex1.png' style='width: 100%; height: auto;'} TSwap (based off Uniswap) is an example of a Decentralized Exchange, or a `DEX`. diff --git a/courses/security/5-tswap/45-invariant-break-write-up-and-poc/+page.md b/courses/security/5-tswap/45-invariant-break-write-up-and-poc/+page.md index 822d36ad5..d37e54cde 100644 --- a/courses/security/5-tswap/45-invariant-break-write-up-and-poc/+page.md +++ b/courses/security/5-tswap/45-invariant-break-write-up-and-poc/+page.md @@ -14,7 +14,7 @@ We should be able to even re-run our test to remind ourselves of the issue: forge test --mt statefulFuzz_constantProductFormulaStaysTheSameX ``` - +::image{src='/security-section-5/45-invariant-break-write-up-and-poc/invariant-break-write-up-and-poc1.png' style='width: 100%; height: auto;'} There it is. Let's get the `proof of code` out of the way first! @@ -131,7 +131,7 @@ Now, if we run this test, performing a single swap, we would expect this to pass forge test --mt testInvariantBroken ``` - +::image{src='/security-section-5/45-invariant-break-write-up-and-poc/invariant-break-write-up-and-poc2.png' style='width: 100%; height: auto;'} Of course this would pass, we need 10 swaps in order for our invariant to break. Let's implement that logic next. @@ -179,7 +179,7 @@ If we run this unit test now... forge test --mt testInvariantBroken -vvvv ``` - +::image{src='/security-section-5/45-invariant-break-write-up-and-poc/invariant-break-write-up-and-poc3.png' style='width: 100%; height: auto;'} Boom! We have our PoC which can be pasted into our report, which I've written below. Challenge yourself to write the other portions of this write-up and then compare to my included example: diff --git a/courses/security/5-tswap/48-recap/+page.md b/courses/security/5-tswap/48-recap/+page.md index ce3b07228..bd386a13f 100644 --- a/courses/security/5-tswap/48-recap/+page.md +++ b/courses/security/5-tswap/48-recap/+page.md @@ -18,11 +18,11 @@ It's been a journey, but let's do a quick recap of everything we've covered in t While gathering context for TSwap we learnt what an automated market maker is, what a decentralized exchange is and their functions within DeFi. - + ::image{src='/security-section-5/48-recap/recap1.png' style='width: 100%; height: auto;'} We also covered how AMMs differ from conventional orderbook exchanges and why this adjustment was needed in a blockchain world. - + ::image{src='/security-section-5/48-recap/recap2.png' style='width: 100%; height: auto;'} 3. Liquidity Providers diff --git a/courses/security/5-tswap/5-what-is-an-amm/+page.md b/courses/security/5-tswap/5-what-is-an-amm/+page.md index 6522893d6..cbfa18f50 100644 --- a/courses/security/5-tswap/5-what-is-an-amm/+page.md +++ b/courses/security/5-tswap/5-what-is-an-amm/+page.md @@ -16,7 +16,7 @@ Before we better detail an AMM, we should first understand how `order book` exch An order book exchange is fundamentally very simple, it will track desired buy and sell orders and effectively try to match them. - +::image{src='/security-section-5/5-what-is-an-amm/what-is-an-amm1.png' style='width: 100%; height: auto;'} Order book exchanges come with a fatal flaw in a blockchain ecosystem though - cost. @@ -30,7 +30,7 @@ This is where Automated Market Makers come in! An AMM functions by leveraging asset pools with the goal of maintaining the ratio of assets traded with the pool. - +::image{src='/security-section-5/5-what-is-an-amm/what-is-an-amm2.png' style='width: 100%; height: auto;'} As we can see, as orders are placed against the liquidity pools the ratio between the two assets traded changes, this drives the price of the asset pair for the next trade when executed. diff --git a/courses/security/5-tswap/6-liquidity-providers/+page.md b/courses/security/5-tswap/6-liquidity-providers/+page.md index c462256b2..f6cb6261e 100644 --- a/courses/security/5-tswap/6-liquidity-providers/+page.md +++ b/courses/security/5-tswap/6-liquidity-providers/+page.md @@ -14,13 +14,13 @@ The first question that probably comes to mind is **_"Where did these pools of t This is where `liquidity providers` come in. `Liquidity providers` add their tokens to liquidity pools to fund the trading by users. In exchange a liquidity provider will often receive an LPToken (liquidity provider token) at the ratio of what they've contributed to the total pool. - +::image{src='/security-section-5/6-liquidity-providers/liquidity-providers1.png' style='width: 75%; height: auto;'} The next questions you're probably asking are **_"Why would anyone do that? What's an LP Token?"_** This is where `fees` come in. Let's look at a slightly adjusted diagram: - +::image{src='/security-section-5/6-liquidity-providers/liquidity-providers2.png' style='width: 75%; height: auto;'} Each transaction in a DEX like TSwap or Uniswap incurs a fee (we've used 0.3% as an example). This fee is typically added to the respective liquidity pool. diff --git a/courses/security/5-tswap/7-how-amms-work/+page.md b/courses/security/5-tswap/7-how-amms-work/+page.md index 0f805c1ed..c89a7bd73 100644 --- a/courses/security/5-tswap/7-how-amms-work/+page.md +++ b/courses/security/5-tswap/7-how-amms-work/+page.md @@ -10,7 +10,7 @@ Now that we've taken a deep look at an AMM, let's put it all together and recap We started by gaining an understanding of order books and how they work. - +::image{src='/security-section-5/7-how-amms-work-recap/how-amms-work-recap1.png' style='width: 75%; height: auto;'} A few characteristics of order book exchanges we covered were: @@ -21,17 +21,17 @@ A few characteristics of order book exchanges we covered were: To solve the above problems DeFi introduced Automated Market Makers (AMMs), and they function a little differently. We learnt that AMMs support user trading through the use of Liquidity Pools. As users execute trades via the liquidity pools, the ratio of the assets within the pool is what determines the price of the traded assets, a system of pure supply and demand. - +::image{src='/security-section-5/7-how-amms-work-recap/how-amms-work-recap2.png' style='width: 75%; height: auto;'} Additionally we learnt the source of these liquidity pools are known as liquidity providers. Liquidity providers are incentivized to add liquidity to a pool though an LP Token system. When liquidity providers deposit funds into a liquidity pool, they often receive LP Tokens, the number of which represents their ratio of contribution to the total pool. - +::image{src='/security-section-5/7-how-amms-work-recap/how-amms-work-recap3.png' style='width: 75%; height: auto;'} This is where fees come in. Fees are charged for each transaction with the protocol and then added to the appropriate pool. What this means practically for a liquidity provider is that their LP Tokens, representing their percentage claim of the pool, increase in value allowing liquidity providers to profit off of every transaction with the pool! - +::image{src='/security-section-5/7-how-amms-work-recap/how-amms-work-recap4.png' style='width: 75%; height: auto;'} ### Wrap Up diff --git a/courses/security/5-tswap/8-t-swap-recon-continued/+page.md b/courses/security/5-tswap/8-t-swap-recon-continued/+page.md index ed7cf6042..fa5f4a82e 100644 --- a/courses/security/5-tswap/8-t-swap-recon-continued/+page.md +++ b/courses/security/5-tswap/8-t-swap-recon-continued/+page.md @@ -29,7 +29,7 @@ Every pool is a pair of TOKEN X & WETH. This may be a great point in our process to go through the code base and make connections to what functions are actually performing these actions described in the docs. We may even create .... - +::image{src='/security-section-5/8-tswap-recon-continued/tswap-recon-cont1.png' style='width: 75%; height: auto;'} A Protocol Diagram! diff --git a/courses/security/6-thunder-loan/11-static-analysis-slither-aderyn/+page.md b/courses/security/6-thunder-loan/11-static-analysis-slither-aderyn/+page.md index b61ffb529..a3112f7ec 100644 --- a/courses/security/6-thunder-loan/11-static-analysis-slither-aderyn/+page.md +++ b/courses/security/6-thunder-loan/11-static-analysis-slither-aderyn/+page.md @@ -42,7 +42,7 @@ make slither Our output is more concise than ever, but already we can see some vulnerabilities detected. This is a great place to start our review. - +::image{src='/security-section-6/11-static-analysis-slither-aderyn/static-analysis-slither-aderyn1.png' style='width: 100%; height: auto;'} Let's even just begin by taking a look at the very first issue detected. @@ -85,6 +85,6 @@ or aderyn . ``` - +::image{src='/security-section-6/11-static-analysis-slither-aderyn/static-analysis-slither-aderyn2.png' style='width: 100%; height: auto;'} Looking good, thanks `Aderyn`! Let's go through some of the issues detected by `Aderyn` together, in the next lesson. diff --git a/courses/security/6-thunder-loan/15-recon-ipoolfactory/+page.md b/courses/security/6-thunder-loan/15-recon-ipoolfactory/+page.md index 27a758dca..755c0748e 100644 --- a/courses/security/6-thunder-loan/15-recon-ipoolfactory/+page.md +++ b/courses/security/6-thunder-loan/15-recon-ipoolfactory/+page.md @@ -8,7 +8,7 @@ title: Recon - Manual Review - IPoolFactory.sol After setting our initial context and utilizing our suite of auditing tools, it's time to get our hands dirty with some thorough manual review. Much like our previous auditing process, one viable option available to us is to start from the test suite. Just look at this test coverage, it'll probably be an informational on it's own. - +::image{src='/security-section-6/15-recon-ipoolfactory/recon-ipoolfactory3.png' style='width: 100%; height: auto;'} This time we're going to dive right into manual review however. @@ -18,13 +18,13 @@ Run solidity metrics again and let's take another look at what we're working wit > **Remember:** You can run solidity metrics by right-clicking the `src` folder and selecting `Solidity: Metrics` to generate the report. - +::image{src='/security-section-6/15-recon-ipoolfactory/recon-ipoolfactory1.png' style='width: 100%; height: auto;'} Copy this table into a spreadsheet of your choice, it will allow us to sort and manage our scope more easily through this process, tracking what we've done as we go. I used Google Sheets and I've set my table up like below. I've only kept the file and complexity columns. - +::image{src='/security-section-6/15-recon-ipoolfactory/recon-ipoolfactory2.png' style='width: 100%; height: auto;'} ### The Tincho Method Applied @@ -62,4 +62,4 @@ I otherwise see no glaring issues here! Let's check this file off, celebrate our little win and move on to the next one! - +::image{src='/security-section-6/15-recon-ipoolfactory/recon-ipoolfactory4.png' style='width: 100%; height: auto;'} diff --git a/courses/security/6-thunder-loan/16-itswappool/+page.md b/courses/security/6-thunder-loan/16-itswappool/+page.md index e91f87657..8b4c90ff2 100644 --- a/courses/security/6-thunder-loan/16-itswappool/+page.md +++ b/courses/security/6-thunder-loan/16-itswappool/+page.md @@ -44,4 +44,4 @@ The limited nature of tokens being used may raise questions pertaining to the pr Wow, another quick one down, we're flying through these quick wins. Thanks Tincho Method! - +::image{src='/security-section-6/16-itswappool/itswappool1.png' style='width: 100%; height: auto;'} diff --git a/courses/security/6-thunder-loan/17-ithunderloan/+page.md b/courses/security/6-thunder-loan/17-ithunderloan/+page.md index 313486a74..eb98fcffc 100644 --- a/courses/security/6-thunder-loan/17-ithunderloan/+page.md +++ b/courses/security/6-thunder-loan/17-ithunderloan/+page.md @@ -22,7 +22,7 @@ Ok, fairly simple file again. This seems to be an interface for the ThunderLoan. - Does the contract actually implement the interface - Do the functions in the interface match the functions to be called on the contract - +::image{src='/security-section-6/17-ithunderloan/ithunderloan1.png' style='width: 100%; height: auto;'} Straight away, we can see that Thunder Loan isn't actually implementing this interface, easy informational finding. @@ -53,4 +53,4 @@ Whether or not the above would classify as a `low` or `informational` severity f Great! Two more findings, let's keep going! - +::image{src='/security-section-6/17-ithunderloan/ithunderloan2.png' style='width: 100%; height: auto;'} diff --git a/courses/security/6-thunder-loan/18-flashloan-receiver/+page.md b/courses/security/6-thunder-loan/18-flashloan-receiver/+page.md index 64ac00e4e..949241df1 100644 --- a/courses/security/6-thunder-loan/18-flashloan-receiver/+page.md +++ b/courses/security/6-thunder-loan/18-flashloan-receiver/+page.md @@ -37,7 +37,7 @@ We can check if the import is being inherited anywhere by searching our workspac > **Protip:** You can use the keyboard shortcuts `ctrl + shift + f`(windows) and `cmd + shift + f`(mac) to open the workspace search menu. - +::image{src='/security-section-6/18-flashloan-receiver/flashloan-receiver1.png' style='width: 100%; height: auto;'} It seems as though this import is only being used in one of Thunder Loan's mock files for testing. @@ -93,7 +93,7 @@ function executeOperation( We've completed our first pass of all of Thunder Loan's interfaces! - +::image{src='/security-section-6/18-flashloan-receiver/flashloan-receiver2.png' style='width: 100%; height: auto;'} We haven't really found anything _meaty_ but we've found some informationals to call out. It's important to remember that our ultimate goal is to improve the protocol not just with security but with engineering best practices as well. This is just one example of adding value to the review you're performing. diff --git a/courses/security/6-thunder-loan/2-phase-1-scoping/+page.md b/courses/security/6-thunder-loan/2-phase-1-scoping/+page.md index 708f974f7..37d960549 100644 --- a/courses/security/6-thunder-loan/2-phase-1-scoping/+page.md +++ b/courses/security/6-thunder-loan/2-phase-1-scoping/+page.md @@ -65,7 +65,7 @@ Let's run solidity metrics on our `src` folder to get a send of the size and com > **Remember:** You can right click the `src` folder in your workspace and select `Solidity: Metrics` to generate the report. - +::image{src='/security-section-6/2-phase-1-scoping/phase-1-scoping1.png' style='width: 100%; height: auto;'} With an nSLOC of 391 and a Complexity of 327, Thunder Loan represents the biggest code bases we've approached yet. We can see most of the logic and complexity exists within ThunderLoan.sol and ThunderLoanUpgraded.sol. diff --git a/courses/security/6-thunder-loan/21-failure-to-initialize-remix/+page.md b/courses/security/6-thunder-loan/21-failure-to-initialize-remix/+page.md index 2176ca61a..f5d621700 100644 --- a/courses/security/6-thunder-loan/21-failure-to-initialize-remix/+page.md +++ b/courses/security/6-thunder-loan/21-failure-to-initialize-remix/+page.md @@ -39,15 +39,15 @@ contract FailureToInitialize is Initializable { The example here is very simple, but it should illustrate the potential impact of failing to initialize. Go ahead and compile and deploy `FailureToInitialize.sol` - +::image{src='/security-section-6/21-failure-to-initialize-remix/failure-to-initialize-remix1.png' style='width: 100%; height: auto;'} You should see it begin unitialized with `myValue` set to zero. If the protocol then proceeds to be used (by calling `increment`), the `initialize` function can be called at any time to overwrite the expected `myValue`. - +::image{src='/security-section-6/21-failure-to-initialize-remix/failure-to-initialize-remix2.png' style='width: 100%; height: auto;'} If our `myValue` is changed on us via initialize, we're not even able to re-initialize to fix `myValue` now, effectively breaking our protocol! - +::image{src='/security-section-6/21-failure-to-initialize-remix/failure-to-initialize-remix3.png' style='width: 100%; height: auto;'} You could imagine a situation like this impacting the management of something very important - like billions of dollars. Failure to initialize can be a very severe attack path depending on the architecture of the protocol and what's being initialized. diff --git a/courses/security/6-thunder-loan/22-failure-to-initialize-case-study/+page.md b/courses/security/6-thunder-loan/22-failure-to-initialize-case-study/+page.md index cbdc4bbfe..9ab8927e1 100644 --- a/courses/security/6-thunder-loan/22-failure-to-initialize-case-study/+page.md +++ b/courses/security/6-thunder-loan/22-failure-to-initialize-case-study/+page.md @@ -4,7 +4,7 @@ title: Exploit - Failure to Initialize - Case Study ### Exploit - Failure to Initialize - Case Study - +::image{src='/security-section-6/22-failure-to-initialize-case-study/failure-to-initialize-case-study1.png' style='width: 100%; height: auto;'} The post above lives in infamy in the Web3 ecosystem. @@ -12,13 +12,13 @@ You can check out the thread and read more about the hack [**here**](https://git Within that thread there's a [**link to the transaction**](https://etherscan.io/tx/0x05f71e1b2cb4f03e547739db15d080fd30c989eda04d37ce6264c5686e0722c9) that caused the Parity Bug. Let's have a look at what was done. - +::image{src='/security-section-6/22-failure-to-initialize-case-study/failure-to-initialize-case-study2.png' style='width: 100%; height: auto;'} We can see in the transaction which function was called `initWallet`. This function is taking important parameters such as `_owners` and `_required`. Because the contract hadn't been initialized, the hacker was able to set their own wallet as the owner of the contract and the required multisig signatures to zero. - +::image{src='/security-section-6/22-failure-to-initialize-case-study/failure-to-initialize-case-study3.png' style='width: 100%; height: auto;'} This ultimately resulted in a scramble to free locked funds and chaos in the ecosystem, all because the initialize function had been forgotten. diff --git a/courses/security/6-thunder-loan/23-oracleupgradeable-continued/+page.md b/courses/security/6-thunder-loan/23-oracleupgradeable-continued/+page.md index 107ff7b5f..d4b19e737 100644 --- a/courses/security/6-thunder-loan/23-oracleupgradeable-continued/+page.md +++ b/courses/security/6-thunder-loan/23-oracleupgradeable-continued/+page.md @@ -99,4 +99,4 @@ Awesome! We've finished our recon of `OracleUpgradeable.sol`! That's one more co Let's mark this first pass done for now and move on to `AssetToken.sol` in the next lesson! - +::image{src='/security-section-6/23-oracle-upgradeable-continued/oracle-upgradeable-continued1.png' style='width: 100%; height: auto;'} diff --git a/courses/security/6-thunder-loan/25-asset-token-update-exchange-rate/+page.md b/courses/security/6-thunder-loan/25-asset-token-update-exchange-rate/+page.md index 507f99f58..1346239f8 100644 --- a/courses/security/6-thunder-loan/25-asset-token-update-exchange-rate/+page.md +++ b/courses/security/6-thunder-loan/25-asset-token-update-exchange-rate/+page.md @@ -84,7 +84,7 @@ Both of the returned variables here are declared as private, so these functions We didn't find too many issues with AssetToken.sol, but we got a much better understanding of the role it plays within Thunder Loan. We can check this one off our list! - +::image{src='/security-section-6/25-asset-token-update-exchange-rate/asset-token-update-exchange-rate1.png' style='width: 100%; height: auto;'} We'll finally approach ThunderLoan.sol itself in the next lesson. While technically a little bigger, we expect a lot of overlap between ThunderLoan.sol and ThunderLoanUpgraded.sol as well as valuable context regarding what's being changed between the two versions. diff --git a/courses/security/6-thunder-loan/28-testing-deleting-mappings/+page.md b/courses/security/6-thunder-loan/28-testing-deleting-mappings/+page.md index b81a7fe0a..9f8436844 100644 --- a/courses/security/6-thunder-loan/28-testing-deleting-mappings/+page.md +++ b/courses/security/6-thunder-loan/28-testing-deleting-mappings/+page.md @@ -64,7 +64,7 @@ contract DeleteMappingTest { If we deploy the above contract in Remix we can then set two addresses as `token` and `token2` to see the mapping working as expected: - +::image{src='/security-section-6/28-testing-deleting-mappings/testing-deleting-mapping1.png' style='width: 100%; height: auto;'} We can then call the `remove` function, passing token. We see that our getter is now returning `address(0)`. diff --git a/courses/security/6-thunder-loan/31-diagramming-thunderloan/+page.md b/courses/security/6-thunder-loan/31-diagramming-thunderloan/+page.md index a6c87681a..b80917212 100644 --- a/courses/security/6-thunder-loan/31-diagramming-thunderloan/+page.md +++ b/courses/security/6-thunder-loan/31-diagramming-thunderloan/+page.md @@ -6,7 +6,7 @@ title: Diagramming Thunder Loan It's at this point in a review that I may begin to diagram out what I currently understand of a protocol. A diagram of Thunder Loan might look something like this: - +::image{src='/security-section-6/31-diagramming-thunderloan/diagramming-thunderloan1.png' style='width: 100%; height: auto;'} From what we understand so far, a `liquidity provider` calls `deposit` on the `ThunderLoan` contract passing a token which has has an `AssetToken` contract created by the protocol owner at an earlier point. diff --git a/courses/security/6-thunder-loan/32-thunderloan-redeem/+page.md b/courses/security/6-thunder-loan/32-thunderloan-redeem/+page.md index 536dbc6dc..86739540f 100644 --- a/courses/security/6-thunder-loan/32-thunderloan-redeem/+page.md +++ b/courses/security/6-thunder-loan/32-thunderloan-redeem/+page.md @@ -105,7 +105,7 @@ As we perform our review we should constantly be looking for questions like this We can use `chisel` to answer this question for us quite quickly. Try it out! We would expect to receive `2e6` USDC given an `exchangeRate` of `2`. - +::image{src='/security-section-6/32-thunderloan-redeem/thunderloan-redeem1.png' style='width: 100%; height: auto;'} And it does! diff --git a/courses/security/6-thunder-loan/33-thunderloan-flashloan/+page.md b/courses/security/6-thunder-loan/33-thunderloan-flashloan/+page.md index b3355c0f4..2b4a33cfb 100644 --- a/courses/security/6-thunder-loan/33-thunderloan-flashloan/+page.md +++ b/courses/security/6-thunder-loan/33-thunderloan-flashloan/+page.md @@ -366,7 +366,7 @@ Alright - strictly speaking, it doesn't look like this is doing anything. This f A whole loan flow looks something like this: - +::image{src='/security-section-6/33-thunderloan-flashloan/thunderloan-flashloan1.png' style='width: 100%; height: auto;'} After the flash loan is executed, this function closes with a very important check: diff --git a/courses/security/6-thunder-loan/35-thunderloan-repay-final-functions/+page.md b/courses/security/6-thunder-loan/35-thunderloan-repay-final-functions/+page.md index a6517ec93..ad3e6ad10 100644 --- a/courses/security/6-thunder-loan/35-thunderloan-repay-final-functions/+page.md +++ b/courses/security/6-thunder-loan/35-thunderloan-repay-final-functions/+page.md @@ -127,7 +127,7 @@ A small line, but potentially _very_ impactful. Well - we've completed our whole first pass of this code base (save the upgrade to ThunderLoan) and we didn't find anything significant!? - +::image{src='/security-section-6/35-thunderloan-repay-final-functions/thunderloan-repay-final-functions1.png' style='width: 100%; height: auto;'} In this process we've gained a tonne of context and understanding, but we've also left a number of unanswered questions throughout the code. I think our best approach is going to be diving into the questions we've left ourselves, answering them and seeing what becomes of them. diff --git a/courses/security/6-thunder-loan/37-improving-test-coverage-to-find-a-high/+page.md b/courses/security/6-thunder-loan/37-improving-test-coverage-to-find-a-high/+page.md index 3b48c25ff..b89e1123c 100644 --- a/courses/security/6-thunder-loan/37-improving-test-coverage-to-find-a-high/+page.md +++ b/courses/security/6-thunder-loan/37-improving-test-coverage-to-find-a-high/+page.md @@ -255,11 +255,11 @@ Already, we should be able to run this test to determine if there's an issue her forge test --mt testRedeemAfterLoan -vvvv ``` - +::image{src='/security-section-6/37-improving-test-coverage-to-find-a-high/improving-test-coverage-to-find-a-high1.png' style='width: 100%; height: auto;'} Oh snap. Let's take a closer look at the trace output to determine where the test is failing. - +::image{src='/security-section-6/37-improving-test-coverage-to-find-a-high/improving-test-coverage-to-find-a-high2.png' style='width: 100%; height: auto;'} We can clearly see from the trace that `transferUnderlyingTo` is failing because `AssetToken` has an insufficient balance... I wonder if commenting out the weird `updateExchangeRate` in our `deposit` function would resolve this. Let's give it a try... @@ -284,7 +284,7 @@ Now run the test once more. forge test --mt testRedeemAfterLoan -vvvv ``` - +::image{src='/security-section-6/37-improving-test-coverage-to-find-a-high/improving-test-coverage-to-find-a-high3.png' style='width: 100%; height: auto;'} WOOOOO! We found something! The `updateExchangeRate` function effectively keeps track of how much money is in the protocol at all times. By calling this function within `deposit` (when the protocol isn't expecting to gain value through fees) the expected amount to withdraw is breaking when a `liquidity provider` calls `redeem`. diff --git a/courses/security/6-thunder-loan/39-exploit-oracle-manipulation-minimized/+page.md b/courses/security/6-thunder-loan/39-exploit-oracle-manipulation-minimized/+page.md index 1268decd8..5feb5c729 100644 --- a/courses/security/6-thunder-loan/39-exploit-oracle-manipulation-minimized/+page.md +++ b/courses/security/6-thunder-loan/39-exploit-oracle-manipulation-minimized/+page.md @@ -14,14 +14,14 @@ All of the images and diagrams for this lesson will be in the [**src/oracle-mani First, let's recall what we've been talking about regarding flash loans. These are equity focuses DeFi systems which allow anyone to leverage more than their own buying power for the duration of a single transaction, for a fee. - +::image{src='/security-section-6/39-exploit-oracle-manipulation-minimized/exploit-oracle-manipulation-minimized1.png' style='width: 100%; height: auto;'} We also learnt that this was an amazing tool to facilitate arbitrage, whereby a user can normalize the prizes between Dexs by buying and selling an asset with an identified price difference between them. This has two effects 1. The user makes a profit denoted by the margin between the two listings of the asset 2. The listed prices of the asset should change as demand and price on each protocol adjusts for the arbitration - +::image{src='/security-section-6/39-exploit-oracle-manipulation-minimized/exploit-oracle-manipulation-minimized2.png' style='width: 100%; height: auto;'} Do you see where we're going with this yet? @@ -31,7 +31,7 @@ The impact on Thunder Loan itself is a little harder to see, but let's consider We know the flash loan receiver needs to be a smart contract with the executeOperation function. It's in this function that the funds are used for the purpose the user borrowed them, what if they were first used to manipulate a Dex price? - +::image{src='/security-section-6/39-exploit-oracle-manipulation-minimized/exploit-oracle-manipulation-minimized3.png' style='width: 100%; height: auto;'} In the diagram above, we can see that an interaction with TSwap is being used to manipulate the price ratio between the two tokens (USDC and WETH). This allows the flash loan receiver to purchase an NFT at a massive WETH discount, ultimately selling it for a profit in USDC. @@ -51,4 +51,4 @@ The repo has some fantastic examples and links to Remix, [**Damn Vulnerable DeFi When you're ready, I'll see you in the next lesson where we'll identify how this specifically impacts `Thunder Loan` and we'll construct a write-up (with Poc)! - +::image{src='/security-section-6/39-exploit-oracle-manipulation-minimized/exploit-oracle-manipulation-minimized4.png' style='width: 100%; height: auto;'} diff --git a/courses/security/6-thunder-loan/4-what-is-flash-loan/+page.md b/courses/security/6-thunder-loan/4-what-is-flash-loan/+page.md index 2ef83841f..8dfad586b 100644 --- a/courses/security/6-thunder-loan/4-what-is-flash-loan/+page.md +++ b/courses/security/6-thunder-loan/4-what-is-flash-loan/+page.md @@ -14,7 +14,7 @@ Let's consider a typical scenario. Suppose there are two DEXs, A and B. On Dex A You could buy one Ethereum at DEX A for $5, then head over to DEX B and sell that Ethereum for $6. This simple transaction would net you a profit of $1. This process is known as **Arbitrage.** - +::image{src='/security-section-6/4-what-is-a-flash-loan/what-is-a-flash-loan1.png' style='width: 100%; height: auto;'} Imagine if this situation were to scale up, and you were able to buy 10, 100 or 1000 ETH. The problem lies in the average person's ability to shoulder the upfront investment. diff --git a/courses/security/6-thunder-loan/40-exploit-oracle-manipulation-thunderloan-poc/+page.md b/courses/security/6-thunder-loan/40-exploit-oracle-manipulation-thunderloan-poc/+page.md index 09d4f06de..547de0c18 100644 --- a/courses/security/6-thunder-loan/40-exploit-oracle-manipulation-thunderloan-poc/+page.md +++ b/courses/security/6-thunder-loan/40-exploit-oracle-manipulation-thunderloan-poc/+page.md @@ -428,7 +428,7 @@ Let's run our test command and you'll see why. forge test --mt testOracleManipulation -vvv ``` - +::image{src='/security-section-6/40-exploit-oracle-manipulation-thunderloan-poc/exploit-oracle-manipulation-thunderloan-poc1.png' style='width: 100%; height: auto;'} Oh no! As expected. It looks like our repay function is failing and the reason: **_ThunderLoan doesn't think it's flash loaning!_** @@ -465,7 +465,7 @@ With that final adjustment made (and a side bug uncovered!), we're ready to run forge test --mt testOracleManipulation -vvv ``` - +::image{src='/security-section-6/40-exploit-oracle-manipulation-thunderloan-poc/exploit-oracle-manipulation-thunderloan-poc2.png' style='width: 100%; height: auto;'} There's nearly a 30% loss in fees demonstrated by our test! Let's consider the severity of an issue like this... diff --git a/courses/security/6-thunder-loan/41-oracle-manipulation-recap/+page.md b/courses/security/6-thunder-loan/41-oracle-manipulation-recap/+page.md index f801cf2ee..0aab5fe33 100644 --- a/courses/security/6-thunder-loan/41-oracle-manipulation-recap/+page.md +++ b/courses/security/6-thunder-loan/41-oracle-manipulation-recap/+page.md @@ -16,7 +16,7 @@ Arbitrage is the process by which a profit can be made by identifying a price di This is one of the primary use cases of flash loans. - +::image{src='/security-section-6/41-oracle-manipulation-recap/oracle-manipulation-recap1.png' style='width: 100%; height: auto;'} ### Flash Loans @@ -28,7 +28,7 @@ The fees collected from flash loans serve as incentive for those supplying liqui In the case of a recognized price discrepancy, this means any user would be able to take greater advantage of the arbitrage opportunity and the greatest profits aren't reserved for whales or those with the most money. - +::image{src='/security-section-6/41-oracle-manipulation-recap/oracle-manipulation-recap2.png' style='width: 100%; height: auto;'} ### Oracle Manipulation @@ -172,7 +172,7 @@ contract MaliciousFlashLoanReceiver is IFlashLoanReceiver { --- - +::image{src='/security-section-6/41-oracle-manipulation-recap/oracle-manipulation-recap3.png' style='width: 100%; height: auto;'} This section was heavy. Now's a great time to take a break before continuing on. diff --git a/courses/security/6-thunder-loan/42-exploit-deposit-instead-of-repay/+page.md b/courses/security/6-thunder-loan/42-exploit-deposit-instead-of-repay/+page.md index 9df1f0c05..f2e30e92b 100644 --- a/courses/security/6-thunder-loan/42-exploit-deposit-instead-of-repay/+page.md +++ b/courses/security/6-thunder-loan/42-exploit-deposit-instead-of-repay/+page.md @@ -166,7 +166,7 @@ Here we're setting an `amountToBorrow` and deploying our malicious contract. We Once the `flash loan` has been executed, we have the user call `redeemMoney` and assert that the `DepositOverRepay` contract's balance is higher than it started. - +::image{src='/security-section-6/42-exploit-deposit-instead-of-repay/exploit-deposit-instead-of-repay1.png' style='width: 100%; height: auto;'} WOOOOOOOOOOOOOOOO! We did it! Our test is showing that the `DepositOverRepay` contract is able to `redeem` the tokens they deposited into `ThunderLoan`, stealing them! @@ -268,7 +268,7 @@ function deposit(IERC20 token, uint256 amount) external revertIfZero(amount) rev We've thoroughly gone though ThunderLoan.sol now, and while you can always look at one more line of code and always further scrutinize - we're going to move on. - +::image{src='/security-section-6/42-exploit-deposit-instead-of-repay/exploit-deposit-instead-of-repay2.png' style='width: 100%; height: auto;'} ### ThunderLoanUpgradeable.sol diff --git a/courses/security/6-thunder-loan/43-exploit-storage-collision-storage-refresher/+page.md b/courses/security/6-thunder-loan/43-exploit-storage-collision-storage-refresher/+page.md index 959ba3f94..074e10aab 100644 --- a/courses/security/6-thunder-loan/43-exploit-storage-collision-storage-refresher/+page.md +++ b/courses/security/6-thunder-loan/43-exploit-storage-collision-storage-refresher/+page.md @@ -14,29 +14,29 @@ Otherwise, we're going to go through a quick refresher on storage in Solidity. So, **_How does storage work?_** - +::image{src='/security-section-6/43-exploit-storage-collision-storage-refresher/exploit-storage-collision-storage-refresher1.png' style='width: 100%; height: auto;'} Variables in a smart contract get saved to storage sequentially. The first declaration being assigned slot 0, the second, slot 1 etc. Storage _isn't_, but can be thought of as an array of 32 byte slots, in which each variable is allocated. - +::image{src='/security-section-6/43-exploit-storage-collision-storage-refresher/exploit-storage-collision-storage-refresher2.png' style='width: 100%; height: auto;'} Mapping and arrays are handled uniquely by the solidity compiler. The length of these items is stored in the next available slot (like normal), but the elements, or content of the mappings/arrays are stored in a separate slot. This slot is determined by a hash function on the first slot. - +::image{src='/security-section-6/43-exploit-storage-collision-storage-refresher/exploit-storage-collision-storage-refresher3.png' style='width: 100%; height: auto;'} Constant variables are **not** assigned storage slots, these are stored directly in a contract's bytecode. - +::image{src='/security-section-6/43-exploit-storage-collision-storage-refresher/exploit-storage-collision-storage-refresher4.png' style='width: 100%; height: auto;'} Variables initialized within a function also **do not** get assigned storage slots. These variables exist only for the duration of the function call and as such are kept in memory. - +::image{src='/security-section-6/43-exploit-storage-collision-storage-refresher/exploit-storage-collision-storage-refresher5.png' style='width: 100%; height: auto;'} ### Upgrading When a contract is upgraded, the variables within it are mapped to the same storage slots that the original implementation had. These are the storage slots that the proxy is expecting very specific variables to be found in. By changing the order of our variables we assure that the incorrect values/types will be referenced throughout the upgrades logic. - +::image{src='/security-section-6/43-exploit-storage-collision-storage-refresher/exploit-storage-collision-storage-refresher6.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/security/6-thunder-loan/44-exploit-storage-collision-diagram/+page.md b/courses/security/6-thunder-loan/44-exploit-storage-collision-diagram/+page.md index 1e3c14768..108ff04a8 100644 --- a/courses/security/6-thunder-loan/44-exploit-storage-collision-diagram/+page.md +++ b/courses/security/6-thunder-loan/44-exploit-storage-collision-diagram/+page.md @@ -12,7 +12,7 @@ To kick off, let's take a closer look at the basic principles of proxy interacti To put it simply, imagine we have an implementation contract. When a user executes a function, say `setValue(x)`, the call initially goes to the proxy. The proxy is programmed to look at the implementation contract to execute the function. - +::image{src='/security-section-6/44-exploit-storage-collision-diagram/exploit-storage-collision-diagram1.png' style='width: 100%; height: auto;'} In the above example, if our contract has an instruction to `setValue` to `5`, the call gets sent to the proxy, which looks at the implementation contract to perform the logic. @@ -26,13 +26,13 @@ The proxy responds by setting it's slot 0 to the value 5. Now, what does an upgrade look like? - +::image{src='/security-section-6/44-exploit-storage-collision-diagram/exploit-storage-collision-diagram2.png' style='width: 100%; height: auto;'} When a protocol is upgraded, the storage and values within the proxy don't change. The proxy contract is instructed to point to a different implementation to execute different logic. If the values expected to be accessed by the implementations logic, don't match what's actually in the accessed storage slots, this is where we run into problems. - +::image{src='/security-section-6/44-exploit-storage-collision-diagram/exploit-storage-collision-diagram3.png' style='width: 100%; height: auto;'} We can see, if a user called setValue, with the new implementation in play, that instead of recording the value to storage slot 0, the new value was assigned to storage slot 1! diff --git a/courses/security/6-thunder-loan/45-exploit-storage-collision-remix-examplee/+page.md b/courses/security/6-thunder-loan/45-exploit-storage-collision-remix-examplee/+page.md index 8b648140d..78c0864c1 100644 --- a/courses/security/6-thunder-loan/45-exploit-storage-collision-remix-examplee/+page.md +++ b/courses/security/6-thunder-loan/45-exploit-storage-collision-remix-examplee/+page.md @@ -88,25 +88,25 @@ Compile `StorageCollision.sol` and deploy our 3 contracts (Proxy.sol will show u We can then set `Implementation A` via `StorageCollision::setImplementation`. - +::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example1.png' style='width: 100%; height: auto;'} In order to interact with `Implementation A` in Remix now, we need to copy the address of our StorageCollisionProxy deployment, and add it to the `At Address` field. This will provide us a new contract field with which to interact. - +::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example2.png' style='width: 100%; height: auto;'} Go ahead and set any number (less than type(uint256).max 😜) as the value. - +::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example3.png' style='width: 100%; height: auto;'} Update the `At Address` field to configure our contract in Remix to be interacted with via our StorageCollisionProxy. - +::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example4.png' style='width: 100%; height: auto;'} From here, we should be able to updated the implementation address of our protocol by calling `setImplementation` and passing the address for `Implementation B`. We expect the logic of the protocol to change, but elements in storage should remain the same. Storage is handled by our proxy. However, if we call `value` after setting our new implementation address, we see it's actually `0`. - +::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example5.png' style='width: 100%; height: auto;'} Worse than this, our new `initialized` variable seems to have defaulted to `true`! @@ -131,7 +131,7 @@ This means, when we're checking our `intialized` value, storage slot 0 of our pr We can confirm this by interacting with our StorageCollisionProxy contract and calling `readStorage` and passing our storage slot. - +::image{src='/security-section-6/45-exploit-storage-collision-remix-example/exploit-storage-collision-remix-example6.png' style='width: 100%; height: auto;'} Oh my goodness. This should illustrate clearly what a `storage collision` issue looks like, and our `ThunderLoanUpgraded` is falling for it hard. diff --git a/courses/security/6-thunder-loan/46-exploit-storage-collision-poc/+page.md b/courses/security/6-thunder-loan/46-exploit-storage-collision-poc/+page.md index 9856c17ba..7b8a3433b 100644 --- a/courses/security/6-thunder-loan/46-exploit-storage-collision-poc/+page.md +++ b/courses/security/6-thunder-loan/46-exploit-storage-collision-poc/+page.md @@ -47,7 +47,7 @@ Now, if we run this test... forge test --mt testUpgradeBreaksFee -vvv ``` - +::image{src='/security-section-6/46-exploit-storage-collision-poc/exploit-storage-collision-poc1.png' style='width: 100%; height: auto;'} Holy Cow, there it is. Another High severity finding! diff --git a/courses/security/6-thunder-loan/47-exploit-storage-collision-write-up/+page.md b/courses/security/6-thunder-loan/47-exploit-storage-collision-write-up/+page.md index 95e49449b..2b9110c7c 100644 --- a/courses/security/6-thunder-loan/47-exploit-storage-collision-write-up/+page.md +++ b/courses/security/6-thunder-loan/47-exploit-storage-collision-write-up/+page.md @@ -8,7 +8,7 @@ _Follow along with the video lesson:_ ### Exploit - Storage Collision - Write Up - +::image{src='/security-section-6/47-exploit-storage-collision-write-up/exploit-storage-collision-write-up1.png' style='width: 100%; height: auto;'} Picking up where we left off, let's dive right into the write up for this `storage collision` finding, together. diff --git a/courses/security/6-thunder-loan/49-section-6-recap/+page.md b/courses/security/6-thunder-loan/49-section-6-recap/+page.md index 1a458cf00..6156b985c 100644 --- a/courses/security/6-thunder-loan/49-section-6-recap/+page.md +++ b/courses/security/6-thunder-loan/49-section-6-recap/+page.md @@ -16,7 +16,7 @@ We learnt that a Flash Loan is a loan which lasts for a single transaction. The Flash loans are a powerful and important DeFi primitive because they afford any user the ability to act as a 'whale', or someone with a lot of liquidity. - +::image{src='/security-section-6/49-section-6-recap/section-6-recap1.png' style='width: 100%; height: auto;'} ### Additional Research diff --git a/courses/security/6-thunder-loan/5-pay-back-or-revert/+page.md b/courses/security/6-thunder-loan/5-pay-back-or-revert/+page.md index 36a9fa04a..f3f033039 100644 --- a/courses/security/6-thunder-loan/5-pay-back-or-revert/+page.md +++ b/courses/security/6-thunder-loan/5-pay-back-or-revert/+page.md @@ -34,7 +34,7 @@ if (endingBalance < startingBalance + fee) { Effectively, a user taking a `flash loan` is able to do anything they want between the `transferUnderlyingTo` and the conditional check at the end of this function. This is only possible because if that check on the `endingBalance` doesn't pass, the entire transaction (and anything that was done with the loan) will revert! - +::image{src='/security-section-6/5-pay-back-or-revert/pay-back-or-revert1.png' style='width: 100%; height: auto;'} It's easy to see what opportunities a system like `flash loans` enables for the average user. No longer will these advantages be available only to whales! diff --git a/courses/security/6-thunder-loan/6-liquidity-providers/+page.md b/courses/security/6-thunder-loan/6-liquidity-providers/+page.md index 8a026d4d8..15c38c04d 100644 --- a/courses/security/6-thunder-loan/6-liquidity-providers/+page.md +++ b/courses/security/6-thunder-loan/6-liquidity-providers/+page.md @@ -16,4 +16,4 @@ Much like we saw in `TSwap`, where a `Liquidity Provider` would deposit funds in A `Liquidity Provider` would deposit funds into a `flash loan` protocol, receiving some form of LP Token representative of their contribution. The `flash loan` protocol then acrues fees as people use `flash loans`, which increases the value of a `Liquidity Provider`'s representative allotment of the pool. - +::image{src='/security-section-6/6-liquidity-providers/liquidity-providers1.png' style='width: 100%; height: auto;'} diff --git a/courses/security/6-thunder-loan/7-arbitrage-walkthrough/+page.md b/courses/security/6-thunder-loan/7-arbitrage-walkthrough/+page.md index 8737f71e3..aefc0c527 100644 --- a/courses/security/6-thunder-loan/7-arbitrage-walkthrough/+page.md +++ b/courses/security/6-thunder-loan/7-arbitrage-walkthrough/+page.md @@ -10,7 +10,7 @@ Alright, with a little more context and understanding about flash loans, let's w We'll start with a poor, average user who can only afford 1 ETH at $5. - +::image{src='/security-section-6/7-arbitrage-walkthrough/arbitrage-walkthrough1.png' style='width: 100%; height: auto;'} His options are pretty limited. How would this look with a flash loan protocol involved? @@ -23,7 +23,7 @@ Steps: > **Steps 2-4 happen in a single transaction!** - +::image{src='/security-section-6/7-arbitrage-walkthrough/arbitrage-walkthrough2.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/security/6-thunder-loan/9-recap/+page.md b/courses/security/6-thunder-loan/9-recap/+page.md index 2366024e8..a8cfdfa40 100644 --- a/courses/security/6-thunder-loan/9-recap/+page.md +++ b/courses/security/6-thunder-loan/9-recap/+page.md @@ -10,7 +10,7 @@ We're only a few lessons in and already we've covered some complex concepts, so ### Arbitrage - +::image{src='/security-section-6/9-recap/recap1.png' style='width: 100%; height: auto;'} `Arbitrage` is the market correcting action of recognizing a difference in listed asset prices between exchanges and buying from one to sell on the other, ultimately profiting while normalizing the asset price. @@ -18,7 +18,7 @@ This is one of the major use cases for `flash loans`. ### Flash Loans - +::image{src='/security-section-6/9-recap/recap2.png' style='width: 100%; height: auto;'} `Flash loans` are systems by which `liquidity providers` can loan out funds (for a fee) without collateral. **The loan must be paid back in the same transaction that it is taken.** diff --git a/courses/security/7-bridges/10-unsupported-opcodes/+page.md b/courses/security/7-bridges/10-unsupported-opcodes/+page.md index d5e006645..bb2af1108 100644 --- a/courses/security/7-bridges/10-unsupported-opcodes/+page.md +++ b/courses/security/7-bridges/10-unsupported-opcodes/+page.md @@ -30,7 +30,7 @@ In Solidity, this Assembly block is actually written in a language called `Yul`, The [**Solidity Documentation**](https://docs.soliditylang.org/en/latest/yul.html) is a great reference for what these Yul functions are doing. - +::image{src='/security-section-7/10-unsupported-opcodes/unsupported-opcodes1.png' style='width: 100%; height: auto;'} What does this mean for our Boss Bridge function? @@ -73,7 +73,7 @@ Since we have some unanswered questions, this is actually a good chance to lean By searching the checklist for "opcodes" we should be able to find: - +::image{src='/security-section-7/10-unsupported-opcodes/unsupported-opcodes2.png' style='width: 100%; height: auto;'} Hmm.. this definitely seems like it could be an important consideration in a cross chain bridge like Boss Bridge. Let's see how this applies to our situation. @@ -83,7 +83,7 @@ Run `forge build`. This should generate a JSON file for TokenFactory in our `out **If your TokenFactory.json file looks like this:** - +::image{src='/security-section-7/10-unsupported-opcodes/unsupported-opcodes3.png' style='width: 100%; height: auto;'} **...right-click and select `Format Document`.** @@ -93,7 +93,7 @@ Within this list, we expect to find the `create` opcode. A reference list for op We can definitely see it popping up, if we search our bytecode for this.. don't worry about it showing up a few time for our purposes here. - +::image{src='/security-section-7/10-unsupported-opcodes/unsupported-opcodes4.png' style='width: 100%; height: auto;'} This opcode is of course compatible with the `Ethereum` chain, but Boss Bridge is meant to work on `zkSync Era`! I wonder if the `create` opcode is supported, we should check [**their docs**](https://docs.zksync.io/). @@ -118,7 +118,7 @@ Alright, all good to know, but not exactly what we're looking for. It _does_ see To guarantee that create/create2 functions operate correctly, the compiler must be aware of the bytecode of the deployed contract in advance. " - +::image{src='/security-section-7/10-unsupported-opcodes/unsupported-opcodes5.png' style='width: 100%; height: auto;'} Uh oh. The third example we're given by the zkSync docs looks suspiciously like our `Boss Bridge` execution of `create`! diff --git a/courses/security/7-bridges/12-signatures/+page.md b/courses/security/7-bridges/12-signatures/+page.md index 1a56efacd..463d9724d 100644 --- a/courses/security/7-bridges/12-signatures/+page.md +++ b/courses/security/7-bridges/12-signatures/+page.md @@ -132,19 +132,19 @@ To run the required server we can then run: When can then open `http://localhost:3000` in a browser to access the locally hosted demo. Once set up, you can click on `signatures` in the top right and it should look something like this: - +::image{src='/security-section-7/12-signatures/signatures1.png' style='width: 100%; height: auto;'} Fundamentally a message signature takes a message, and hashes it with a user's private key. - +::image{src='/security-section-7/12-signatures/signatures2.png' style='width: 100%; height: auto;'} A public key can then be used to verify the signature on a message. - +::image{src='/security-section-7/12-signatures/signatures3.png' style='width: 100%; height: auto;'} If the message being verified has been changed, or if the public key doesn't match the signature, a message won't be verified. - +::image{src='/security-section-7/12-signatures/signatures4.png' style='width: 100%; height: auto;'} With this understanding of how signatures work refreshed in our minds, we can come back to the MessageHashUtils contract. In order to make messaging and hashes more standardized a few Improvement Proposals have been made and adopted. One of which is [**ERC-191: Signed Data Standard**](https://eips.ethereum.org/EIPS/eip-191). @@ -172,7 +172,7 @@ The standard proposed was that all signed data would follow the format: In this format the `version` denotes the type and structure of the data being signed. - +::image{src='/security-section-7/12-signatures/signatures5.png' style='width: 100%; height: auto;'} The example provided in the ERC does a great job at detailing how this works in practice. diff --git a/courses/security/7-bridges/14-eip-712/+page.md b/courses/security/7-bridges/14-eip-712/+page.md index 7d81aa4b4..3a3bd7d6a 100644 --- a/courses/security/7-bridges/14-eip-712/+page.md +++ b/courses/security/7-bridges/14-eip-712/+page.md @@ -16,11 +16,11 @@ In a practical sense this has transactions formatting data in such a way that ra From this: - +::image{src='/security-section-7/14-eip-712/eip-7121.png' style='width: 100%; height: auto;'} To this: - +::image{src='/security-section-7/14-eip-712/eip-7122.png' style='width: 100%; height: auto;'} ### EIP-712 Example diff --git a/courses/security/7-bridges/15-polygon/+page.md b/courses/security/7-bridges/15-polygon/+page.md index eb9e35366..ac4c706bc 100644 --- a/courses/security/7-bridges/15-polygon/+page.md +++ b/courses/security/7-bridges/15-polygon/+page.md @@ -64,7 +64,7 @@ Many of these passed arguments are hashed into the transaction data and then the Something to note about ecrecover - it returns `address(0)` on an error! - +::image{src='/security-section-7/15-polygon/polygon1.png' style='width: 100%; height: auto;'} The EVM actually _does_ have a check to assure `address(0)` isn't returned, but this was never copied into the ecrecovery wrapper, which means ecrecovery - still returns `address(0)`. diff --git a/courses/security/7-bridges/18-deposit-token/+page.md b/courses/security/7-bridges/18-deposit-token/+page.md index 243690d66..d13ce3474 100644 --- a/courses/security/7-bridges/18-deposit-token/+page.md +++ b/courses/security/7-bridges/18-deposit-token/+page.md @@ -33,7 +33,7 @@ function depositTokensToL2(address from, address l2Recipient, uint256 amount) ex The `NATSPEC` here nicely details the functionality we'd expect based on our earlier protocol diagram. This function locks tokens into the `vault`, which emits an event. This event is listened for off-chain to trigger the mint of tokens on L2. - +::image{src='/security-section-7/18-deposit-token/deposit-token1.png' style='width: 100%; height: auto;'} Foremost, we can see that a user can only call this function if the protocol is not paused, via the `whenNotPaused` modifier. diff --git a/courses/security/7-bridges/19-arbitrary/+page.md b/courses/security/7-bridges/19-arbitrary/+page.md index b12e834ee..9088cc6ff 100644 --- a/courses/security/7-bridges/19-arbitrary/+page.md +++ b/courses/security/7-bridges/19-arbitrary/+page.md @@ -16,7 +16,7 @@ If not, that's ok. Our friend Slither is here to help us. This vulnerabilitiy is make slither ``` - +::image{src='/security-section-7/19-arbitrary/arbitrary1.png' style='width: 100%; height: auto;'} `arbitrary from in transferFrom: token.safeTransferFrom(from,address(vault),amount)` Hmm, what does this mean exactly? If we're ever unsure of what Slither is telling us, we can follow [**the link**](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom) in the output to read more about the detector and what it's .. detecting. diff --git a/courses/security/7-bridges/2-phase-1-scoping/+page.md b/courses/security/7-bridges/2-phase-1-scoping/+page.md index 3abf8a976..2057f6621 100644 --- a/courses/security/7-bridges/2-phase-1-scoping/+page.md +++ b/courses/security/7-bridges/2-phase-1-scoping/+page.md @@ -94,7 +94,7 @@ Alright, things look pretty good. Let's see how thorough the test coverage on Bo forge coverage ``` - +::image{src='/security-section-7/2-phase-1-scoping/phase-1-scoping1.png' style='width: 100%; height: auto;'} Ok, ok.. I see you Boss Bridge. This team clearly has _some_ knowledge about security best practices. We've got some room for improvement though with a couple missing functions/branches. We'll have to take a closer look at this test suite for sure. @@ -108,7 +108,7 @@ Before moving on we should definitely acquire a sense of the size of this code b Right-click the `src` folder and select `Solidity: Metrics`. - +::image{src='/security-section-7/2-phase-1-scoping/phase-1-scoping2.png' style='width: 100%; height: auto;'} We can see this code base is quite a bit smaller and arguably less complex than some we've been over already. It's about half the size of ThunderLoan! diff --git a/courses/security/7-bridges/20-arbitrary-poc/+page.md b/courses/security/7-bridges/20-arbitrary-poc/+page.md index 282e372da..47fe20883 100644 --- a/courses/security/7-bridges/20-arbitrary-poc/+page.md +++ b/courses/security/7-bridges/20-arbitrary-poc/+page.md @@ -82,7 +82,7 @@ Now, we can run this test. forge test --mt testCanMoveApprovedTokensOfOtherUsers -vvv ``` - +::image{src='/security-section-7/20-arbitrary-poc/arbitrary-poc1.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/security/7-bridges/21-recon-continued-again/+page.md b/courses/security/7-bridges/21-recon-continued-again/+page.md index f586e9a80..20d0b769d 100644 --- a/courses/security/7-bridges/21-recon-continued-again/+page.md +++ b/courses/security/7-bridges/21-recon-continued-again/+page.md @@ -10,7 +10,7 @@ _Follow along with the video lesson:_ `Slither` did a great job detecting a `high severity` vulnerability in our protocol, in the last lesson. Let's see what else it has to show us. - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again1.png' style='width: 100%; height: auto;'} `sends eth to arbitrary user`, I wonder what this is identifying. Let's navigate to the link `Slither` provides for detail. @@ -55,7 +55,7 @@ function sendToL1(uint8 v, bytes32 r, bytes32 s, bytes memory message) public no Ok, what's the next potential issue detected by Slither? - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again2.png' style='width: 100%; height: auto;'} `ignores return value by token.approve(target,amount)`, this seems to be identified within L1Vault.sol. @@ -75,13 +75,13 @@ In other circumstances, something like this may be higher severity, but we know, **Slither's next output:** - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again3.png' style='width: 100%; height: auto;'} This detection is pointing out `zero-address checks`. Would be fine, but the protocol outlines `zero-address checks` being omitted intentionally to save gas. Known issue, moving on! **Next output:** - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again4.png' style='width: 100%; height: auto;'} OooOoo `Reentrancy in L1BossBridge::depositTokensToL2`, this sounds like it has potential, let's take a look at the function and discuss what may be happening here. @@ -108,11 +108,11 @@ function depositTokensToL2(address from, address l2Recipient, uint256 amount) ex **Next output:** - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again5.png' style='width: 100%; height: auto;'} `Slither`... just doesn't like `Assembly`, so it calls it out any time it sees it. We're using `Assembly` intentionally, so we can ignore this one. The next few are actually non-issues for us, so let's address them all at once. - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again6.png' style='width: 100%; height: auto;'} 1. `Slither` detects different versions of solidity being used in `Boss Bridge`, but we can see that the 4 contracts we're concerned about all use `0.8.20`. This should be fine. @@ -122,7 +122,7 @@ function depositTokensToL2(address from, address l2Recipient, uint256 amount) ex **Next output:** - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again7.png' style='width: 100%; height: auto;'} Alright, now we're onto something, albeit something small. It seems the `L1BossBridge` variable `DEPOSIT_LIMIT` never changes and as such should be declared as a constant. An easy informational finding. @@ -133,7 +133,7 @@ uint256 public DEPOSIT_LIMIT = 100_000 ether; Similarly, our next `Slither` output is calling out that our `token` variable, in `L1Vault.sol` could be declared as immutable. - +::image{src='/security-section-7/21-recon-continued-again/recon-continued-again8.png' style='width: 100%; height: auto;'} ```js // @Audit-Informational: Unchanged state variables could be declared as constant diff --git a/courses/security/7-bridges/22-infinite-mint/+page.md b/courses/security/7-bridges/22-infinite-mint/+page.md index 7c9cd5c5e..b7c2f9377 100644 --- a/courses/security/7-bridges/22-infinite-mint/+page.md +++ b/courses/security/7-bridges/22-infinite-mint/+page.md @@ -84,7 +84,7 @@ Let's run the test. forge test --mt testCanTransferFromVaultToVault ``` - +::image{src='/security-section-7/22-infinite-mint/infinite-mint1.png' style='width: 100%; height: auto;'} Yikes. diff --git a/courses/security/7-bridges/27-signature-replay-poc/+page.md b/courses/security/7-bridges/27-signature-replay-poc/+page.md index b733c78a9..9c9a07b15 100644 --- a/courses/security/7-bridges/27-signature-replay-poc/+page.md +++ b/courses/security/7-bridges/27-signature-replay-poc/+page.md @@ -128,7 +128,7 @@ Ok, let's run it and see how Boss Bridge responds to our signature replay attack forge test --mt testSignatureReplay --vvv ``` - +::image{src='/security-section-7/27-signature-replay-poc/signature-replay-poc1.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/security/7-bridges/3-recon/+page.md b/courses/security/7-bridges/3-recon/+page.md index 4f03e6d27..3b97bc2db 100644 --- a/courses/security/7-bridges/3-recon/+page.md +++ b/courses/security/7-bridges/3-recon/+page.md @@ -16,7 +16,7 @@ The Boss Bridge repo includes make commands for us! make slither ``` - +::image{src='/security-section-7/3-recon/recon1.png' style='width: 100%; height: auto;'} Oh .. ok, wow, we've got some work to do. We'll .. go through these later 😅 diff --git a/courses/security/7-bridges/31-recap/+page.md b/courses/security/7-bridges/31-recap/+page.md index 2cf9b4b98..3a476fd37 100644 --- a/courses/security/7-bridges/31-recap/+page.md +++ b/courses/security/7-bridges/31-recap/+page.md @@ -16,7 +16,7 @@ Before we recap things, I see I overlooked a tool that could be valuable. Boss B [**evmdiff**](https://www.evmdiff.com/) Allows you to directly compare two chains and clearly identify their differences. For example, Arbitrum One has a number of additional precompiles that aren't present on Ethereum: - +::image{src='/security-section-7/31-recap1/recap1.png' style='width: 100%; height: auto;'} Another common difference between chains is op code support - Arbitrum, until recently, didn't support the PUSH0 op code! diff --git a/courses/security/7-bridges/4-checklist/+page.md b/courses/security/7-bridges/4-checklist/+page.md index 499157d98..e1187e6a6 100644 --- a/courses/security/7-bridges/4-checklist/+page.md +++ b/courses/security/7-bridges/4-checklist/+page.md @@ -12,17 +12,17 @@ In this section of the course, unlike the others, we're going to more closely ap Previously only on [**GitHub**](https://github.com/Cyfrin/audit-checklist), this checklist is now [**hosted on Solodit**](https://solodit.xyz/checklist) directly! - +::image{src='/security-section-7/4-checklist/checklist1.png' style='width: 100%; height: auto;'} This checklist serves as an itemized reference of things that should be checked when performing an audit. It breaks down a huge number of vulnerabilities and attack vectors as well as provides descriptions, references and remediations. Each section of the checklist poses questions a security researcher should ask themselves to determine the risk and applicability to a code base they're reviewing. - +::image{src='/security-section-7/4-checklist/checklist2.png' style='width: 100%; height: auto;'} If that wasn't enough, in addition to the conveniences above, this is a living document where open sources contributions are welcome and encouraged. By selecting `edit` an auditor is able to submit a new issue on GitHub to have vetted and added to the checklist. - +::image{src='/security-section-7/4-checklist/checklist3.png' style='width: 100%; height: auto;'} This tool is incredibly valuable and I encourage you to get acquainted. We'll be putting it to good use throughout Boss Bridge. diff --git a/courses/security/7-bridges/6-boss-bridge-diagram/+page.md b/courses/security/7-bridges/6-boss-bridge-diagram/+page.md index a3fbb29f0..dad46455a 100644 --- a/courses/security/7-bridges/6-boss-bridge-diagram/+page.md +++ b/courses/security/7-bridges/6-boss-bridge-diagram/+page.md @@ -8,7 +8,7 @@ _Follow along with the video lesson:_ ### Boss Bridge Diagram - +::image{src='/security-section-7/6-boss-bridge-diagram/boss-bridge-diagram1.png' style='width: 100%; height: auto;'} How does your diagram compare to mine? You _did_ try to do one .. **_right?_** diff --git a/courses/security/7-bridges/7-l1-token/+page.md b/courses/security/7-bridges/7-l1-token/+page.md index 6374753c1..aede43070 100644 --- a/courses/security/7-bridges/7-l1-token/+page.md +++ b/courses/security/7-bridges/7-l1-token/+page.md @@ -16,7 +16,7 @@ Let's employ The Tincho and start with the smallest contract again and work our Right-click the `src` folder and select `Solidity: Metrics` to generate our report. - +::image{src='/security-section-7/7-l1token/l1token1.png' style='width: 100%; height: auto;'} It looks like our smallest contract is going to be L1Token.sol, so let's start there. diff --git a/courses/security/7-bridges/8-vault/+page.md b/courses/security/7-bridges/8-vault/+page.md index eda6af62b..db667f070 100644 --- a/courses/security/7-bridges/8-vault/+page.md +++ b/courses/security/7-bridges/8-vault/+page.md @@ -8,7 +8,7 @@ _Follow along with the video lesson:_ ### L1Vault.sol - +::image{src='/security-section-7/8-vault/l1vault1.png' style='width: 100%; height: auto;'} Alright! The next most complex contract that our Solidity Metrics report identifies is `L1Vault.sol`. Let's take a look at that contract next. diff --git a/courses/security/7-bridges/9-tokenfactory/+page.md b/courses/security/7-bridges/9-tokenfactory/+page.md index 438939752..24c55cb8b 100644 --- a/courses/security/7-bridges/9-tokenfactory/+page.md +++ b/courses/security/7-bridges/9-tokenfactory/+page.md @@ -8,7 +8,7 @@ _Follow along with the video lesson:_ ### TokenFactory.sol - +::image{src='/security-section-7/9-tokenfactory/tokenfactory1.png' style='width: 100%; height: auto;'} Two down, two to go! The next contract we should take a look at is TokenFactory.sol. We should recall from our diagram this is is responsible for deploying L1Token.sol. diff --git a/courses/security/8-mev-and-governance/10-mev-live-again/+page.md b/courses/security/8-mev-and-governance/10-mev-live-again/+page.md index d81340ad4..a3b5b9301 100644 --- a/courses/security/8-mev-and-governance/10-mev-live-again/+page.md +++ b/courses/security/8-mev-and-governance/10-mev-live-again/+page.md @@ -21,7 +21,7 @@ And I'm here to tell you, it doesn't matter. The bots simulate the transaction, We look at a [modified example](https://github.com/Cyfrin/sc-exploits-minimized/blob/main/src/MEV/Bouncer.sol) where we add a "bouncer" contract to try to "block" the transactions. -bouncer +::image{src='/security-section-8/10-mev-live-again/bouncer.png' style='width: 100%; height: auto;' alt='bouncer'} ```javascript // SPDX-License-Identifier: MIT diff --git a/courses/security/8-mev-and-governance/11-case-study-pashov/+page.md b/courses/security/8-mev-and-governance/11-case-study-pashov/+page.md index cd3e183c8..40a10426b 100644 --- a/courses/security/8-mev-and-governance/11-case-study-pashov/+page.md +++ b/courses/security/8-mev-and-governance/11-case-study-pashov/+page.md @@ -37,7 +37,7 @@ You're highly encouraged to read through these articles for more context and a d `Sandwich Attacks` are similar in nature to `Just In Time (JIT)` liquidity exploits in that they both involve a `front run` and `back run` phase of the attack. -pashov +::image{src='/security-section-8/11-case-study-pashov/case-study-pashov1.png' style='width: 100%; height: auto;' alt='pashov'} In the diagram above, the user ends up paying way more for their expected swap! diff --git a/courses/security/8-mev-and-governance/12-mev-prevention/+page.md b/courses/security/8-mev-and-governance/12-mev-prevention/+page.md index 0e2a74d10..beecae388 100644 --- a/courses/security/8-mev-and-governance/12-mev-prevention/+page.md +++ b/courses/security/8-mev-and-governance/12-mev-prevention/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ Our first line of defense against MEV is to refine our designs. To illustrate this, let's revisit our [**Puppy Raffle repo**](https://github.com/Cyfrin/4-puppy-raffle-audit/blob/main/src/PuppyRaffle.sol). The issue was when `selectWinner` was called. - +::image{src='/security-section-8/12-mev-prevention/mev-prevention1.png' style='width: 100%; height: auto;'} How can we protect Puppy Raffle from MEV attacks? Well, we can do a couple things. @@ -57,7 +57,7 @@ There's no universal statement that covers all the possible situations in which Another thing we can consider for defence is the use of a private or "dark" `mempool`, such as [**Flashbots Protect**](https://docs.flashbots.net/flashbots-protect/overview), [**MEVBlocker**](https://mevblocker.io/) or [**Securerpc**](https://securerpc.com/). -pashov +::image{src='/security-section-8/12-mev-prevention/flashbots.png' style='width: 100%; height: auto;' alt='pashov'} Instead of submitting your transaction to a `public mempool`, you can send your transaction to this `private mempool`. Unlike the `public mempool`, this keeps the transaction for itself until it's time to post it on the chain. diff --git a/courses/security/8-mev-and-governance/2-perseverance/+page.md b/courses/security/8-mev-and-governance/2-perseverance/+page.md index 38b306015..f1ce6e025 100644 --- a/courses/security/8-mev-and-governance/2-perseverance/+page.md +++ b/courses/security/8-mev-and-governance/2-perseverance/+page.md @@ -20,7 +20,7 @@ Securing Web3 isn't a one man/woman job. Collaboration and working together is a As always, there'll be an audit-data branch of the Vault Guardians repo to compare your results to. -vault guardians +::image{src='/security-section-8/2-perserverance/vault-guardians.png' style='width: 100%; height: auto;' alt='vault guardians'} ### Wrap Up diff --git a/courses/security/8-mev-and-governance/3-mev-introduction/+page.md b/courses/security/8-mev-and-governance/3-mev-introduction/+page.md index 80905ab18..8b297befd 100644 --- a/courses/security/8-mev-and-governance/3-mev-introduction/+page.md +++ b/courses/security/8-mev-and-governance/3-mev-introduction/+page.md @@ -14,11 +14,11 @@ In order to develop an in-depth understanding, I would highly recommend visiting ## What is the mempool? -regular transaction +::image{src='/security-section-8/3-mev-introduction/regular-transaction.png' style='width: 100%; height: auto;' alt='regular transaction'} When a transaction is initiated it uses an RPC_URL, as we know. This URL points to a specific node on the blockchain which, instead of immediately integrating it into its block, places it into its 'memory pool', or 'mempool'. This constitutes the lower tier of workings that enable blockchain. -mempool +::image{src='/security-section-8/3-mev-introduction/mempool.png' style='width: 100%; height: auto;' alt='mempool'} As we know, nodes essentially "take turns" building blocks for the blockchain. So if you send your transaction to a single node, the node will have to wait until it's that node's turn to include your transaction! This could take months! @@ -36,6 +36,6 @@ If a malicious actor were to see a transaction in this waiting room that would b The malicious actor's transaction would execute before the victims! -front-running +::image{src='/security-section-8/3-mev-introduction/mev.svg' style='width: 100%; height: auto;' alt='front-running'} This is called Front-Running and is one of the most common forms of MEV. Let's look at a more minimal diagram in the next lesson before moving on. diff --git a/courses/security/8-mev-and-governance/4-mev-minimized/+page.md b/courses/security/8-mev-and-governance/4-mev-minimized/+page.md index 17cc39a47..1341c59f7 100644 --- a/courses/security/8-mev-and-governance/4-mev-minimized/+page.md +++ b/courses/security/8-mev-and-governance/4-mev-minimized/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ We can take a look at this image to see a minimized visual representation of what MEV looks like. In specific, this kind of MEV is known as "front-running". -regular transaction +::image{src='/security-section-8/4-mev-minimized/minimized.png' style='width: 100%; height: auto;' alt='regular transaction'} ### MEV - Everywhere diff --git a/courses/security/8-mev-and-governance/5-mev-in-puppy-raffle/+page.md b/courses/security/8-mev-and-governance/5-mev-in-puppy-raffle/+page.md index afdca5040..a273f25f1 100644 --- a/courses/security/8-mev-and-governance/5-mev-in-puppy-raffle/+page.md +++ b/courses/security/8-mev-and-governance/5-mev-in-puppy-raffle/+page.md @@ -56,7 +56,7 @@ A user does this by recognizing that they lost, and then paying more gas to have This will lower the prize of the winner! - +::image{src='/security-section-8/5-puppy-mev/mev-in-puppy-raffle1.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/security/8-mev-and-governance/6-mev-t-swap/+page.md b/courses/security/8-mev-and-governance/6-mev-t-swap/+page.md index 0092af9a0..764a7eb49 100644 --- a/courses/security/8-mev-and-governance/6-mev-t-swap/+page.md +++ b/courses/security/8-mev-and-governance/6-mev-t-swap/+page.md @@ -102,7 +102,7 @@ Imagine a node operator happened to be a `liquidity provider` in `TSwap`. This o This malicious node operator would have the power to delay the processing of this `deposit` transaction in favor of validating more swap transactions maximizing the fees they would obtain from the protocol at the expensive of the new depositer! - +::image{src='/security-section-8/6-tswap-mev/mev-in-tswap1.png' style='width: 100%; height: auto;'} ### Wrap Up diff --git a/courses/security/8-mev-and-governance/7-mev-thunder-loan/+page.md b/courses/security/8-mev-and-governance/7-mev-thunder-loan/+page.md index d1b50aee7..8c86f5ef2 100644 --- a/courses/security/8-mev-and-governance/7-mev-thunder-loan/+page.md +++ b/courses/security/8-mev-and-governance/7-mev-thunder-loan/+page.md @@ -18,7 +18,7 @@ By closely monitoring the `mempool`, a malicious actor would be able to see a pe The malicious actor can then **swap back** (this is called `back running`) before the loan's repayment checks TSwap again! This would drastically impact the flash loan experience in Thunder Loan and may cause several of them to fail, or worse - cost victims a tonne in unexpected fees. - +::image{src='/security-section-8/7-thunder-loan-mev/mev-thunder-loan1.png'} ### Wrap Up diff --git a/courses/security/8-mev-and-governance/8-mev-boss-bridge/+page.md b/courses/security/8-mev-and-governance/8-mev-boss-bridge/+page.md index 57b2562da..39c61f108 100644 --- a/courses/security/8-mev-and-governance/8-mev-boss-bridge/+page.md +++ b/courses/security/8-mev-and-governance/8-mev-boss-bridge/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ Now you're starting to see the picture, and the Boss Bridge MEV becomes clear. -boss bridge mev +::image{src='/security-section-8/8-mev-boss/mev-boss-bridge1.png' style='width: 100%; height: auto;' alt='boss bridge mev'} Similarly to the Signature Replay attack, a malicious actor could see a signer's call to sendToL1 pending in the MemPool. With access to the signature sent in the transaction, it can be front run, causing the sendToL1 transaction to happen unexpectedly, or multiple times. diff --git a/courses/solidity/1-simple-storage/10-mappings/+page.md b/courses/solidity/1-simple-storage/10-mappings/+page.md index 61d6df7b5..c826d0665 100644 --- a/courses/solidity/1-simple-storage/10-mappings/+page.md +++ b/courses/solidity/1-simple-storage/10-mappings/+page.md @@ -41,10 +41,10 @@ Previously, we created an `addPerson` function that was adding a struct `Person` nameToFavoriteNumber[_name] = _favoriteNumber; ``` -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Mappings have a constant time complexity for lookups, meaning that retrieving a value by its key is done in constant time, -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > The default value for all key types is zero. In our case, `nameToFavoriteNumber["ET"]` will be equal to 0. ### Conclusion diff --git a/courses/solidity/1-simple-storage/11-deploying/+page.md b/courses/solidity/1-simple-storage/11-deploying/+page.md index 9c599f987..391140bd7 100644 --- a/courses/solidity/1-simple-storage/11-deploying/+page.md +++ b/courses/solidity/1-simple-storage/11-deploying/+page.md @@ -8,7 +8,7 @@ _You can follow along with the video course from here._ Over the past eight lessons, we crafted the `SimpleStorage` contract. It defines a custom type `Person`, includes an internal variable that can be read and updated, and contains a public array and mapping that can also be modified. In this lesson, we will deploy the contract to a **real testnet**, which fully simulates a live blockchain environment without using real Ether. -> 🔥 **CAUTION**
+> 🔥 **CAUTION**:br > You could be tempted to immediately deploy this contract to a testnet. As a general rule, I caution against this. Make sure to write tests, carry out audits and ensure the robustness of your contract before deploying it to production. However, for the sake of this demonstration, we're going to deploy this as a dummy contract on a testnet. Before deploying, be always sure to make a **compilation check**. This ensures that the contract has no errors or warnings and is fit for deployment. @@ -16,28 +16,28 @@ Before deploying, be always sure to make a **compilation check**. This ensures t ### Deployment on a testnet We can start the deployment process by going into the deployment tab and switching from the local virtual environment (Remix VM) to the Injected Provider - MetaMask. This action will allow Remix to send requests and interact with your MetaMask account. - +::image{src='/solidity/remix/lesson-2/deploying/deploying2.png' style='width: 100%; height: auto;'} You will be then prompted to select an account from your MetaMask wallet. Once you've connected that account to Remix, you should see a confirmation that the account is properly linked and that you are using the Sepolia testnet. - +::image{src='/solidity/remix/lesson-2/deploying/deploying3.png' style='width: 100%; height: auto;'} Ensure you have enough Sepolia ETH in your account, which you can obtain from a [faucet](https://www.alchemy.com/faucets/ethereum-sepolia). Once your balance is sufficient, you can proceed by clicking the "Deploy" button. After that, MetaMask will ask to sign and send the transaction on the testnet. - +::image{src='/solidity/remix/lesson-2/deploying/deploying4.png' style='width: 100%; height: auto;'} Once the transaction is executed, the contract address will be listed under deployed contracts, along with the transaction details. This is how the deployment transaction is displayed on Etherscan. - +::image{src='/solidity/remix/lesson-2/deploying/deploying6.png' style='width: 100%; height: auto;'} ### Contract interaction Since the contract has been deployed, we can now interact with it and **update the blockchain**. For example, if you want to store a number, you can do so by clicking the button 'store': MetaMask will ask for another transaction confirmation, that will update the favorite number. We can check the details on etherscan at the deployed address: - +::image{src='/solidity/remix/lesson-2/deploying/deploying7.png' style='width: 100%; height: auto;'} -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > View and pure functions will not send transactions -> 💡 **TIP**
> _Celebrate small victories and milestones. These psychological boosts will keep you engaged in the learning process._ +> 💡 **TIP**:br > _Celebrate small victories and milestones. These psychological boosts will keep you engaged in the learning process._ It's possible to deploy a contract to different testnets or a real mainnet, just by switching the Metamask network. Be sure to have enough net-compatible ETHs to deploy your contract. diff --git a/courses/solidity/1-simple-storage/15-zksync-plugin/+page.md b/courses/solidity/1-simple-storage/15-zksync-plugin/+page.md index 8879994aa..1fee63afb 100644 --- a/courses/solidity/1-simple-storage/15-zksync-plugin/+page.md +++ b/courses/solidity/1-simple-storage/15-zksync-plugin/+page.md @@ -10,7 +10,7 @@ _Follow along with the video_ In this lesson, you're about to learn the same type of layer 2 or rollup deployment that professional developers are using. On Remix, we can start by activating the **zkSync plugin** in our environment. In the _plugin manager_, search for "zkSync" and activate the zkSync module. You'll notice that a new zkSync tab on the left side will appear. - +::image{src='/solidity/1-simple-storage/15-zksync-plugin/zksync-module.png' style='width: 100%; height: auto;'} This module is made of sections for compiling, deploying, and interacting with contracts on zkSync. @@ -18,14 +18,14 @@ This module is made of sections for compiling, deploying, and interacting with c Let's start by compiling the `SimpleStorage.sol` file by hitting the "Compile" button. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Ensure that the **Solidity Compiler Version** in the contract matches the _[zkSync compiler requirements](https://github.com/Cyfrin/foundry-full-course-cu?tab=readme-ov-file#zksync-l2-deploy)_. As of this recording, the required version is `0.8.24`. ### Deploying After compilation, you can go to the `environment tab` to connect your MetaMask wallet, ensuring it is set to the _zkSync Sepolia testnet_. Once connected, you can **deploy and verify** the `SimpleStorage` contract. - +::image{src='/solidity/1-simple-storage/15-zksync-plugin/wallet.png' style='width: 100%; height: auto;'} ### Verifying Deployment @@ -35,7 +35,7 @@ After hitting the deploy button, MetaMask will request a **signature**. Approve To check our deployment, you can copy the contract address and paste it into the [zksync Sepolia explorer](https://sepolia.explorer.zksync.io/). Here, you can view the contract details. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > At the moment of recording, the zkSync plugin contains a minor bug. Please refer to lesson 14. ### Conclusion diff --git a/courses/solidity/1-simple-storage/16-zksync-interactions/+page.md b/courses/solidity/1-simple-storage/16-zksync-interactions/+page.md index 90a7a737b..84948f3d2 100644 --- a/courses/solidity/1-simple-storage/16-zksync-interactions/+page.md +++ b/courses/solidity/1-simple-storage/16-zksync-interactions/+page.md @@ -8,6 +8,6 @@ _Follow along with the video_ > In the zkSync module section `transactions`, you'll find buttons for calling `SimpleStorage` functions like `addPerson`, `listOfPeople`, `nameToFavoriteNumber`, `retrieve`, and `store`. Clicking the blue buttons will display the output in the terminal, while the orange buttons are used for storing values. - +::image{src='/solidity/1-simple-storage/16-zksync-interactions/interactions' style='width: 100%; height: auto;'} For example, when you click the orange `store` button and enter the number `77`, MetaMask will prompt you for confirmation. Once confirmed, you can click `retrieve` to see the stored value. Feel free to experiment with these functions, but keep in mind that the testnet can sometimes be a bit slow. diff --git a/courses/solidity/1-simple-storage/18-evm-recap/+page.md b/courses/solidity/1-simple-storage/18-evm-recap/+page.md index e9c5d02ba..3fcdf11a9 100644 --- a/courses/solidity/1-simple-storage/18-evm-recap/+page.md +++ b/courses/solidity/1-simple-storage/18-evm-recap/+page.md @@ -13,7 +13,7 @@ In this section, we'll quickly summarize the lessons from 1 to 9 and learn about EVM stands for _Ethereum Virtual Machine_. It's a decentralized computational engine that executes smart contracts. Any contract that it's written in Solidity, can be deployed to any EVM-compatible blockchain. Examples of such blockchains and Layer 2 solutions include **Ethereum**, **Polygon**, **Arbitram**, **Optimism**, and **Zksync**. -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > Although a blockchain like Zksync may be EVM-compatible, it is essential to verify that all Solidity keywords are supported ### Contract Setup diff --git a/courses/solidity/1-simple-storage/3-introduction/+page.md b/courses/solidity/1-simple-storage/3-introduction/+page.md index c80eb50a6..defe11ea6 100644 --- a/courses/solidity/1-simple-storage/3-introduction/+page.md +++ b/courses/solidity/1-simple-storage/3-introduction/+page.md @@ -10,7 +10,7 @@ _Follow along with this video:_ To get started, navigate to the official _Cyfrin Updraft_ [GitHub repository](https://github.com/Cyfrin/foundry-full-course-f23) -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Each course will have an associated link, where you’ll find all the **code** that you will be working on within in each lesson and a > **README** section, that contains instructions on how to work with the code. @@ -23,7 +23,7 @@ The repository serves two main purposes: - 🚪 **Easy access:** each lesson can be consulted and cloned effortlessly - 🗣 👥 **Discussion and Network:** you can engage with fellow students, ask questions, and participate in collaborative learning. -> 🔥 **CAUTION**
+> 🔥 **CAUTION**:br > To raise issues or start discussions based on a specific repository, please use the [**Discussions tab**](https://github.com/Cyfrin/foundry-full-course-f23/discussions) of the _Cyfrin Updraft - Career Path_ instead of creating issues directly on the repository itself. #### Asking Questions @@ -46,7 +46,7 @@ Having an account on the following platforms is highly recommended: Now comes the exciting part: building and deploying your first smart contract. We're going to be utilizing a tool called [Remix](https://remix.ethereum.org/), an IDE (Integrated Development Environment) for deploying and interacting with smart contracts. You can access it through this [link](https://remix.ethereum.org/). -> 💡 **TIP**
+> 💡 **TIP**:br > The best way to get the most out of this guide is to **code along**. You're encouraged to change the speed of the tutorial video to match your coding pace. Remember that **space repetition** is critical while building a new skill. After concluding the next lesson, you'll have already built and deployed your first smart contract to a blockchain. Let's jump right into it! @@ -56,5 +56,5 @@ After concluding the next lesson, you'll have already built and deployed your fi At the end of each lesson, you will find a _Test Yourself_ section. This part will help you reinforce the concepts you just learned and coded about. There will be _theoretical_ questions - marked with 📕, as well as _coding_ questions -marked with 👨‍💻. -> 💡 **TIP**
+> 💡 **TIP**:br > Be sure that you _truly understand_ the answers before going on to the next lesson. diff --git a/courses/solidity/1-simple-storage/4-setting-up-your-first-contract/+page.md b/courses/solidity/1-simple-storage/4-setting-up-your-first-contract/+page.md index 4ae32aca8..dc2a83464 100644 --- a/courses/solidity/1-simple-storage/4-setting-up-your-first-contract/+page.md +++ b/courses/solidity/1-simple-storage/4-setting-up-your-first-contract/+page.md @@ -39,7 +39,7 @@ pragma solidity ^0.8.19; pragma solidity >=0.8.19 <0.9.0; ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Remember to write comments in your code for you to refer to later on. ### SPDX License Identifier diff --git a/courses/solidity/1-simple-storage/5-basic-types/+page.md b/courses/solidity/1-simple-storage/5-basic-types/+page.md index ebd76f59f..a1a2808e4 100644 --- a/courses/solidity/1-simple-storage/5-basic-types/+page.md +++ b/courses/solidity/1-simple-storage/5-basic-types/+page.md @@ -28,7 +28,7 @@ bool hasFavoriteNumber = true; //the variable hasFavoriteNumber` represents the It's possible to specify the number of **bits** used for `uint` and `int`. For example, uint256 specifies that the variable has 256 bits. uint is a shorthand for uint256. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > It's always advisable to be **explicit** when specifying the length of the data type. The _semicolon_ at the end of each line signifies that a statement is completed. @@ -72,7 +72,7 @@ Bytes can be allocated in size (up to `bytes32`). However, bytes and bytes32 rep uint256 favoriteNumber; ``` -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Every variable in Solidity comes with a _default value_. Uninitialized uint256 for example, defaults to `0` (zero) and an uninitialized boolean defaults to `false`. ## Conclusion diff --git a/courses/solidity/1-simple-storage/6-functions/+page.md b/courses/solidity/1-simple-storage/6-functions/+page.md index c1c70c0c9..78b92fc43 100644 --- a/courses/solidity/1-simple-storage/6-functions/+page.md +++ b/courses/solidity/1-simple-storage/6-functions/+page.md @@ -49,15 +49,15 @@ At this stage, you can compile your code by navigating to the compile tab and hi The **Deploy and Run Transactions** tab holds a variety of parameters that are used during the deployment process. You'll be assigned an _account_ with some ETH to deploy your smart contract. - +::image{src='/solidity/remix/lesson-2/functions/deploy_and_run.png' style='width: 100%; height: auto;'} In this environment, your contract is assigned a unique address. You can re-access your deployed contract by expanding the **Deployed Contracts** interface and simultaneously opening the terminal, which shows log data of all contract deployments and transactions. - +::image{src='/solidity/remix/lesson-2/functions/deployment_address.png' style='width: 100%; height: auto;'} If we open the Remix terminal we can see that deploying the contract has just sent a simulated transaction on the Remix environment. You can check out its details such as status, hash, from, to and gas. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > The process of sending a transaction is the **same** for deploying a contract and for sending Ethers. The only difference is that the machine-readable code of the deployed contract is placed inside the _data_ field of the deployment transaction. ### Transactions creation @@ -72,7 +72,7 @@ This contract is missing a way to check if the number has been updated: now we c The default visibility of the `favoriteNumber` variable is **internal**, preventing external contracts and users from viewing it. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Appending the `public` keyword next to a variable will automatically change its visibility and it will generate a **get function**. ```solidity @@ -81,7 +81,7 @@ uint256 public favoriteNumber; After completing compilation and deployment, a button labelled `favoriteNumber` will become visible. When pressed, it should return the most recent stored value of the variable `favoriteNumber`. - +::image{src='/solidity/remix/lesson-2/functions/favorite-number.png' style='width: 100%; height: auto;'} #### Visibility @@ -110,11 +110,11 @@ function retrieve() public pure returns(uint256){ } ``` - +::image{src='/solidity/remix/lesson-2/functions/blue-button.png' style='width: 50%; height: auto;'} The keyword `returns` specifies the type(s) of value a function will return. -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > While calling `view` or `pure` functions doesn’t typically require gas, they do require it when called by another function that modifies the state or storage through a transaction (e.g. calling the function `retrieve` inside the function `storage`). This cost is called **execution cost** and it will add up to the transaction cost. ### The scope of a variable diff --git a/courses/solidity/1-simple-storage/7-arrays-and-structs/+page.md b/courses/solidity/1-simple-storage/7-arrays-and-structs/+page.md index 5557be44b..65020396f 100644 --- a/courses/solidity/1-simple-storage/7-arrays-and-structs/+page.md +++ b/courses/solidity/1-simple-storage/7-arrays-and-structs/+page.md @@ -22,7 +22,7 @@ The brackets indicate that we have a list of `uint256`, an array of numbers. If uint256[] list_of_favorite_numbers = [0, 78, 90]; ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Arrays are zero-indexed: the first element is at position zero (0), the second is position (index) 1, and so on. The issue with this data structure is that we cannot link the owner with its favorite value. One solution is to establish a **new type** using the `struct` keyword, named `Person`, which is made of two _attributes_: a favorite number and a name. @@ -34,7 +34,7 @@ struct Person { } ``` -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > Rename the variables `favorite_number` to avoid name clashes From this struct, we can instantiate a variable `my_friend` that has the type `Person`, with a favorite number of seven and the name 'Pat'. We can retrieve these details using the getter function that was generated by the `public` keyword. diff --git a/courses/solidity/1-simple-storage/8-errors-and-warnings/+page.md b/courses/solidity/1-simple-storage/8-errors-and-warnings/+page.md index d92c8cc73..0592e7226 100644 --- a/courses/solidity/1-simple-storage/8-errors-and-warnings/+page.md +++ b/courses/solidity/1-simple-storage/8-errors-and-warnings/+page.md @@ -12,7 +12,7 @@ In the previous lesson, we learned how to combine _arrays_ and _structs_ to stor If we remove a semicolon from the code and then try to compile it, you'll encounter some 🚫 **error messages**. They will prevent the compiler from converting the code into a machine-readable form. - +::image{src='/solidity/remix/lesson-2/errors-warnings/errors2.png' style='width: 100%; height: auto;'} Restoring the semicolon to its correct position will prevent any errors, enabling us to proceed with deploying the code to the Remix VM. On the other hand, if we delete the SPDX license identifier from the top of our code and recompile, we will receive a yellow box showing a ⚠️ **warning**. @@ -21,7 +21,7 @@ On the other hand, if we delete the SPDX license identifier from the top of our > Warning: SPDX license identifier not provided in source file ``` - +::image{src='/solidity/remix/lesson-2/errors-warnings/warning.png' style='width: 100%; height: auto;'} Unlike errors, **warnings** allow the code to be compiled and deployed but it's wise to take them seriously and aim to remove them entirely. They point out poor or risky practices in your code and sometimes indicate potential bugs. @@ -43,7 +43,7 @@ Let's now attempt to resolve the semicolon error we intentionally created before We can input the compiler error under the drop-down menu, execute the search, and get a comprehensive explanation of why the error happened and how to fix it. - +::image{src='/solidity/remix/lesson-2/errors-warnings/phind-answer.png' style='width: 100%; height: auto;'} #### Othe resources @@ -51,7 +51,7 @@ It is advised to make active use of AI tools, as they can substantially boost yo You can also take part of online communities like **GitHub discussions** and **Stack Exchange**, where you'll find valuable insights, answers to your questions, and support from fellow developers. -> 💡 **TIP**
+> 💡 **TIP**:br > One of the most important aspects of being an excellent software engineer or prompt engineer is not just having the information but knowing where to find it. ### Conclusion diff --git a/courses/solidity/1-simple-storage/9-memory-storage-calldata/+page.md b/courses/solidity/1-simple-storage/9-memory-storage-calldata/+page.md index 924d2b58c..420a70780 100644 --- a/courses/solidity/1-simple-storage/9-memory-storage-calldata/+page.md +++ b/courses/solidity/1-simple-storage/9-memory-storage-calldata/+page.md @@ -23,7 +23,7 @@ Solidity can store data in **six** different locations. In this lesson, we will In Solidity, `calldata` and `memory` are temporary storage locations for variables during function execution. `calldata` is read-only, used for function inputs that can't be modified. In contrast, `memory` allows for read-write access, letting variables be changed within the function. To modify `calldata` variables, they must first be loaded into `memory`. -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > Most variable types default to `memory` automatically. However, for **strings**, you must specify either `memory` or `calldata` due to the way arrays are handled in memory. ```solidity @@ -43,7 +43,7 @@ function addPerson(string calldata _name, uitn256 _favoriteNumber) public { } ``` - +::image{src='/solidity/remix/lesson-2/memory/calldata.png' style='width: 100%; height: auto;'} ### Storage @@ -65,11 +65,11 @@ If you try to specify the `memory` keyword for an `uint256` variable, you'll enc > Data location can only be specified for array, struct, or mapping type ``` - +::image{src='/solidity/remix/lesson-2/memory/memory-err.png' style='width: 100%; height: auto;'} In Solidity, a `string` is recognized as an **array of bytes**. On the other hand, primitive types, like `uint256` have built-in mechanisms that dictate how and where they are stored, accessed and manipulated. -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > You can't use the `storage` keyword for variables inside a function. Only `memory` and `calldata` are allowed here, as the variable only exists temporarily. ```solidity diff --git a/courses/solidity/2-storage-factory/1-factory-introduction/+page.md b/courses/solidity/2-storage-factory/1-factory-introduction/+page.md index c3e108bd7..8657e4fac 100644 --- a/courses/solidity/2-storage-factory/1-factory-introduction/+page.md +++ b/courses/solidity/2-storage-factory/1-factory-introduction/+page.md @@ -30,7 +30,7 @@ It's possible to interact with this newly deployed `SimpleStorage` via the `stor The **`sfGet`** function, when given the input '0', will indeed return the number provided by the previous function. The **address** of the `SimpleStorage` contract can then be retrieved by clicking on the get function `listOfSimpleStorageContracts`. - +::image{src='/solidity/remix/lesson-3/setting-up/graph-1.png' style='width: 100%; height: auto;'} ### Conclusion The `StorageFactory` contract manages numerous instances of an external contract `SimpleStorage`. It provides functionality to deploy new contract instances dynamically and allows for the storage and retrieval of values from each instance. These instances are maintained and organized within an array, enabling efficient tracking and interaction. diff --git a/courses/solidity/2-storage-factory/2-setting-up-the-factory/+page.md b/courses/solidity/2-storage-factory/2-setting-up-the-factory/+page.md index f8240306c..99b2c0408 100644 --- a/courses/solidity/2-storage-factory/2-setting-up-the-factory/+page.md +++ b/courses/solidity/2-storage-factory/2-setting-up-the-factory/+page.md @@ -14,7 +14,7 @@ You can begin by visiting the [Github repository of the previous section](https: This contract allows to store a favorite number, a list of people with their favorite number, a mapping and different functionalities to interact with them. This lesson aims to create a **new contract** that can deploy and interact with `SimpleStorage`. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > One of the fundamental aspects of blockchain development is the seamless and permissionless interaction between contracts, known as **composability**. This is particularly crucial in decentralized finance (DeFi), where complex financial products interact effortlessly through common smart contract interfaces. Let's set up the backbone of the code, that contains the function `createSimplestorageContract`. This function will deploy a `SimpleStorage` contract and save the result into a _storage variable_: @@ -33,10 +33,10 @@ contract StorageFactory { We need to establish a connection between the two contracts, since `StorageFactory` needs to have a complete knowledge of `SimpleStorage`. One first approach could be copying the `SimpleStorage` contract above `StorageFactory`. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > It's allowed to have multiple contracts in the same file. As best practice, however, it's recommended to use only one file for each contract -> 💡 **TIP**
+> 💡 **TIP**:br > You can avoid confusion by keeping open **only** the file(s) you're currently working on. ### Conclusion diff --git a/courses/solidity/2-storage-factory/3-deploying-a-contract-from-a-contract/+page.md b/courses/solidity/2-storage-factory/3-deploying-a-contract-from-a-contract/+page.md index f24279996..f8df48aff 100644 --- a/courses/solidity/2-storage-factory/3-deploying-a-contract-from-a-contract/+page.md +++ b/courses/solidity/2-storage-factory/3-deploying-a-contract-from-a-contract/+page.md @@ -25,7 +25,7 @@ contract StorageFactory { } ``` -> 👀❗**IMPORTANT**
> `SimpleStorage` on the left and `simpleStorage` on the right are treated as entirely distinct entities due to their differing capitalization. `Simple Storage` refers to the contract type while `simpleStorage` refers to the variable name. +> 👀❗**IMPORTANT**:br > `SimpleStorage` on the left and `simpleStorage` on the right are treated as entirely distinct entities due to their differing capitalization. `Simple Storage` refers to the contract type while `simpleStorage` refers to the variable name. When the `new` keyword is used, the compiler recognizes the intention to deploy a new contract instance. After compiling, we can proceed to deploy it. diff --git a/courses/solidity/2-storage-factory/4-solidity-imports/+page.md b/courses/solidity/2-storage-factory/4-solidity-imports/+page.md index 613b05a98..b0d54b148 100644 --- a/courses/solidity/2-storage-factory/4-solidity-imports/+page.md +++ b/courses/solidity/2-storage-factory/4-solidity-imports/+page.md @@ -21,7 +21,7 @@ You can now remove the previously added `SimpleStorage` code and replace it with import "./SimpleStorage.sol"; ``` -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > All the solidity contracts should be compiled together using the _same compiler version_. It's important to ensure **consistency** between compiler versions across files since each one will have its own `pragma` statement. ### Named Imports @@ -40,7 +40,7 @@ You can also use named imports to import multiple contracts: import { SimpleStorage, SimpleStorage1 } from "./SimpleStorage.sol"; ``` -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Try to always default to named imports instead of importing the entire file. ### Conclusion diff --git a/courses/solidity/2-storage-factory/5-ai-help-ii/+page.md b/courses/solidity/2-storage-factory/5-ai-help-ii/+page.md index e613804e1..7ea8dfa4e 100644 --- a/courses/solidity/2-storage-factory/5-ai-help-ii/+page.md +++ b/courses/solidity/2-storage-factory/5-ai-help-ii/+page.md @@ -36,7 +36,7 @@ Here is my full code: The AI can provide insightful and very comprehensive answers. For instance, an AI may indicate that `simpleStorage` is a variable of type `SimpleStorage`, which is a contract defined in the file `SimpleStorage.sol`. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > AI systems are highly efficient at solving basic coding tasks. However, as the complexity of codebases and projects increases, the effectiveness of AI begins to diminish. Advanced tasks often require deep contextual understanding, innovative problem-solving, and area integrations where current AI capabilities fall short. ### Other resources diff --git a/courses/solidity/2-storage-factory/6-interacting-with-contracts-abi/+page.md b/courses/solidity/2-storage-factory/6-interacting-with-contracts-abi/+page.md index 8cb45a988..f00720317 100644 --- a/courses/solidity/2-storage-factory/6-interacting-with-contracts-abi/+page.md +++ b/courses/solidity/2-storage-factory/6-interacting-with-contracts-abi/+page.md @@ -39,20 +39,20 @@ function sfStore(uint256 _simpleStorageIndex, uint256 _simpleStorageNumber) publ } ``` -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Every time you have to interact with another contract, you need: > > 1. the contract **address** > 2. the contract **ABI (Application Binary Interface)**: a standardized way for interacting with the binary version of a smart contract deployed on the blockchain. It specifies the functions, their parameters, and the structure of the data that can be used to interact with the contract. It's generated by the compiler. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > If you do not have the full ABI available, a function selector will suffice (see later in the course). If you go to Solidity's compile tab, you will find a button that lets you copy the ABI to the clipboard. - +::image{src='/solidity/remix/lesson-3/interacting/interacting-contract1.png' style='width: 100%; height: auto;'} -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > In Solidity, it's possible to **type cast** an _address_ to a type _contract_ We can now proceed to store a new number on a `SimpleStorage` contract: diff --git a/courses/solidity/2-storage-factory/7-inheritance-in-solidity/+page.md b/courses/solidity/2-storage-factory/7-inheritance-in-solidity/+page.md index 223da3193..42bdca209 100644 --- a/courses/solidity/2-storage-factory/7-inheritance-in-solidity/+page.md +++ b/courses/solidity/2-storage-factory/7-inheritance-in-solidity/+page.md @@ -45,7 +45,7 @@ function store(uint256 _newFavNumber) public {} TypeError: Overriding function is missing "override" specifier. ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > To override a method from the parent contract, we must replicate the exact function **signature**, including its name, parameters and adding the visibility and the `override` keyword to it: ```solidity diff --git a/courses/solidity/2-storage-factory/8-summary-and-recap/+page.md b/courses/solidity/2-storage-factory/8-summary-and-recap/+page.md index 989b2a7c9..4315c9b37 100644 --- a/courses/solidity/2-storage-factory/8-summary-and-recap/+page.md +++ b/courses/solidity/2-storage-factory/8-summary-and-recap/+page.md @@ -49,7 +49,7 @@ function store(uint256 _num) public virtual { ### Conclusion In this section, we explored deploying multiple contract instances using the `new` keyword and enhancing code reusability through contract _imports_. We also covered interacting with other contracts using their address and ABI. Additionally, we learned about inheritance and function overriding, allowing derived contracts to customize inherited functionalities. -💡 **TIP**
+💡 **TIP**:br When you finish a section, take a moment to acknowledge your progress, celebrate it and share your achievements with your community. ### 🧑‍💻 Test yourself diff --git a/courses/solidity/3-fund-me/1-fund-me-intro/+page.md b/courses/solidity/3-fund-me/1-fund-me-intro/+page.md index 02db397bf..6e78e813b 100644 --- a/courses/solidity/3-fund-me/1-fund-me-intro/+page.md +++ b/courses/solidity/3-fund-me/1-fund-me-intro/+page.md @@ -12,7 +12,7 @@ In this section, we'll create a _decentralized crowdfunding_ contract. The compl For this project, we will be using two contracts: `FundMe`, the main crowdfunding contract, and `PriceConverter`. They function much like _Kickstarter_, allowing users to **send** any native blockchain cryptocurrency. They also enable the owner of the contract to **withdraw** all the funds collected. We will then deploy these contracts on a **testnet**. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Use testnet sparingly. Limiting testnet transactions helps prevent network congestion, ensuring a smoother testing experience for everyone. ### fund and withdraw diff --git a/courses/solidity/3-fund-me/11-more-solidity-math/+page.md b/courses/solidity/3-fund-me/11-more-solidity-math/+page.md index 85b8b9e3a..0d9f16fc9 100644 --- a/courses/solidity/3-fund-me/11-more-solidity-math/+page.md +++ b/courses/solidity/3-fund-me/11-more-solidity-math/+page.md @@ -25,10 +25,10 @@ function getConversionRate(uint256 ethAmount) internal view returns (uint256) { } ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > The line `uint256 ethAmountInUsd = (ethPrice * ethAmount)` results in a value with a precision of 1e18 \* 1e18 = 1e36. To bring the precision of `ethAmountInUsd` back to 1e18, we need to divide the result by 1e18. -> 🔥 **CAUTION**
+> 🔥 **CAUTION**:br > Always multiply before dividing to maintain precision and avoid truncation errors. For instance, in floating-point arithmetic, `(5/3) * 2` equals approximately 3.33. In Solidity, `(5/3)` equals 1, which when multiplied by 2 yields 2. If you multiply first `(5*2)` and then divide by 3, you achieve better precision. ### Example of `getConversionRate` diff --git a/courses/solidity/3-fund-me/14-libraries/+page.md b/courses/solidity/3-fund-me/14-libraries/+page.md index 963938d88..b92ebdd70 100644 --- a/courses/solidity/3-fund-me/14-libraries/+page.md +++ b/courses/solidity/3-fund-me/14-libraries/+page.md @@ -13,7 +13,7 @@ In the previous lesson, we used the `getPrice()` function and `getConversionRate Great examples of Libraries can be found in the [Solidity by example](https://solidity-by-example.org/library/) website. Solidity libraries are similar to contracts but do not allow the declaration of any **state variables** and **cannot receive ETH**. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > All functions in a library must be declared as `internal` and are embedded in the contract during compilation. If any function is not marked as such, the library cannot be embedded directly, but it must be deployed independently and then linked to the main contract. We can start by creating a new file called `PriceConverter.sol`, and replace the `contract` keyword with `library`. diff --git a/courses/solidity/3-fund-me/15-safemath/+page.md b/courses/solidity/3-fund-me/15-safemath/+page.md index fe07988b3..34027141b 100644 --- a/courses/solidity/3-fund-me/15-safemath/+page.md +++ b/courses/solidity/3-fund-me/15-safemath/+page.md @@ -59,7 +59,7 @@ function add() public { } ``` -> 🔥 **CAUTION**
+> 🔥 **CAUTION**:br > It's important to use unchecked blocks with caution as they reintroduce the possibility of overflows and underflows. ### Conclusion diff --git a/courses/solidity/3-fund-me/16-for-loop/+page.md b/courses/solidity/3-fund-me/16-for-loop/+page.md index ea317c75e..9cbbe8463 100644 --- a/courses/solidity/3-fund-me/16-for-loop/+page.md +++ b/courses/solidity/3-fund-me/16-for-loop/+page.md @@ -56,7 +56,7 @@ The loop begins at index 0 and goes through all the elements in the `funders` ar 1. Accesses the `funder` address at the current index 2. Resets the corresponding funding amount in the `addressToAmountFunded` mapping to zero, clearing the `funder`'s record. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > The **addressToAmountFunded** map connects addresses with the respective amounts they funded. ### Shortcuts diff --git a/courses/solidity/3-fund-me/17-resetting-an-array/+page.md b/courses/solidity/3-fund-me/17-resetting-an-array/+page.md index 847b227d9..530aa35ee 100644 --- a/courses/solidity/3-fund-me/17-resetting-an-array/+page.md +++ b/courses/solidity/3-fund-me/17-resetting-an-array/+page.md @@ -16,7 +16,7 @@ The simplest way to reset the `funders` array is similar to the method used with funders = new address[](); ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > You might recall using the `new` keyword when deploying a contract. In this context, however, it resets the `funders` array to a zero-sized, blank address array. ### Conclusion diff --git a/courses/solidity/3-fund-me/18-sending-eth-from-a-contract/+page.md b/courses/solidity/3-fund-me/18-sending-eth-from-a-contract/+page.md index 199a67694..8f83a0262 100644 --- a/courses/solidity/3-fund-me/18-sending-eth-from-a-contract/+page.md +++ b/courses/solidity/3-fund-me/18-sending-eth-from-a-contract/+page.md @@ -44,7 +44,7 @@ To send funds using the `call` function, we convert the address of the receiver The `call` function returns two variables: a boolean for success or failure, and a byte object which stores returned data if any. -> 👀❗**IMPORTANT**
> `call` is the recommended way of sending and receiving Ethereum or other blockchain native tokens. +> 👀❗**IMPORTANT**:br > `call` is the recommended way of sending and receiving Ethereum or other blockchain native tokens. ### Conclusion diff --git a/courses/solidity/3-fund-me/19-constructor/+page.md b/courses/solidity/3-fund-me/19-constructor/+page.md index c160d05c9..c77c2065f 100644 --- a/courses/solidity/3-fund-me/19-constructor/+page.md +++ b/courses/solidity/3-fund-me/19-constructor/+page.md @@ -20,7 +20,7 @@ A more efficient solution is to use a **constructor** function: constructor() {} ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > The constructor does not use the `function` and `public` keywords. ### Assigning the Owner in the Constructor diff --git a/courses/solidity/3-fund-me/2-setup/+page.md b/courses/solidity/3-fund-me/2-setup/+page.md index 411041b09..0d2ebed1e 100644 --- a/courses/solidity/3-fund-me/2-setup/+page.md +++ b/courses/solidity/3-fund-me/2-setup/+page.md @@ -12,7 +12,7 @@ Let's begin by coding `FundMe`, a crowdfunding contract allowing users to send f Start from scratch by opening your [Remix IDE](https://remix.ethereum.org/) and deleting all existing contracts. Next, create a new contract named `FundMe`. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > Before you start coding, try to write down in plain English what you want your code to achieve. This helps clarify your goals and structure your approach. We want `FundMe` to perform the following tasks: diff --git a/courses/solidity/3-fund-me/20-modifiers/+page.md b/courses/solidity/3-fund-me/20-modifiers/+page.md index 4c65dc534..df64acd0d 100644 --- a/courses/solidity/3-fund-me/20-modifiers/+page.md +++ b/courses/solidity/3-fund-me/20-modifiers/+page.md @@ -31,7 +31,7 @@ modifier onlyOwner { } ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > The modifier is named `onlyOwner` to reflect the condition it checks. ### The `_` (underscore) diff --git a/courses/solidity/3-fund-me/21-testnet-demo/+page.md b/courses/solidity/3-fund-me/21-testnet-demo/+page.md index 38074425b..fa3bf4296 100644 --- a/courses/solidity/3-fund-me/21-testnet-demo/+page.md +++ b/courses/solidity/3-fund-me/21-testnet-demo/+page.md @@ -12,7 +12,7 @@ In this lesson, we'll delve into _end-to-end testing_ of a Solidity contract's d First, we need to _compile_ the contract to ensure the code is correct. On Remix, set the **injected provider** to MetaMask and confirm it is properly synced to the testnet. Ensure you have some Sepolia Ether (ETH) in your wallet if you plan to deploy the contract on Sepolia. - +::image{src='/solidity/remix/lesson-4/testnet/testnet1.png' style='width: 100%; height: auto;'} We'll deploy the `FundMe` contract by clicking deploy and then confirming the transaction in MetaMask, which may take some time. @@ -26,7 +26,7 @@ After successfully deploying the `FundMe` contract, you'll see several buttons t The `fund` function allows us to send ETH to the contract (minimum 5 USD). The `owner` of the contract is our MetaMask account, as the **constructor** sets the deployer as the owner. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > If the `fund` function is called without any value or with less than 5 USD, you will encounter a gas estimation error, indicating insufficient ETH, and gas will be wasted. ### Successful Transaction diff --git a/courses/solidity/3-fund-me/22-immutability-and-constants/+page.md b/courses/solidity/3-fund-me/22-immutability-and-constants/+page.md index c0565b43f..027f9cef3 100644 --- a/courses/solidity/3-fund-me/22-immutability-and-constants/+page.md +++ b/courses/solidity/3-fund-me/22-immutability-and-constants/+page.md @@ -24,10 +24,10 @@ We can apply these keywords to variables assigned once and never change. For val Using the `constant` keyword can save approximately 19,000 gas, which is close to the cost of sending ETH between two accounts. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Naming conventions for `constant` are all caps with underscores in place of spaces (e.g., `MINIMUM_USD`). -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > Converting the current ETH gas cost to USD, we see that when ETH is priced at 3000 USD, defining `MINIMUM_USD` as a constant costs 9 USD, nearly 1 USD more than its public equivalent. ### Immutable @@ -36,7 +36,7 @@ While `constant` variables are for values known at compile time, `immutable` can Comparing gas usage after making `owner` an `immutable` variable, we observe similar gas savings to the `constant` keyword. -> 💡 **TIP**
+> 💡 **TIP**:br > Don't focus too much on gas optimization at this early stage of learning. ### Conclusion diff --git a/courses/solidity/3-fund-me/3-sending-eth-through-a-function/+page.md b/courses/solidity/3-fund-me/3-sending-eth-through-a-function/+page.md index 62c26d385..ad12f079e 100644 --- a/courses/solidity/3-fund-me/3-sending-eth-through-a-function/+page.md +++ b/courses/solidity/3-fund-me/3-sending-eth-through-a-function/+page.md @@ -46,10 +46,10 @@ require(msg.value > 1 ether, "Didn't send enough ETH"); //if the condition is fa An online tool like [Ethconverter](https://eth-converter.com/) can be useful for executing conversions between _Ether_, _Wei_, and _Gwei_. -> 👀❗**IMPORTANT**
+> 👀❗**IMPORTANT**:br > 1 Ether = 1e9 Gwei = 1e18 Wei -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Gas costs are usually expressed in Gwei If a user attempts to send less Ether than the required amount, the transaction will **fail** and a _message_ will be displayed. For example, if a user attempts to send 1000 Wei, which is significantly less than one Ether, the function will revert and does not proceed. diff --git a/courses/solidity/3-fund-me/4-solidity-reverts/+page.md b/courses/solidity/3-fund-me/4-solidity-reverts/+page.md index dfdb83fc4..2d8340fbd 100644 --- a/courses/solidity/3-fund-me/4-solidity-reverts/+page.md +++ b/courses/solidity/3-fund-me/4-solidity-reverts/+page.md @@ -33,12 +33,12 @@ A _revert_ action **undoes** all prior operations and returns the remaining gas ### Gas Usage -> 🔥 **CAUTION**
+> 🔥 **CAUTION**:br > The gas used in the transaction will not be refunded if the transaction fails due to a revert statement. The gas has already been **consumed** because the code was executed by the computers, even though the transaction was ultimately reverted. Users can specify how much gas they're willing to allocate for a transaction. In the case where the `fund` function will contain a lot of lines of code after the `require` and we did indeed set a limit, the gas which was previously allocated but not used will not be charged to the user -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > If a transaction reverts, is defined as failed ### Transaction Fields diff --git a/courses/solidity/3-fund-me/5-real-world-price-data/+page.md b/courses/solidity/3-fund-me/5-real-world-price-data/+page.md index 6f1ff67a1..120b778db 100644 --- a/courses/solidity/3-fund-me/5-real-world-price-data/+page.md +++ b/courses/solidity/3-fund-me/5-real-world-price-data/+page.md @@ -39,16 +39,16 @@ Chainlink offers ready-made features that can be added to a smart contract. And _Chainlink Data Feeds_ are responsible for powering over $50 billion in the DeFi world. This network of Chainlink nodes aggregates data from various **exchanges** and **data providers**, with each node independently verifying the asset price. - +::image{src='/solidity/remix/lesson-4/datafeeds/datafeed2.png' style='width: 100%; height: auto;'} They aggregate this data and deliver it to a reference contract, the **price feed contract**, in a single transaction. Each contract will store the pricing details of a specific cryptocurrency - +::image{src='/solidity/remix/lesson-4/datafeeds/datafeed1.png' style='width: 100%; height: auto;'} ### Chainlink CRF The Chainlink VRF (Verifiable Random Function) provides a solution for generating **provably random numbers**, ensuring true fairness in applications such as NFT randomization, lotteries, and gaming. These numbers are determined off-chain, and they are immune to manipulation. - +::image{src='/solidity/remix/lesson-4/datafeeds/datafeed3.png' style='width: 100%; height: auto;'} ### Chainlink Keepers diff --git a/courses/solidity/3-fund-me/6-mid-lesson-recap/+page.md b/courses/solidity/3-fund-me/6-mid-lesson-recap/+page.md index 91fcd2d5c..2c01f79ac 100644 --- a/courses/solidity/3-fund-me/6-mid-lesson-recap/+page.md +++ b/courses/solidity/3-fund-me/6-mid-lesson-recap/+page.md @@ -36,7 +36,7 @@ The Solidity global property msg.value contains the amount of cryptocurrency sen Chainlink is a revolutionary technology that enables the integration of external data and computation into smart contracts. It provides a decentralized way of **injecting data**, which is particularly useful for assets whose values fluctuate over time. For instance, if a smart contract deals with real-world assets such as stocks or commodities, obtaining real-time pricing information is crucial. -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Chainlink enables smart contracts to interact with real-world data and services without sacrificing the security and reliability guarantees inherent to blockchain technology. Consider a smart contract that deals with a commodity like gold. _Chainlink Price Feeds_ can provide real-time gold prices, allowing the smart contract to reflect the current market prices. diff --git a/courses/solidity/3-fund-me/7-interfaces/+page.md b/courses/solidity/3-fund-me/7-interfaces/+page.md index 8c3cb0d34..775ef2074 100644 --- a/courses/solidity/3-fund-me/7-interfaces/+page.md +++ b/courses/solidity/3-fund-me/7-interfaces/+page.md @@ -34,7 +34,7 @@ To obtain the ABI, you can import, compile, and deploy the PriceFeed contract it An alternative method involves the use of an **Interface**, which defines methods signature without their implementation logic. If compiled, the Price Feed Interface, it would return the ABI of the Price Feed contract itself, which was previously deployed on the blockchain. We don't need to know anything about the function implementations, only knowing the `AggregatorV3Interface` methods will suffice. The Price Feed interface, called `Aggregator V3 Interface`, can be found in [Chainlink's GitHub repository](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol). -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > Interfaces allow different contracts to interact seamlessly by ensuring they share a common set of functionalities. We can test the Interface usage by calling the `version()` function: @@ -45,7 +45,7 @@ We can test the Interface usage by calling the `version()` function: } ``` -> 🗒️ **NOTE**
+> 🗒️ **NOTE**:br > It's best to work on testnets only after your deployment is complete, as it can be time and resource consuming. ### Conclusion diff --git a/courses/solidity/3-fund-me/8-ai-help-iii/+page.md b/courses/solidity/3-fund-me/8-ai-help-iii/+page.md index ffdcb4040..1da6c8f9f 100644 --- a/courses/solidity/3-fund-me/8-ai-help-iii/+page.md +++ b/courses/solidity/3-fund-me/8-ai-help-iii/+page.md @@ -50,7 +50,7 @@ As explained by the AI: If the implementation contract at the given address does not have a `version()` function or if it has a different function signature, calling `version()` on that contract will result in a compilation error or a runtime error. ``` -> 🚧 **WARNING**
+> 🚧 **WARNING**:br > Always verify AI-provided information by consulting a discussion forum ### Conclusion diff --git a/courses/solidity/4-ai-prompting/1-ai-and-forums/+page.md b/courses/solidity/4-ai-prompting/1-ai-and-forums/+page.md index f68fd2a33..b6765d3dd 100644 --- a/courses/solidity/4-ai-prompting/1-ai-and-forums/+page.md +++ b/courses/solidity/4-ai-prompting/1-ai-and-forums/+page.md @@ -22,7 +22,7 @@ Lets go through them. Pinpoint your error, review your code manually making small adjustments you suspect may resolve the issue. Pinpointing the error in your code will help you frame your question/prompt in the next step. - +::image{src='/solidity/ai-prompting/debug1.png' style='width: 100%; height: auto;'} ### Ask Your AI diff --git a/courses/solidity/4-ai-prompting/3-setting-up-github/+page.md b/courses/solidity/4-ai-prompting/3-setting-up-github/+page.md index 084a5818f..5693a6b21 100644 --- a/courses/solidity/4-ai-prompting/3-setting-up-github/+page.md +++ b/courses/solidity/4-ai-prompting/3-setting-up-github/+page.md @@ -10,7 +10,7 @@ Here I'm going to walk you through the creation of a GitHub account. Asking well-formatted, articulate questions greatly enhances your chances of receiving prompt and effective answers. Many times, these communities are comprised of people who answer queries simply out of goodwill and a shared passion for the knowledge involved. Therefore, make sure your questions are well-crafted to do justice to their time and effort! - +::image{src='/solidity/ai-prompting/github1.png' style='width: 100%; height: auto;'} A key platform to engage with these communities is GitHub. If you haven't already, now's the perfect time to activate an account. Don't skip ahead, this is imperative. Let's get started. @@ -18,11 +18,11 @@ A key platform to engage with these communities is GitHub. If you haven't alread GitHub is the go-to platform for developers. It offers a manageable approach to maintaining code repositories and facilitates collaborative coding and issue resolution. Setting up an account on GitHub is pretty straightforward. If you haven't already done this, you will need an email to get started. - +::image{src='/solidity/ai-prompting/github3.png' style='width: 100%; height: auto;'} To sign up for GitHub, just click on "Sign up" and enter your valid email address. - +::image{src='/solidity/ai-prompting/github4.png' style='width: 100%; height: auto;'} ## **Step 2: Account Creation** @@ -30,11 +30,11 @@ Click on "Create account". After registering your email on GitHub, you will rece When prompted, choose the free version. - +::image{src='/solidity/ai-prompting/github5.png' style='width: 100%; height: auto;'} And voila! You've created your GitHub profile. - +::image{src='/solidity/ai-prompting/github6.png' style='width: 100%; height: auto;'} ### **Moving Forward: Asking 'Great' Questions** diff --git a/courses/solidity/4-ai-prompting/4-formatting-a-question/+page.md b/courses/solidity/4-ai-prompting/4-formatting-a-question/+page.md index 607817bba..141568b58 100644 --- a/courses/solidity/4-ai-prompting/4-formatting-a-question/+page.md +++ b/courses/solidity/4-ai-prompting/4-formatting-a-question/+page.md @@ -12,7 +12,7 @@ As practice, I want you to navigate to the [**GitHub discussions page**](https:/ > Try to categorize your discussion appropriately. `General` for conversations and discussions, `QA` for questions. - +::image{src='/solidity/ai-prompting/question1.png' style='width: 100%; height: auto;'} ## The Art of Asking Questions diff --git a/courses/solidity/4-ai-prompting/5-speedrun/+page.md b/courses/solidity/4-ai-prompting/5-speedrun/+page.md index 7ce0729cf..79bee9109 100644 --- a/courses/solidity/4-ai-prompting/5-speedrun/+page.md +++ b/courses/solidity/4-ai-prompting/5-speedrun/+page.md @@ -8,7 +8,7 @@ _Follow along the course with this video._ In this section we're examining a resource that isn't explicitly part of this course but is highly useful in expanding your knowledge about Ethereum and the Ethereum Virtual Machine (EVM). This resource comes courtesy of my good friend Austin Griffin. Let's go over what it can do for you. - +::image{src='/solidity/speedrun/speedrun1.png' style='width: 100%; height: auto;'} ### Introduction to Speedrun Ethereum w/ Austin Griffin @@ -26,7 +26,7 @@ Through Speedrun Ethereum, you'll delve into a plethora of projects, including: ...and much more - +::image{src='/solidity/speedrun/speedrun2.png' style='width: 100%; height: auto;'} To take advantage of these learning opportunities, visit [Speedrunethereum.com](https://speedrunethereum.com/) and get started! @@ -36,7 +36,7 @@ Scaffold-eth-2 is a great resource for those learning Solidity and trying to vis It provides a clean front-end UI that will update dynamically with your smart contract changes, allowing you to interact with it and monitor adjustments you've made. - +::image{src='/solidity/speedrun/speedrun3.png' style='width: 100%; height: auto;'} ### Final Remarks diff --git a/courses/wallets/2-section-1/8-setting-up-a-safe/+page.md b/courses/wallets/2-section-1/8-setting-up-a-safe/+page.md index 669edebf7..c110ad9bf 100644 --- a/courses/wallets/2-section-1/8-setting-up-a-safe/+page.md +++ b/courses/wallets/2-section-1/8-setting-up-a-safe/+page.md @@ -14,72 +14,72 @@ For developers, if you have an ownership role or permissions in your smart contr 1. **Visit the Safe Official Website**: Follow this [link](https://app.safe.global/welcome) to visit the Safe official website. You should see a page like this: - + ::image{src='../../../../static/wallets/8-setting-up-safe/landingPage.png' style='width: 75%; height: auto;'} 2. **Select the Blockchain Network**: Choose the chain on which you want to deploy the Safe smart contract wallet. For this example, we'll use the Ethereum Sepolia test net. - + ::image{src='../../../../static/wallets/8-setting-up-safe/walletConnectModal.png' style='width: 75%; height: auto;'} 3. **Connect Your Wallet**: Select the wallet type you want to use to connect to the Safe website. - + ::image{src='../../../../static/wallets/8-setting-up-safe/createAccount.png' style='width: 75%; height: auto;'} 4. **Enter Safe Name**: After connecting your wallet, enter the name of your Safe. - + ::image{src='../../../../static/wallets/8-setting-up-safe/addSigners.png' style='width: 75%; height: auto;'} 5. **Configure Signers**: Ideally, set up a 2-of-3 Safe smart contract wallet. For simplicity, we'll set up a 1-of-1 Safe smart contract wallet. Configure the required threshold for transaction approval. 6. **Estimate Gas Fees**: The next page will estimate the gas fee for creating your Safe smart contract wallet. You can either pay it yourself or use Safe's sponsorship for testnets. - + ::image{src='../../../../static/wallets/8-setting-up-safe/gasEstimation.png' style='width: 75%; height: auto;'} 7. **Deploy Safe Wallet**: Once the transaction is sent, you will see a page showing the address where your Safe smart contract is deployed. - + ::image{src='../../../../static/wallets/8-setting-up-safe/transactionState.png' style='width: 75%; height: auto;'} 8. **Start Using Safe**: Click on the `Start using Safe {Wallet}` button to access the user interface of your Safe smart contract wallet. - + ::image{src='../../../../static/wallets/8-setting-up-safe/UserUi.jpeg' style='width: 75%; height: auto;'} ### Viewing and Verifying Your Safe Wallet To view your wallet on Etherscan, click on the icon circled in the image below: - + ::image{src='../../../../static/wallets/8-setting-up-safe/viewWalletIcon.png' style='width: 75%; height: auto;'} This will take you to the Etherscan page of your deployed Safe smart contract wallet: - + ::image{src='../../../../static/wallets/8-setting-up-safe/smartContractEtherscanPage.png' style='width: 75%; height: auto;'} Click on the `contract` tab to view the Gnosis Safe Proxy contract: - + ::image{src='../../../../static/wallets/8-setting-up-safe/GnosisSafeProxy.png' style='width: 75%; height: auto;'} To verify the smart contract code, compare it with the source code in the Gnosis Safe GitHub repository. Click on the link circled below to get the deployed bytecode of the contract: - + ::image{src='../../../../static/wallets/8-setting-up-safe/sourceCodeContract.png' style='width: 75%; height: auto;'} ### Using the Safe Wallet User Interface The user interface offers various features. You can connect to applications listed on the UI or use the `wallet-connect` feature for apps not listed. - + ::image{src='../../../../static/wallets/8-setting-up-safe/apps.png' style='width: 75%; height: auto;'} To connect to an app like Uniswap via `wallet-connect`, follow these steps: 1. Visit the Uniswap website and select `wallet-connect` instead of MetaMask. 2. Copy the connection link from the modal: - + ::image{src='../../../../static/wallets/8-setting-up-safe/walletConnectModal.png' style='width: 75%; height: auto;'} 3. Paste the link into the Safe wallet UI: - + ::image{src='../../../../static/wallets/8-setting-up-safe/safeWalletConnect.png' style='width: 75%; height: auto;'} 4. Approve the connection: - + ::image{src='../../../../static/wallets/8-setting-up-safe/safeWalletConnectModal.png' style='width: 75%; height: auto;'} Once connected, your Safe wallet will be linked to Uniswap or any other app you choose.