Smart Contract Development
  • Introduction
    • What is a Transaction
    • Accounts and Signing
    • What is a smart contract
  • Learning Solidity
    • Introduction
    • Module 1
      • Variable Types
      • Variable Scope: State & Local variables
      • Global variables
      • Functions
        • View and Pure
        • Shadowing in Fuctions
      • Mapping
      • Require
      • Events
    • Project #1: Simple Registry
    • Module 2
      • Constructor
      • Data Location: Value & Reference
      • Interface
      • Import
        • Importing in Foundry
      • Inheritance
      • ERC-20
      • Checks-effect-interaction pattern
    • Project #2: Basic Vault
    • Module 3
      • Payable
      • Receive
      • Fallback
      • Returns
    • Project #3: ERC20+ETH Wrapper
    • Module 4
      • Immutable and Constant
      • Fixed-point Math
      • Abstract contracts
      • ERC-4626
      • Modifier + Inheritance +Ownable
      • Type
    • Project #4: Fractional Wrapper
    • Module 5
      • If-else
      • Libraries
        • TransferHelper
      • Chainlink Oracle
    • Project #5: Collateralized Vault
  • Compendium
    • Solidity Basics
      • Variable Types
      • Value Types
        • address
        • enum
      • Reference Types
        • strings
        • mappings
        • struct
        • Arrays
        • Multi-Dimensional arrays
      • Global Objects
      • Functions
        • Function types
        • Constructor Function
        • Transaction vs Call
        • Require, Revert, Assert
      • Function signature + selectors
      • Payable
        • Payable + withdraw
        • msg.value & payable functions
      • Receive
      • Fallback function (sol v 0.8)
        • Fallback function (sol v 0.6)
      • call, staticcall, delegatecall
    • Return & Events
    • Control Variable Visibility
    • Local Variables (Storage v Memory)
    • Data Location and Assignment Behaviors
    • Modifiers & Inheritance & Import
      • import styles
    • Interface & Abstract Contracts
    • ABI & Debugging
    • Libraries
    • Conditional(ternary) operators
    • Smart Contract Life-cycle
      • Pausing Smart Contracts
      • Destroying Smart Contracts
    • Merkle Trie and MPT
    • Merkle Tree Airdrop
  • Try & catch
  • Ethereum Signatures
  • EVM, Storage, Opcodes
    • EVM
    • Wei, Ether, Gas
    • Storage
    • ByteCode and Opcodes
    • Transaction costs & Execution costs
  • Reading txn input data
  • Data Representation
  • Yul
    • Yul
      • Intro
      • Basic operations
      • Storage Slots
      • Storage of Arrays and Mappings
      • Memory Operations
      • Memory: how solidity uses memory
      • Memory: Return, Require, Tuples and Keccak256
      • Memory: Logs and Events
      • Inter-contract calls
      • calldata
      • free memory pointer
    • Yul Exercises
      • read state variable
      • read mapping
      • iterate Array, Return Sum
    • memory-safe
  • Upgradable Contracts
    • Upgradability & Proxies
    • UUPS Example
    • Minimal Proxy Example
    • TPP Example
    • 🚧Diamond
      • On Storage
  • Gas Opt
    • Block Limit
    • gasLimit & min cost
    • Solidity Optimiser
    • Memory v calldata
    • Memory caching vs direct storage vs pointers
    • < vs <=
    • reverting early
    • X && Y, ||
    • constant and immutable
    • caching sload into mload
    • Syntactic Sugar
    • using unchecked w/o require
    • Compact Strings
    • Calling a view function
    • Custom errors over require
    • usage of this.
      • multiple address(this)
  • ERCs & EIPs
    • ERC-20.sol
      • Core functions
      • transfer()
      • transferFrom()
      • TLDR transfer vs transferFrom
    • Landing
      • ERC721.sol
      • EIP-721
        • LooksRare
        • Page 1
      • ERC-1271
      • EIP-2981
      • ERC-165
      • EIP-1167: Minimal Proxy Contract
    • VRFConsumerBase
    • UniswapV2Library
  • Yield Mentorship 2022
    • Projects
      • #1 Simple Registry
      • #2 Basic Vault
      • #3 ERC20+ETH Wrapper
        • setFailTransferTrue
      • #4 Fractional Wrapper
      • #5 Collateralized Vault
        • Process
        • Vault.sol
        • Testing
        • Chainlink Oracles
        • Pricing + Decimal scaling
        • Refactor for Simplicity
      • #9 Flash Loan Vault
        • Implementing ERC3156
        • Full code for lender
        • Ex-rate calculation
    • State Inheritance Testing
    • Testing w/ Mocks
    • Yield Style Guide
    • Github Actions
    • TransferHelper.sol
    • math logic + internal fn
    • Interfaces: IERC20
  • Foundry
    • Overview
    • Importing Contracts
    • Testing
      • stdError.arithmeticError
      • assume vs bound
      • Traces
      • label & console2
      • std-storage
  • Smart Contract Security
    • Damn Vulnerable Defi
      • 1. Unstoppable
      • 2. Naive receiver
      • 3. Truster
      • 4. Side Entrance
      • 5. The Rewarder
      • 6. Selfie
      • 7. Compromised
      • 8. Puppet
      • 9. Puppet V2
      • 10 - Free Rider
    • Merkle Tree: shortened proof attack
  • Fixed-Point Math
    • AMM Math
  • Solidity Patterns
    • checks-effects-interactions pattern
    • Router // batch
    • claimDelegate: stack unique owners
    • claimDelegate: cache previous user
  • Array: dup/ascending check
  • Deployment
    • Behind the Scenes
    • Interacting with External Contracts
    • Logging, Events, Solidity, Bloom Filter
  • Misc
    • Mnemonic Phrases
    • Bidul Ideas
  • Archive
    • Brownie Framework
      • Brownie basics
        • storing wallets in .env
        • Deployment to ganache
        • Interacting with contract
        • Unit Testing
        • Testnet deployment
        • Interacting w/ deployed contract
        • Brownie console
      • Brownie Advanced
        • Dependencies: import contracts
        • helpful_scripts.py
        • verify and publish
        • Forking and Mocking
        • Mocking
        • Forking
      • Testing
      • Scripts Framework
        • deploy.py
        • get_accounts
        • deploy_mocks()
        • fund_with_<token>()
      • Brownie Networks
    • Brownie Projects
      • SharedWallet
        • Multiple Beneficiaries
        • Common Code Contract
        • Adding Events
        • Renounce Ownership
        • Separate Files
      • Supply Chain
        • ItemManager()
        • Adding Events
        • Adding unique address to each item
      • Lottery
      • Aave - Lending and Borrowing
        • Approve & Deposit
        • Borrow
      • NFT
      • Advanced Collectible
        • adv_deploy() + Testing
        • Create Metadata
        • Setting the TokenURI
    • node npm
    • Ganache
    • Truffle
    • Remix
    • Installing Env
Powered by GitBook
On this page
  • Objective:
  • For reference, Simple Collectible:
  • Constructor and Initial layout
  • setTokenURI
  • Mapping Updates should emit events
  1. Archive
  2. Brownie Projects

Advanced Collectible

PreviousNFTNextadv_deploy() + Testing

Last updated 7 months ago

Objective:

Create NFT contract that allows a user to mint a Doggie NFT of random breed. Randomness will be supplied by Chainlink VRF.

  • Inherit ERC721, VRFConsumerBase

  • Breed {PUG, SHIBA_INU, ST_BERNARD}

For reference, Simple Collectible:

Constructor and Initial layout

// SPDX-License-Identifier: MIT
pragma solidity 0.6.6;

// using OpenZepplin 3.x 
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";

contract AdvancedCollectible is ERC721, VRFConsumerBase {
    uint256 public tokenCounter;      
    uint256 public fee;
    uint256 public keyhash;
    
    enum Breed {PUG, SHIBA_INU, ST_BERNARD}
    
    constructor(
    address _vrfCoordinator, 
    address _linkToken, 
    uint _fee, 
    bytes32 _keyhash) public ERC721("Doggie", "DOG"), VRFConsumerBase(_vrfCoordinator, _linkToken) {

        tokenCounter = 0;
        fee = _fee;
        keyhash = _keyhash;
    }

    function createCollectible(string memory tokenURI) public returns(bytes32) {
        bytes32 requestId = requestRandomness(keyhash,fee);  //catch requestID 
    }

    function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
        require(randomness > 0, "RNG not received");

    }

}

Constructors

We have two nested constructors, each from our inherited contracts. If you choose to parameterize the arguments for the nested constructor, you will have to pass them into the top-level constructor and then again into the nested - like so in VRFConsumerBase above.

With ERC721(), both name and symbol are going to be static, so we opt to pass them in directly.

Approach: createCollectible & fulfillRandomness

When a user calls createCollectible, a request for randomness is initiated to the VRF Coordinator. This request is subsequently passed off-chain to the oracle network for RNG, and back to the VRF Coordinator for on-chain verification.

On verification, VRF Coordinator calls the fulfillRandomness function on our contract, returning the random value.

Since fulfillRandomess receives the random number, we will have to house the breed selection and minting components within this function - different from SimpleCollectible.sol

createCollectible()

We want the user that called createCollectible() to be assigned the tokenId and breed generated from the RNG.

To capture this, we use a mapping between requestId and address.

function createCollectible(string memory tokenURI) public returns(bytes32) {
    bytes32 requestId = requestRandomness(keyhash,fee);     //catch requestID to associate RNG with user with tokenID
    requestIdtoSender[requestId] = msg.sender;
}

fulfillRandomness()

function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
    require(randomness > 0, "RNG not received");
    Breed breed = Breed(randomness % 3);
    uint mintingTokenId = tokenCounter;
    tokenIdToBreed[mintingTokenId] = breed;
    
    //_safeMint(msg.sender, mintingTokenId);
    
    tokenCounter = ++ tokenCounter;
    return mintingTokenId;  
}
  • use this to generate a random breed selection -> modulo 3 (since 3 breed types)

  • tokenCounter is current available tokenID -> grab it as mintingTokenId

  • map mintingTokenId to the random breed attribute selected:

// add to declaration section
mapping (uint256 => Breed) public tokenIdToBreed; 
  • increment tokenCounter and return mintingTokenId (which was just minted)

NFT attributes can be associated to their tokenID via mappings

mint NFT -> safeMint()

cannot use _safeMint(msg.sender, mintingTokenId)as we did before.

The caller of fulfillRandomness is VRF Coordinator, therefore msg.sender would yield that contract's address.

We need to get the original msg.sender of createCollectible

This is why the mapping requestIdtoSender was introduced earlier. It associates the original caller (user), with their requestId - with this we can extract the correct address. Like so:

function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
    require(randomness > 0, "RNG not received");
    Breed breed = Breed(randomness % 3);
    uint mintingTokenId = tokenCounter;
    tokenIdToBreed[mintingTokenId] = breed;
    
    //_safeMint(msg.sender, newTokenId);
    address owner = requestIdtoSender[requestId];
    _safeMint(owner, mintingTokenId);

    tokenCounter = ++ tokenCounter;
    return mintingTokenId;
}

setTokenURI

function setTokenURI(uint tokenId, string memory tokenURI) returns() {
    // associate on-chain metadata (breed) with off-chain metadata (URI)
    require(_isApprovedOrOwner(_msgSender(), tokenId), " ERC721: caller is not owner nor approved!");
    _setTokenURI(tokenId, _tokenURI);
}

For a token that exists, it returns whether 'spender' is allowed to manage 'tokenId'._msgSender()

  • _msgSender() is from Context.sol which ERC721 inherits.

**

Mapping Updates should emit events

This is a recommended best practice.

event requestCollectible(bytes32 indexed requestId, address requester);
event breedAssigned(uint indexed tokenId, Breed breed);
function createCollectible() public returns(bytes32) {
    bytes32 requestId = requestRandomness(keyhash, fee);     //catch requestID to associate RNG with user with tokenID
    requestIdtoSender[requestId] = msg.sender;
    emit requestCollectible(requestId, msg.sender);
}
function fulfillRandomness(bytes32 requestId, uint randomness) internal override {
    require(randomness > 0, "RNG not received");
    Breed breed = Breed(randomness % 3);
    uint mintingTokenId = tokenCounter;
    
    tokenIdToBreed[mintingTokenId] = breed;
    emit breedAssigned(mintingTokenId, breed);
    
    //_safeMint(msg.sender, newTokenId);
    address owner = requestIdtoSender[requestId];
    _safeMint(owner, mintingTokenId);
    // setTokenURI
    tokenCounter = ++ tokenCounter;
}
From ERC721