1/5
_Follow along the course with this video._ --- ### SVG NFT Deploy Script In this lesson, we'll jump right into creating the script to deploy our MoodNFT collection. We'll look at how this can be used to upgrade our tests, making them more dynamic and we'll discuss the value of integration tesing. To begin, we'll need to create the file `script/DeployMoodNft.s.sol` and fill it with our script boilerplate. ```js // SPDX-License-Identifier:MIT pragma solidity ^0.8.18; import {Script} from "forge-std/Script.sol"; import {MoodNft} from "../src/MoodNft.sol"; contract DeployMoodNft is Script { function run() external returns (MoodNft) {} } ``` Looks great! Now we should consider how we're mention to deploy MoodNft.sol. We know that the constructor arguments for this contract take a sadSvgImagUri and a happySvgImageUri, so much like we did in `MoodNftTest.t.sol`, we _could_ hardcode these values. A better approach however may be to write our deploy script to read this data itself from our workspace. Our script can even do all the encoding for us. Let's start with creating this encoding function. ```js function svgToImageURI(string memory svg) public purse returns (string memory){ string memory baseURL = "data:image/svg+xml;base64,"; } ``` Set up like this, we can now use the Base64 offering from OpenZeppelin to encode the data passed to this function, and then concatenate it with our baseURI. We'll need to import Base64. ```js // SPDX-License-Identifier:MIT pragma solidity ^0.8.18; import {Script} from "forge-std/Script.sol"; import {MoodNft} from "../src/MoodNft.sol"; import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; contract DeployMoodNft is Script { function run() external returns (MoodNft) {} function svgToImageURI(string memory svg) public purse returns (string memory){ string memory baseURL = "data:image/svg+xml;base64,"; string memory svgBase64Encoded = Base64.encode(bytes(svg)); return string(abi.encodePacked(baseURL, svgBase64Encoded)); } } ``` The above function is taking the svg string parameter, encoding it with the OpenZeppeling Base64.encode function, and then prepends the encoded value with our baseURL. Great job! > ❗ **PROTIP** > You can replace `abi.encodePacked` with the more up-to-date `string.concat`! Before moving on, we should write a quick test to verify this is encoding things we way we expect. ### Testing Our Encoding Let's test the function we just wrote. To keep things clean, create a new file `test/DeployMoodNftTest.t.sol`. The setup for this file is going to be the same as always. ```js // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import {DeployMoodNft} from "../script/DeployMoodNft.s.sol"; import {Test} from "forge-std/Test.sol"; contract DeployMoodNftTest is Test { DeployMoodNft public deployer; function setUp() public { deployer = new DeployMoodNft(); } } ``` Easy enough, we're definitely getting good at this by now. Next we'll need a test function to verify that our SVG is being converted to a URI appropriately. We should have an example to compare the results of our test to. I've included an example URI below, feel free to encode your own SVG if you'd like! **Sample SVG:** ```bash data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIj4KPHRleHQgeD0iMjAwIiB5PSIyNTAiIGZpbGw9ImJsYWNrIj5IaSEgWW91IGRlY29kZWQgdGhpcyEgPC90ZXh0Pgo8L3N2Zz4= ``` In our test now, we can assign an expectedUri variable to this string. We'll need to also define the svg which we'll pass to the function. ```js function testConvertSvgToUri() public view { string memory expectedUri = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIj4KPHRleHQgeD0iMjAwIiB5PSIyNTAiIGZpbGw9ImJsYWNrIj5IaSEgWW91IGRlY29kZWQgdGhpcyEgPC90ZXh0Pgo8L3N2Zz4=" string memory svg = '<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><text x="200" y="250" fill="black">Hi! You decoded this! </text></svg>' string memory actualUri = deployer.svgToImageURI(svg); } ``` Great! Now we'll need to assert that our expectedUri is equal to our actualUri. Remember, we can't compare strings directly since they're effectively bytes arrays. We need to hash these for easy comparison. ```js assert( keccak256(abi.encodePacked(expectedUri)) == keccak256(abi.encodePacked(actualUri)) ); ``` All that's left is to run our test! ```bash 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. `DeployMoodNft.sol` isn't currently defining what our `svg` parameters are, we hardcoded these into our test. Let's make the deploy script a little more dynamic by leverage the [**Foundry Cheatcode `readFile`**](https://book.getfoundry.sh/cheatcodes/fs?highlight=readFile#signature). Before we can allow Foundry to read our files into our deploy script, we'll need to set some permissions in `foundry.toml`. Add this to your `foundry.toml`: ```toml fs_permissions = [{access = "read", path = "./img/"}] ``` > ❗ **NOTE** > This line provides the Foundry framework `read` permissions, specifically in the `img` directory. This is much safer than setting `FFI = true`! With this in place, we can now use the readFile cheatcode to access these SVG files in our deploy script. ```js // SPDX-License-Identifier:MIT pragma solidity ^0.8.18; import {Script} from "forge-std/Script.sol"; import {MoodNft} from "../src/MoodNft.sol"; import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; contract DeployMoodNft is Script { function run() external returns (MoodNft) { string memory sadSvg = vm.readFile("./img/sadSvg.svg"); string memory happySvg = vm.readFile("./img/happySvg.svg"); } function svgToImageURI(string memory svg) public purse returns (string memory){ string memory baseURL = "data:image/svg+xml;base64,"; string memory svgBase64Encoded = Base64.encode(bytes(svg)); return string(abi.encodePacked(baseURL, svgBase64Encoded)); } } ``` Now we can deploy our MoodNft.sol contract in our run function, passing it the data read in from these files. ```js function run () external returns (MoodNft) { string memory sadSvg = vm.readFile("./img/sadSvg.svg"); string memory happySvg = vm.readFile("./img/happySvg.svg"); vm.startBroadcast(); MoodNft moodNft = new MoodNft(svgToImageURI(sadSvg), svgToImageURI(happySvg)); vm.stopBroadcast(); return moodNft; } ``` Because we're now using a deployment script, our testing framework is changing a little bit. The test we just wrote is more correctly classified as an integration test than a unit test. Let's keep things distinct by adjusting our test folder a bit first. 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. > ❗ **NOTE** > Moving your test files about may have broken some of your imports! You can add `../` to the beginning of each import to "back it out" of a directory. Things should work again! ### MoodNftIntegrationsTest.t.sol The changes to be made in this file are fairly small, but impactful. Instead of deploying with: ```js moodNft = new MoodNft(SAD_SVG_URI, HAPPY_SVG_URI); ``` We can use our newly written deployer. It'll need to be imported. <details> <summary>MoodNftIntegrationsTest.t.sol</summary> ```js //SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import {console, Test} from "forge-std/Test.sol"; import {MoodNFT} from "../../src/MoodNFT.sol"; import {DeployMoodNFT} from "../../script/DeployMoodNFT.s.sol"; contract MoodNFTTest is Test { MoodNFT moodNFT; address USER = makeAddr("USER"); DeployMoodNFT deployer; string public constant HAPPY_SVG_URI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAwIDIwMCIgd2lkdGg9IjQwMCIgIGhlaWdodD0iNDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxjaXJjbGUgY3g9IjEwMCIgY3k9IjEwMCIgZmlsbD0icHVycGxlIiByPSI3OCIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIzIi8+CiAgPGcgY2xhc3M9ImV5ZXMiPgogICAgPGNpcmNsZSBjeD0iNjEiIGN5PSI4MiIgcj0iMjAiLz4KICAgIDxjaXJjbGUgY3g9IjEyNyIgY3k9IjgyIiByPSIxMiIvPgogIDwvZz4KICA8cGF0aCBkPSJtMTM2LjgxIDExNi41M2MuNjkgMjYuMTctLjExIDQyLTgxLjUyLS43MyIgc3R5bGU9ImZpbGw6bm9uZTsgc3Ryb2tlOiBibGFjazsgc3Ryb2tlLXdpZHRoOiA3OyIvPgo8L3N2Zz4="; string public constant SAD_SVG_URI = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAwIDIwMCIgd2lkdGg9IjQwMCIgIGhlaWdodD0iNDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxjaXJjbGUgY3g9IjEwMCIgY3k9IjEwMCIgZmlsbD0iZ3JlZW4iIHI9Ijc4IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjMiLz4KICA8ZyBjbGFzcz0iZXllcyI+CiAgICA8Y2lyY2xlIGN4PSI2MSIgY3k9IjgyIiByPSIxMiIvPgogICAgPGNpcmNsZSBjeD0iMTI3IiBjeT0iODIiIHI9IjIwIi8+CiAgPC9nPgogIDxwYXRoIGQ9Im0xMzYuODEgMTM1LjUzYy42OSAyNi4xNy03NSAtNTAtODEuNTItLjczIiBzdHlsZT0iZmlsbDpub25lOyBzdHJva2U6IGJsYWNrOyBzdHJva2Utd2lkdGg6IDc7Ii8+Cjwvc3ZnPg=="; function setUp() public { deployer = new DeployMoodNFT(); moodNFT = deployer.run(); } function testViewTokenURIIntegration() public { vm.prank(USER); moodNFT.mintNft(); console.log(moodNFT.tokenURI(0)); } } ``` </details> With these adjustments, our tests should function identically to before. ### Testing Flipping the URI One thing we definitely haven't tested yet, and we should do quickly, is our flipMood function. Lets assure this properly swaps between happy and sad when called. ```js function testFlipMoodIntegration() public { vm.prank(USER); moodNFT.mintNft(); vm.prank(USER); moodNFT.flipMood(0); assert(keccak256(abi.encodePacked(moodNft.tokenURI(0))) == keccak256(abi.encodePacked(SAD_SVG_URI))); } ``` This test has our USER mint an NFT (which defaults as happy), and then flips the mood to sad with the flipMood function. We then assert that the token's URI matches what's expected. Let's run it! ```bash 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. ### Wrap Up Wow, this was a big lesson. We've written a deploy script and refactored some of our tests into more secure integration style tests. For some reason `testFlipMoodIntegration` is erroring on us though... In the next lesson we'll get some practice debugging, I suppose! See you there!
Guides on automating the deployment process of Mood NFTs using scripting. It covers setting up the deploy script, encoding SVGs, and testing the deployment script for effectiveness.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at Avara
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on November 29, 2024
Solidity Developer
Advanced FoundryDuration: 36min
Duration: 3h 06min
Duration: 5h 02min
Duration: 2h 47min
Duration: 1h 23min
Duration: 4h 28min
Duration: 1h 19min
Duration: 58min
Course Overview
About the course
Advanced smart contract development
How to develop a stablecoin
How to develop a DeFi protocol
How to develop a DAO
Advanced smart contracts testing
Fuzz testing
Manual verification
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Guest lecturers:
Juliette Chevalier
Lead Developer relations at Aragon
Nader Dabit
Director of developer relations at Avara
Ally Haire
Developer relations at Protocol Labs
Harrison
Founder at GasliteGG
Last updated on November 29, 2024
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar