Solidity Tinkering w/Scaffold-ETH

πŸ— 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. πŸ‘‡


1. Getting started with Scaffold-ETH: Add totalCounter

  • Spin up Scaffold-ETH. Check out the Burner wallet, get funds for your wallet, and try out the example UI and contract component page.

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;
    // ....
}

2. Array of Structs

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));
    // ....
}

3. Inheritance

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 {...}

4. Deploy script

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"],
  //..
});

5. New Contract + Contract Interaction

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.

  1. 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 {}
    }
    
  2. 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"];
    
  3. 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}
      />
    
  4. 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");
    }
    
  5. 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! πŸ˜‰

Subscribe to BuidlGuidl
Receive the latest updates directly to yourΒ inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.