From 1e5c79474f067680917e5b8755365cffd1788323 Mon Sep 17 00:00:00 2001 From: Martijncvv <70589073+Martijncvv@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:01:18 -0600 Subject: [PATCH] - Basecamp docs improved; (#339) Put code solutions to questions in dropdowns to improve the learning experience. --- .../advanced-functions/function-modifiers.md | 24 ++++++++ .../docs/arrays/filtering-an-array-sbs.md | 40 +++++++++++++ .../docs/erc-20-token/erc-20-token-sbs.md | 9 +++ .../docs/erc-721-token/erc-721-sbs.md | 32 +++++++++++ .../docs/error-triage/error-triage.md | 45 +++++++++++++++ .../hardhat-testing/hardhat-testing-sbs.md | 42 +++++++++++++- .../base-camp/docs/imports/imports-sbs.md | 13 +++++ .../inheritance/abstract-contracts-sbs.md | 15 +++++ .../docs/inheritance/inheritance-sbs.md | 16 ++++++ .../docs/inheritance/multiple-inheritance.md | 56 ++++++++++++++++++ .../base-camp/docs/mappings/mappings-sbs.md | 55 ++++++++++++++++++ .../docs/minimal-tokens/minimal-token-sbs.md | 32 +++++++++++ .../docs/new-keyword/new-keyword-sbs.md | 16 ++++++ .../reading-and-displaying-data/useAccount.md | 9 +++ .../docs/storage/simple-storage-sbs.md | 57 +++++++++++++++++++ .../base-camp/docs/structs/structs-sbs.md | 24 ++++++++ 16 files changed, 482 insertions(+), 3 deletions(-) diff --git a/apps/base-docs/base-camp/docs/advanced-functions/function-modifiers.md b/apps/base-docs/base-camp/docs/advanced-functions/function-modifiers.md index e047678a93..8eb0565025 100644 --- a/apps/base-docs/base-camp/docs/advanced-functions/function-modifiers.md +++ b/apps/base-docs/base-camp/docs/advanced-functions/function-modifiers.md @@ -32,6 +32,10 @@ For a production app, you'll want to use a more robust implementation of `onlyOw The address of the deployer of a contract is **not** included as an accessible property. To make it available, add it as a state variable and assign `msg.sender` in the `constructor`. +
+ +Reveal code + ```solidity contract Modifiers { address owner; @@ -42,12 +46,20 @@ contract Modifiers { } ``` +
+ +
+ ### Creating an `onlyOwner` Modifier [Modifiers] are very similar to functions and are declared with the `modifier` keyword. The modifier can run any Solidity code, including functions, and is allowed to modify state. Modifiers must have a special `_` character, which serves as a placeholder for where the code contained within the modified function will run. Create a simple `onlyOwner` modifier, which returns an `error` of `NotOwner` with the sending address if the sender is not the owner. +
+ +Reveal code + ```solidity error NotOwner(address _msgSender); ``` @@ -61,14 +73,26 @@ modifier onlyOwner { } ``` +
+ +
+ Test your `modifier` by adding a function that uses it: +
+ +Reveal code + ```solidity function iOwnThis() public view onlyOwner returns (string memory) { return "You own this!"; } ``` +
+ +
+ To test, deploy your contract and call the `iOwnThis` function. You should see the message "You own this!". Next, switch the _Account_, and try the function again. You should see an error in the console: diff --git a/apps/base-docs/base-camp/docs/arrays/filtering-an-array-sbs.md b/apps/base-docs/base-camp/docs/arrays/filtering-an-array-sbs.md index c6a6ef09d3..84f611fafb 100644 --- a/apps/base-docs/base-camp/docs/arrays/filtering-an-array-sbs.md +++ b/apps/base-docs/base-camp/docs/arrays/filtering-an-array-sbs.md @@ -52,6 +52,10 @@ Go ahead and write it on your own. It needs to: You should end up with something like: +
+ +Reveal code + ```solidity function _countEvenNumbers() internal view returns(uint) { uint result = 0; @@ -66,6 +70,10 @@ function _countEvenNumbers() internal view returns(uint) { } ``` +
+ +
+ The `_` in front of the function name is a practice used by some developers, in Solidity and in other languages, to indicate visually that this function is intended for internal use only. ### Returning Only Even Numbers @@ -79,6 +87,10 @@ Finish the function on your own. It needs to: You should end up with something like: +
+ +Reveal code + ```solidity function getEvenNumbers() external view returns(uint[] memory) { @@ -98,6 +110,10 @@ function getEvenNumbers() external view returns(uint[] memory) { ``` +
+ +
+ Did you catch the compiler warning about `view`? You aren't modifying state, so you should mark it as such. ### Testing the Function @@ -116,6 +132,10 @@ Only one way to find out. Add a contract-level variable called `numEven`, and initialize it with **5**, the number of even numbers in the array. Modify `getEvenNumbers()` to use `numEven` instead of the `_countEvenNumbers()` function. It should now look like: +
+ +Reveal code + ```solidity function getEvenNumbers() external view returns(uint[] memory) { uint resultsLength = numEven; // <- Changed here @@ -133,6 +153,10 @@ function getEvenNumbers() external view returns(uint[] memory) { } ``` +
+ +
+ Redeploy and test again. Success, the function now only costs about 57,484 gas to run! Except there is a catch. Remember, it's going to cost about 5000 gas to update `numEven` **each time** the array adds an even number. ### A More Realistic Accounting @@ -148,6 +172,10 @@ uint numEven; Add a new function called `debugLoadArray` that takes a `uint` called `_number` as an argument, and fills the array by looping through `_numbers` times, pushing each number into the array. **For now, _don't_ update `numEven`**. +
+ +Reveal code + ```solidity function debugLoadArray(uint _number) external { for(uint i = 0; i < _number; i++) { @@ -156,8 +184,16 @@ function debugLoadArray(uint _number) external { } ``` +
+ +
+ Test out the function by loading in **10** numbers. It costs about 249,610 gas to load the array. Now, add functionality to **also** increment `numEven` when the number added is even. We can't just calculate it, because although the numbers are sequential in the debug function, they might not be in real world use. +
+ +Reveal code + ```solidity function debugLoadArray(uint _number) external { for(uint i = 0; i < _number; i++) { @@ -169,6 +205,10 @@ function debugLoadArray(uint _number) external { } ``` +
+ +
+ **Be sure to redeploy** and try again with **10** numbers. This time, the cost was about 275,335 gas. That's almost 26,000 more gas in an effort to save the 5,000 gas needed to run `_countEvenNumbers()`. ### Looking at the Big Picture diff --git a/apps/base-docs/base-camp/docs/erc-20-token/erc-20-token-sbs.md b/apps/base-docs/base-camp/docs/erc-20-token/erc-20-token-sbs.md index e8057ef639..cca565b3c8 100644 --- a/apps/base-docs/base-camp/docs/erc-20-token/erc-20-token-sbs.md +++ b/apps/base-docs/base-camp/docs/erc-20-token/erc-20-token-sbs.md @@ -81,6 +81,10 @@ Redeploy. Without you needing to do anything, you should find that the `totalSup You can also use this to mint to other users. Go ahead and add the second and third accounts: +
+ +Reveal code + ```solidity constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) { _mint(msg.sender, 1 * 10**18); @@ -89,6 +93,11 @@ constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) { } ``` +
+ +
+ + **Switch back** to the first account and redeploy. Test to confirm that each account has the appropriate amount of tokens. ### Testing the Transfer Function diff --git a/apps/base-docs/base-camp/docs/erc-721-token/erc-721-sbs.md b/apps/base-docs/base-camp/docs/erc-721-token/erc-721-sbs.md index 426264dcfb..ef840ff502 100644 --- a/apps/base-docs/base-camp/docs/erc-721-token/erc-721-sbs.md +++ b/apps/base-docs/base-camp/docs/erc-721-token/erc-721-sbs.md @@ -30,6 +30,10 @@ JPGs may be all the rage right now but in the future, the selfie you post on soc Start by opening the [OpenZeppelin] ERC-721 in Github. Copy the link and use it to import the ERC-721 contract. Create your own contract, called `MyERC721`, that inherits from `ERC721Token`. Add a constructor that initializes the `_name` and `_symbol`. +
+ +Reveal code + ```solidity // SPDX-License-Identifier: MIT @@ -44,6 +48,10 @@ contract MyERC721Token is ERC721 { } ``` +
+ +
+ ### Minting NFTs The minting function that is provided by OpenZeppelin, `_safeMint`, is `internal`. To use it to let your customers mint NFTs, you'll need to implement a function in your contract that calls the one in the imported contract. @@ -67,6 +75,10 @@ To implement ID generation, simply add a `uint` called `counter` to storage and Now, you can add a function called `redeemNFT` that calls `safeMint` using the `msg.sender` and `counter`, and then increments the `counter`: +
+ +Reveal code + ```solidity function redeemNFT() external { _safeMint(msg.sender, counter); @@ -74,6 +86,10 @@ function redeemNFT() external { } ``` +
+ +
+ :::danger As a programmer, you've probably gone through great pains to internalize the idea of zero-indexing. Arrays start at 0. The pixel in the top-left corner of your screen is located at 0, 0. @@ -148,6 +164,10 @@ The metadata for [BAYC] is [stored on IPFS], but some projects even use centrali Start by saving the IPFS metadata bases as constants, at the contract level. Add an enum to enable selection between these two choices, and an instance of that enum. +
+ +Reveal code + ```solidity string constant BAYC = "https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/"; string constant DOODLES = "https://ipfs.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/"; @@ -156,8 +176,16 @@ Start by saving the IPFS metadata bases as constants, at the contract level. Add NFTMetadata nftMetadata = NFTMetadata.BAYC; ``` +
+ +
+ Finally, add an override of `_baseURI` that returns the appropriate selection based on which collection is active, and a function to swap the URI. +
+ +Reveal code + ```solidity function _baseURI() internal override view returns(string memory) { if (nftMetadata == NFTMetadata.BAYC) { @@ -175,6 +203,10 @@ function switchURI() public { } ``` +
+ +
+ Deploy, mint some NFTs, and call `tokenURI` to find the information for token number 1. You should get: ```text diff --git a/apps/base-docs/base-camp/docs/error-triage/error-triage.md b/apps/base-docs/base-camp/docs/error-triage/error-triage.md index d9a808569f..e9804a1c67 100644 --- a/apps/base-docs/base-camp/docs/error-triage/error-triage.md +++ b/apps/base-docs/base-camp/docs/error-triage/error-triage.md @@ -43,6 +43,11 @@ TypeError: Type literal_string "One" is not implicitly convertible to expected t Fix by correcting the type or value, as appropriate for your needs: +
+ +Reveal code + + ```solidity function compilerTypeErrorFixed() public pure returns (string) { string myNumber = "One"; @@ -50,6 +55,10 @@ function compilerTypeErrorFixed() public pure returns (string) { } ``` +
+ +
+ ### Conversion Errors Conversion errors occur when you attempt to _implicitly_ convert one type to another. Solidity only allows this under very narrow circumstances where there is no possibility of ambiguous interpretation of the data. @@ -74,6 +83,11 @@ TypeError: Return argument type int8 is not implicitly convertible to expected t Fix by explicitly casting as necessary: +
+ +Reveal code + + ```solidity function compilerConversionErrorFixed() public pure returns (uint) { int8 first = 1; @@ -82,6 +96,10 @@ function compilerConversionErrorFixed() public pure returns (uint) { } ``` +
+ +
+ :::tip You'll commonly need to use multiple conversions to bridge from one type to another. @@ -124,6 +142,11 @@ TypeError: Type int8 is not implicitly convertible to expected type uint256. Resolve by explicitly converting to the final type: +
+ +Reveal code + + ``` function compilerOperatorErrorFixed() public pure returns (uint) { int8 first = 1; @@ -135,6 +158,10 @@ function compilerOperatorErrorFixed() public pure returns (uint) { } ``` +
+ +
+ ### Stack Depth Limit The [EVM stack] has 1024 slots, but only the top 16 slots are accessible. As a result, you can only have fewer than 16 variables in scope at one time. @@ -195,6 +222,11 @@ CompilerError: Stack too deep. Try compiling with --via-ir (cli) or the equivale Resolve this error by breaking up large functions and separating operations into different levels of scope. +
+ +Reveal code + + ```solidity function stackDepthLimitFixed() public pure returns (uint) { uint subtotalA; @@ -241,6 +273,10 @@ function stackDepthLimitFixed() public pure returns (uint) { } ``` +
+ +
+ --- ## Logical Errors @@ -385,6 +421,11 @@ In this case, the error type is `11`, for overflow/underflow outside of an `unch Fix by changing your code to handle the expected range of values. +
+ +Reveal code + + ```solidity function badSubstractionFixed() public pure returns (int) { int first = 1; @@ -393,6 +434,10 @@ function badSubstractionFixed() public pure returns (int) { } ``` +
+ +
+ ### Divide by Zero Divide by zero errors also trigger a panic, with a code of `12`. diff --git a/apps/base-docs/base-camp/docs/hardhat-testing/hardhat-testing-sbs.md b/apps/base-docs/base-camp/docs/hardhat-testing/hardhat-testing-sbs.md index c6c7ebb33b..c1e9e0d9f9 100644 --- a/apps/base-docs/base-camp/docs/hardhat-testing/hardhat-testing-sbs.md +++ b/apps/base-docs/base-camp/docs/hardhat-testing/hardhat-testing-sbs.md @@ -75,6 +75,10 @@ Next, test the following: - The owner address - The withdraw function +
+ +Reveal code + Start with the value locked, however you must set up a `before` function, which will run before each test case. Then, include some new imports and variables: @@ -134,12 +138,17 @@ describe('Lock', function () { }); }); ``` +
### Testing `unlockTime` Next, you include test cases after the `before` function. -The first test case should verify that the `unlockTime`` variable is correct: +The first test case should verify that the `unlockTime` variable is correct. + +
+ +Reveal code ```typescript it('should get the unlockTime value', async () => { @@ -164,12 +173,20 @@ You can simply run `npx hardhat test` and then get: 1 passing (1s) ``` +
+ ### Testing Ether balance + + In order to get the balance of your `Lock` contract, you simply call `ethers.provider.getBalance`. Create a new test case: +
+ +Reveal code + ```typescript it('should have the right ether balance', async () => { // Get the Lock contract address @@ -183,6 +200,9 @@ it('should have the right ether balance', async () => { }); ``` +
+
+ Then, run `npx hardhat test` and you should get: ``` @@ -195,7 +215,11 @@ Then, run `npx hardhat test` and you should get: ### Testing `owner` -Similar to the previous test cases, you can verify that the owner is correct: +Similar to the previous test cases, you can verify that the owner is correct. + +
+ +Reveal code ```typescript it('should have the right owner', async () => { @@ -204,6 +228,9 @@ it('should have the right owner', async () => { }); ``` +
+
+ Then, run `npx hardhat test` and you should get: ``` @@ -215,6 +242,7 @@ Then, run `npx hardhat test` and you should get: 3 passing (1s) ``` + ### Testing withdraw Testing withdrawal is more complex because you need to assert certain conditions, such as: @@ -254,7 +282,11 @@ it('shouldn"t allow to withdraw a non owner', async () => { }); ``` -Finally, test that the owner can withdraw. You can manipulate the time similarly to the previous test case but you won't change the signer and will assert the new balances: +Finally, test that the owner can withdraw. You can manipulate the time similarly to the previous test case but you won't change the signer and will assert the new balances. + +
+ +Reveal code ```typescript it('should allow to withdraw a owner', async () => { @@ -277,6 +309,10 @@ it('should allow to withdraw a owner', async () => { }); ``` +
+ +
+ You can then run `npx hardhat test` and you should get: ``` diff --git a/apps/base-docs/base-camp/docs/imports/imports-sbs.md b/apps/base-docs/base-camp/docs/imports/imports-sbs.md index 11235bccda..bd21b47fb0 100644 --- a/apps/base-docs/base-camp/docs/imports/imports-sbs.md +++ b/apps/base-docs/base-camp/docs/imports/imports-sbs.md @@ -61,19 +61,32 @@ There's also an `_add` function, which is private. ::: +
+ +Reveal code + ```solidity function registerVisitor() public { visitors.add(msg.sender); } ``` +
+ +
+ Add another function to return the `numberOfVisitors`. Thanks to `using`, this can cleanly call the `length` function: +
+ +Reveal code + ```solidity function numberOfVisitors() public view returns (uint) { return visitors.length(); } ``` +
--- diff --git a/apps/base-docs/base-camp/docs/inheritance/abstract-contracts-sbs.md b/apps/base-docs/base-camp/docs/inheritance/abstract-contracts-sbs.md index 3c735c9eb1..57d6be5b2e 100644 --- a/apps/base-docs/base-camp/docs/inheritance/abstract-contracts-sbs.md +++ b/apps/base-docs/base-camp/docs/inheritance/abstract-contracts-sbs.md @@ -20,12 +20,20 @@ By the end of this lesson you should be able to: Continue with your `Inheritance.sol` file. Add `ContractD` as an `abstract contract`. Add a `virtual` function called `whoAreYou` function, but do **not** add any implementation for that function. +
+ +Reveal code + ```solidity abstract contract ContractD { function whoAreYou() public virtual view returns (string memory); } ``` +
+ +
+ ### Inheriting from an Abstract Function Update `ContractA` to inherit from `ContractD`. @@ -50,12 +58,19 @@ The clue for the correct solution is further down: `Note: Missing implementatio Only `abstract` contracts can declare functions that are not implemented. To fix this, provide an `override` implementation for `whoAreYou` in `ContractA`: +
+ +Reveal code + ```solidity function whoAreYou() public override pure returns (string memory) { return "I'm a person!"; } ``` +
+ + --- ## Conclusion diff --git a/apps/base-docs/base-camp/docs/inheritance/inheritance-sbs.md b/apps/base-docs/base-camp/docs/inheritance/inheritance-sbs.md index 72fa290e95..ab95993ed4 100644 --- a/apps/base-docs/base-camp/docs/inheritance/inheritance-sbs.md +++ b/apps/base-docs/base-camp/docs/inheritance/inheritance-sbs.md @@ -45,6 +45,10 @@ contract ContractA { [Inheritance] between contracts is indicated by the `is` keyword in the contract declaration. Update `ContractA` so that it `is` `ContractB`, and delete the `whoAmI` function from `ContractA`. +
+ +Reveal code + ```solidity contract ContractB { function whoAmI() external pure returns (string memory) { @@ -57,6 +61,10 @@ contract ContractA is ContractB { } ``` +
+ +
+ Deploy and test again. Even though `ContractA` doesn't have any functions in it, the deployment still shows the button to call `whoAmI`. Call it. `ContractA` now reports that it is "contract B", due to the inheritance of the function from `Contract B`. ### Internal Functions and Inheritance @@ -65,6 +73,10 @@ Contracts can call the `internal` functions from contracts they inherit from. Ad Add an external function called `whoAmIExternal` that returns the results of a call to `whoAmIInternal`. +
+ +Reveal code + ```solidity contract ContractB { function whoAmI() external pure returns (string memory) { @@ -83,6 +95,10 @@ contract ContractA is ContractB { } ``` +
+ +
+ Deploy and test. Note that in the deployment for `ContractB`, the `whoAmIInternal` function is **not** available, as it is `internal`. However, calling `whoAmIExternal` can call the `internal` function and return the expected result of "contract B". ### Internal vs. Private diff --git a/apps/base-docs/base-camp/docs/inheritance/multiple-inheritance.md b/apps/base-docs/base-camp/docs/inheritance/multiple-inheritance.md index 1afb2668aa..cfa69f7c2f 100644 --- a/apps/base-docs/base-camp/docs/inheritance/multiple-inheritance.md +++ b/apps/base-docs/base-camp/docs/inheritance/multiple-inheritance.md @@ -20,6 +20,10 @@ By the end of this lesson you should be able to: Continue working with your contracts in `Inheritance.sol`. Add a new contract called `ContractC` with another `whoAmI` function: +
+ +Reveal code + ```solidity contract ContractC { function whoAmI() external pure returns (string memory) { @@ -28,10 +32,18 @@ contract ContractC { } ``` +
+ +
+ ### Inheriting from Two Contracts You can inherit from additional contracts by simply adding a comma and that contract's name after the first. Add inheritance from `ContractC` (an error is expected): +
+ +Reveal code + ```solidity // bad code example, do not use contract ContractA is ContractB, ContractC { @@ -41,6 +53,10 @@ contract ContractA is ContractB, ContractC { } ``` +
+ +
+ The error is because both `ContractB` and `ContractC` contain a function called `whoAmI`. As a result, the compiler needs instruction on which to use. ```text @@ -120,22 +136,42 @@ Deploy and test. The call will now be back to reporting "contract B". Add an `enum` at the contract level in `ContractA` with members for `None`, `ContractBType`, and `ContractCType`, and an instance of it called `contractType`. +
+ +Reveal code + ```solidity enum Type { None, ContractBType, ContractCType } Type contractType; ``` +
+ +
+ Add a `constructor` to `ContractA` that accepts a `Type` and sets `initialType`. +
+ +Reveal code + ```solidity constructor (Type _initialType) { contractType = _initialType; } ``` +
+ +
+ Update `whoAmI` in `ContractA` to call the appropriate `virtual` function based on its `currentType`. +
+ +Reveal code + ```solidity // Bad code example, do not use function whoAmI() public override(ContractB, ContractC) pure returns (string memory) { @@ -149,8 +185,16 @@ function whoAmI() public override(ContractB, ContractC) pure returns (string mem } ``` +
+ +
+ You'll get errors because the function now reads from state, so it is no longer `pure`. Update it to `view`. You'll also have to update the `whoAmI` `virtual` functions to `view` to match. +
+ +Reveal code + ```solidity function whoAmI() public override(ContractB, ContractC) view returns (string memory) { if(contractType == Type.ContractBType) { @@ -163,14 +207,26 @@ function whoAmI() public override(ContractB, ContractC) view returns (string mem } ``` +
+ +
+ Finally, add a function that allows you to switch `currentType`: +
+ +Reveal code + ```solidity function changeType(Type _newType) external { contractType = _newType; } ``` +
+ +
+ Deploy and test. You'll need to use **0**, **1**, and **2** as values to set `contractType`, because Remix won't know about your `enum`. ## Final Code diff --git a/apps/base-docs/base-camp/docs/mappings/mappings-sbs.md b/apps/base-docs/base-camp/docs/mappings/mappings-sbs.md index 14a7c0a6ad..4e1ad136a3 100644 --- a/apps/base-docs/base-camp/docs/mappings/mappings-sbs.md +++ b/apps/base-docs/base-camp/docs/mappings/mappings-sbs.md @@ -50,22 +50,38 @@ As a result, there are a few special characteristics and limitations to keep in Create a contract called `Mappings`. In it, add a `mapping` from an `address` to a `uint` called `favoriteNumbers`. +
+ +Reveal code + ```solidity contract Mappings { mapping (address => uint) favoriteNumbers; } ``` +
+ +
+ ### Writing to the Mapping Add a function called `saveFavoriteNumber` that takes an `address` and `uint`, then saves the `uint` in the mapping, with the `address` as the key. +
+ +Reveal code + ```solidity function saveFavoriteNumber(address _address, uint _favorite) public { favoriteNumbers[_address] = _favorite; } ``` +
+ +
+ Deploy and test it out. Does it work? Probably... You don't have a way to read the data in `favoriteNumber`, but this problem is easy to correct. Similar to arrays, if you mark a `mapping` as public, the Solidity compiler will automatically create a getter for values in that `mapping`. @@ -80,12 +96,20 @@ That won't do at all! Luckily, you can make use of a [global variable] called `msg.sender` to access the `address` of the wallet that sent the transaction. Use this to make it so that only the owner of an `address` can set their favorite number. +
+ +Reveal code + ```solidity function saveFavoriteNumber(uint _favorite) public { favoriteNumbers[msg.sender] = _favorite; } ``` +
+ +
+ Deploy and test again. Success! --- @@ -100,6 +124,10 @@ At least not with any built in features, but you can solve this on your own. A c For this problem, you can use a helper array to store a list of all the keys present in `favoriteNumbers`. Simply add the array, and push new keys to it when saving a new favorite number. +
+ +Reveal code + ```solidity contract Mappings { mapping (address => uint) public favoriteNumbers; @@ -113,8 +141,17 @@ contract Mappings { } ``` +
+ +
+ To return all of the favorite numbers, you can then iterate through `addressesOfFavs`, look up that addresses' favorite number, add it to a return array, and then return the array when you're done. + +
+ +Reveal code + ```solidity function returnAllFavorites() public view returns (uint[] memory) { uint[] memory allFavorites = new uint[](addressesOfFavs.length); @@ -127,10 +164,18 @@ function returnAllFavorites() public view returns (uint[] memory) { } ``` +
+ +
+ On the surface, this solution works, but there is a problem: What happens if a user **updates** their favorite number? Their address will end up in the list twice, resulting in a doubled entry in the return. A solution here would be to check first if the `address` already has a number as a value in `favoriteNumbers`, and only push it to the array if not. +
+ +Reveal code + ```solidity function saveFavoriteNumber(uint _favorite) public { if(favoriteNumbers[msg.sender] == 0) { @@ -140,8 +185,16 @@ function saveFavoriteNumber(uint _favorite) public { } ``` +
+ +
+ You should end up with a contract similar to this: +
+ +Reveal code + ```solidity pragma solidity 0.8.17; @@ -168,6 +221,8 @@ contract Mappings { } ``` +
+ --- ## Conclusion diff --git a/apps/base-docs/base-camp/docs/minimal-tokens/minimal-token-sbs.md b/apps/base-docs/base-camp/docs/minimal-tokens/minimal-token-sbs.md index ac5a792e2c..b1145639cd 100644 --- a/apps/base-docs/base-camp/docs/minimal-tokens/minimal-token-sbs.md +++ b/apps/base-docs/base-camp/docs/minimal-tokens/minimal-token-sbs.md @@ -21,6 +21,10 @@ By the end of this lesson you should be able to: The minimal elements needed for a token are pretty basic. Start by creating a contract called `MinimalToken`. Add a `mapping` to relate user addresses to the number of tokens they possess. Finally, add a variable to track `totalSupply`: +
+ +Reveal code + ```solidity contract MinimalToken { mapping (address => uint) public balances; @@ -28,8 +32,16 @@ contract MinimalToken { } ``` +
+ +
+ Add a `constructor` that initializes the `totalSupply` at 3000 and assigns ownership to the contract creator: +
+ +Reveal code + ```solidity constructor() { totalSupply = 3000; @@ -38,12 +50,20 @@ constructor() { } ``` +
+ +
+ Deploy and test to confirm that the total supply is 3000, and the balance of the first account is as well. ![Balance](../../assets/images/minimal-tokens/balance.png) Update the constructor and hardcode a distribution of the tokens to be evenly split between the first three test accounts: +
+ +Reveal code + ```solidity constructor() { totalSupply = 3000; @@ -54,6 +74,10 @@ constructor() { } ``` +
+ +
+ Redeploy and test again. Now, each of the first three accounts should have 1000 tokens. ![Balance](../../assets/images/minimal-tokens/split-balances.png) @@ -68,6 +92,10 @@ To remediate this, all we need to do is add a function that can update the balan Add a `function` called `transfer` that accepts an `address` of `_to` and a `uint` for the `_amount`. You don't need to add anything for `_from`, because that should only be `msg.sender`. The function should subtract the `_amount` from the `msg.sender` and add it to `_to`: +
+ +Reveal code + ```solidity function transfer(address _to, uint _amount) public { balances[msg.sender] -= _amount; @@ -75,6 +103,10 @@ function transfer(address _to, uint _amount) public { } ``` +
+ +
+ Double-check that you've switched back to the first address and redeploy. Then, try sending 500 tokens to the second address. ![Balance](../../assets/images/minimal-tokens/transferred.png) diff --git a/apps/base-docs/base-camp/docs/new-keyword/new-keyword-sbs.md b/apps/base-docs/base-camp/docs/new-keyword/new-keyword-sbs.md index a9ac4854f9..dd305257d6 100644 --- a/apps/base-docs/base-camp/docs/new-keyword/new-keyword-sbs.md +++ b/apps/base-docs/base-camp/docs/new-keyword/new-keyword-sbs.md @@ -24,6 +24,10 @@ A contract factory is a contract that creates other contracts. To start, let's c Imagine you want to create a contract that can store its owner's name and compliment them upon request. You can create this contract fairly easily. +
+ +Reveal code + ```solidity contract Complimenter { string public name; @@ -38,6 +42,10 @@ contract Complimenter { } ``` +
+ +
+ Deploy and test. ### Creating a Factory @@ -54,6 +62,10 @@ Creating a new contract is simple: `new Complimenter(_name)` You can also save the return from that instantiation into a variable. This reference can be used to call public functions in the deployed contract, and can be cast to an address. We can use it to get an easy reference to find the copies made by the factory. The end result should look similar to: +
+ +Reveal code + ```solidity contract ComplimenterFactory { function CreateComplimenter(string memory _name) public returns (address) { @@ -63,6 +75,10 @@ contract ComplimenterFactory { } ``` +
+ +
+ ### Testing Clear the environment if you haven't already, then start by deploying `ComplimenterFactory`. You've been working hard and deserve nice things, so call `CreateComplimenter` with your name. diff --git a/apps/base-docs/base-camp/docs/reading-and-displaying-data/useAccount.md b/apps/base-docs/base-camp/docs/reading-and-displaying-data/useAccount.md index 052aebc4f4..3d256173e4 100644 --- a/apps/base-docs/base-camp/docs/reading-and-displaying-data/useAccount.md +++ b/apps/base-docs/base-camp/docs/reading-and-displaying-data/useAccount.md @@ -170,6 +170,11 @@ Open `_app.tsx` and import `useEffect` and `useState` from `'react'`. Create a state variable called `mounted`, and set it in the first render cycle to be `true`. Finally, use the same conditional rendering technique to only render the `` element if the app has mounted. +
+ +Reveal code + + ```typescript const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); @@ -183,6 +188,10 @@ return ( ); ``` +
+ +
+ With this fix in place, you should no longer get hydration errors! --- diff --git a/apps/base-docs/base-camp/docs/storage/simple-storage-sbs.md b/apps/base-docs/base-camp/docs/storage/simple-storage-sbs.md index 8d2d3868f9..8ea9451e62 100644 --- a/apps/base-docs/base-camp/docs/storage/simple-storage-sbs.md +++ b/apps/base-docs/base-camp/docs/storage/simple-storage-sbs.md @@ -26,6 +26,10 @@ Create a contract called `SimpleStorage`. In Solidity, variables declared at the class level are automatically `storage` variables. Create a variable to store the age of a person and another to store the number of cars that they own. Give `age` an initial value of your choosing, but don't make an assignment for `cars`; +
+ +Reveal code + ```solidity contract SimpleStorage { uint8 age = 41; @@ -33,6 +37,10 @@ contract SimpleStorage { } ``` +
+ +
+ Because the age of a person, or the number of cars they own, is unlikely to be greater than 255, we can use a `uint8` for each of these. For types that are smaller than 32 bytes, multiple variables of the same type will be [packed] in the same storage slot. For this to work, the variables must be declared together. ```solidity @@ -55,12 +63,18 @@ You can use the constructor to perform various setup tasks. For example, the con Create a constructor function and use it to assign the value of your choosing to `cars`. +
+ +Reveal code + ```solidity constructor() { cars = 1; } ``` +
+ ### Accessing State Variables Deploy your contract in Remix. It should work fine, but you'll have one problem: there isn't a way to see if the variables have the expected values! @@ -69,6 +83,10 @@ You could solve this by writing functions that return the values in your state v Add the `public` keyword to both variables. Unlike most languages, `public` goes **after** the type declaration. Your contract should now be similar to: +
+ +Reveal code + ```solidity contract SimpleStorage { uint8 public age = 41; @@ -79,6 +97,10 @@ contract SimpleStorage { } ``` +
+ +
+ Redeploy your contract and test to confirm. --- @@ -99,12 +121,20 @@ Given this wide variety of conditions, **a** good approach would be to handle ca To meet this need, we can write a `public` function that takes a `uint8` for `_numberOfCars` and then simply assigns that value to the state variable `cars`. Because this function modifies state, it **does not** need `pure` or `view`. It isn't either of those. +
+ +Reveal code + ```solidity function updateNumberOfCars(uint8 _numberOfCars) public { cars = _numberOfCars; } ``` +
+ +
+ Deploy and test to make sure it works as expected. :::warning @@ -121,12 +151,22 @@ Review the **Warning** in the [layout] section of the docs for more details! It would also be good to be able update the `age` value. This problem has slightly different considerations. Sadly, `age` will never go down. It should also probably only go up by one year for each update. The `++` operator works in Solidity, so we can use that to create a function that simple increments age when called. +
+ +Reveal code + + ```solidity function increaseAge() public { age++; } ``` +
+ +
+ + But what if a user calls this function by mistake? Good point! On your own, add a function called `adminSetAge` that can set the `age` to a specified value. @@ -137,6 +177,10 @@ We've got one problem remaining with this contract. What if your user has a diff As mentioned above, the `constructor` **can** take arguments and use them during deployment. Let's refactor the contract to set the two state variables in the constructor based on provided values. +
+ +Reveal code + ```solidity contract SimpleStorage { uint8 public age; @@ -148,12 +192,21 @@ contract SimpleStorage { } ``` +
+ +
+ Redeploy your contract. Note that now you have added parameters to the `constructor`, you'll have to provide them during deployment. ![Deployment](../../assets/images/storage/deployment-with-params.png) Once completed, your contract should be similar to: + +
+ +Reveal code + ```solidity contract SimpleStorage { uint8 public age; @@ -177,6 +230,10 @@ contract SimpleStorage { } ``` +
+ +
+ --- ## Conclusion diff --git a/apps/base-docs/base-camp/docs/structs/structs-sbs.md b/apps/base-docs/base-camp/docs/structs/structs-sbs.md index 8eb580572d..10971c8325 100644 --- a/apps/base-docs/base-camp/docs/structs/structs-sbs.md +++ b/apps/base-docs/base-camp/docs/structs/structs-sbs.md @@ -55,6 +55,10 @@ For Lucky Lottery Numbers, we need a collection. We could use a dynamic array, s Try to use this information to build the `struct` on your own. You should end up with something similar to: +
+ +Reveal code + ```solidity struct Favorites { uint favoriteNumber; @@ -64,6 +68,10 @@ struct Favorites { } ``` +
+ +
+ ### Instantiating a Struct with Its Name There are two ways to instantiate a struct using its name. The first is similar to instantiating a new object in JavaScript: @@ -104,6 +112,10 @@ Favorites[] public userFavorites; Next, add a `public` function to add submitted favorites to the list. It should take each of the members as an argument. Then, assign each argument to the new element via the reference returned by `push()`. +
+ +Reveal code + ```solidity function addFavorite( uint _favoriteNumber, @@ -120,8 +132,16 @@ function addFavorite( } ``` +
+ +
+ Alternatively, you can create an instance in memory, then `push` it to storage. +
+ +Reveal code + ```solidity function addFavorite( uint _favoriteNumber, @@ -140,6 +160,10 @@ function addFavorite( } ``` +
+ +
+ The gas cost is similar for each of these methods. ---