π Scaffold-ETH is a Decentralized Application(dApp) toolkit to learn and build on Ethereum without the overhead of setting up every piece of the application yourself.
You can use Scaffold-ETH to learn about Solidity by taking the initial YourContract.sol smart contract & start iterating on it by adding more complex concepts onto it. Below are some quests to follow along.
You can also check out the BG Labs video on Solidity Deep Dive over here. π
Exercise: Add a counter called 'totalCounter' to your smart Contract that keeps track of how many times the purpose changes.
uint256 public totalCounter;
function setPurpose(string memory newPurpose) public payable {
totalCounter += 1;
// ....
}
Structs: Structs are a data type that allows you to group related data together. Mappings: A mapping is a data type that associates values with keys and stores them. Mappings cannot be looped over. Here's how you can iterate through a mapping.
Exercise: We want to store all the purposes sent by a given address and the block timestamp. We need a Struct & a mapping with an array of those structs. Go to Solidity by example, copy/paste and modify.
struct PurposeSubmission {
string text;
uint256 timestamp;
}
mapping(address => PurposeSubmission[]) public userSubmissions;
function setPurpose(string memory newPurpose) public payable {
userSubmissions[msg.sender].push(PurposeSubmission(newPurpose, block.timestamp));
// ....
}
Inheritance allows us to override or extend the behavior of the original contract. The most common use case is inheriting from battle-tested 3rd party contracts (like OpenZeppelin); these are audited and commonly used contracts.
Exercise: Inherit the OpenZeppelin Ownable Contract. Check all the new functions/variables that appear in the UI.
contract YourContract is Ownable {...}
We'll come back to the owner that comes with the Ownable contract.
Exercise: Solidity supports multiple inheritances. On the same Contract, inherit the OpenZeppelin Pausable contract as well.
import "@openzeppelin/contracts/security/Pausable.sol";
contract YourContract is Ownable, Pausable {...}
The deploy script is where we prepare our contract for deployment (arguments, one-time tx after deploy, etc). You can edit your deployment scripts in packages/hardhat/deploy
Exercise: Call transferOwnership from the deploy script.
await YourContract.transferOwnership(
"FRONTEND_ADDRESS_HERE"
);
Exercise: transferOwnership from the constructor.
// contract
constructor(address _initialOwner) payable {
Ownable._transferOwnership(_initialOwner);
}
// deploy script
await deploy("YourContract", {
//..
args: ["FRONTEND_ADDRESS_HERE"],
//..
});
You can have multiple contracts on your project and look into contract to contract interaction.
Exercise: Create a new contract called WithdrawerContract to get money from our PurposeContract.
Create the Contract
pragma solidity >=0.8.0 <0.9.0;
//SPDX-License-Identifier: MIT
// Create a blueprint first
contract WithdrawerContract {
function withdrawFrom(address _contractAddress) public {
//
}
// to support receiving ETH by default
receive() external payable {}
fallback() external payable {}
}
Create a new deploy script to deploy the Withdrawer Contract.
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
await deploy("WithdrawerContract", {
from: deployer,
log: true,
waitConfirmations: 5,
});
};
module.exports.tags = ["withdrawFrom"];
Add the Withdrawer Contract to the UI on `App.jsx`
< Contract
name="WithdrawerContract"
price={price}
signer={userSigner}
provider={localProvider}
address={address}
blockExplorer={blockExplorer}
contractConfig={contractConfig}
/>
Implement a withdraw function on the Purpose contract & try to call it from the UI.
function withdraw() public {
require(msg.sender != tx.origin, "Not a contract");
(bool sent,) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
}
Contract to Contract call on WithdrawerContract
interface IYourContract {
function withdraw() external;
}
contract WithdrawerContract {
function withdrawFrom(address _contractAddress) public {
IYourContract(_contractAddress).withdraw();
}
// ...
}
Hope this was helpful! These were some sample tasks & you can continue to try out new concepts from Solidity by example with Scaffold-ETH. Next up, why not take on SpeedRunEthereum! π