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
  • Create object: Payment,
  • Create object: Balance,
  • Create : mapping(address => Balance) public balanceReceived;
  • sendmoney()
  1. Compendium
  2. Solidity Basics
  3. Reference Types

struct

mappings + structs are a powerful combination

Picking up from the mapping example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.1;

contract MappingStructExmaple {

    struct Payment {
            uint amount;
            uint timestamp;
    }
    struct Balance {
        uint totalBalance;
        uint numPayments;
        mapping (uint => Payment) payments; //map numPayments to struct Payment - called payments
    }
    mapping(address => Balance) public balanceReceived;

    function getBalance() public view returns(uint){
        return address(this).balance;
    }

    function sendMoney() public payable {
        balanceReceived[msg.sender].totalBalance += msg.value;   // balance init @0. incremented from there.

        Payment memory payment = Payment(msg.value, block.timestamp);        //payment = Payment(amt, time)
        
        balanceReceived[msg.sender].payments[balanceReceived[msg.sender].numPayments] = payment;
        balanceReceived[msg.sender].numPayments++;
    }

    function partialWithdraw(uint _withdraw_amt, address payable _to) public {
        require(_withdraw_amt <= balanceReceived[msg.sender].totalBalance, "Balance exceeded");  //do you hav enuff to withdraw
            balanceReceived[msg.sender].totalBalance -= _withdraw_amt;       //update balances
            _to.transfer(_withdraw_amt);                                    // whr u want to send to?
    }

    function withdrawAllMoney(address payable _to) public {   //i can withdraw to addr of choice
        uint user_balance = balanceReceived[msg.sender].totalBalance;
        balanceReceived[msg.sender].totalBalance = 0;
        // checks-effect interaction pattern 
        _to.transfer(user_balance);
    }
}

Create object: Payment,

  • Payment.amount

  • Payment.time

to reflect the value and datetime per transaction.

Create object: Balance,

  • Balance.totalBalance

  • numPayments (initialized @ 0)

  • mapping(uint => Payment) payments

Create : mapping(address => Balance) public balanceReceived;

Because mappings have no length, we can't do something like balanceReceived.length or payments.length. It's technically not possible. In order to store the length of the payments mapping, we have an additional helper variable numPayments.

So, if you want to the first payment for address 0x123... you could address it like this: balanceReceived[0x123...].payments[0].amount = .... But that would mean we have static keys for the payments mapping inside the Balance struct. We actually store the keys in numPayments, that would mean, the current payment is in balanceReceived[0x123...].numPayments. If we put this together, we can do balanceReceived[0x123...].payments[balanceReceived[0x123...].numPayments].amount = ....

numPayments is not number of payments made.

no. of payments made = numPayments -1 (since we start frm 0)

numPayments serves as an incrementing counter, to load the next "index" into the mapping to create a sequence starting with 0.

balanceReceived

    //mapping(address => uint) public balanceReceived;
    mapping(address => Balance) public balanceReceived;

In this new mapping, balanceReceived[addr] returns a struct:

  • balanceReceived[addr].totalbalance

  • balanceReceived[addr].numPayments

sendmoney()

Updating total balance

balanceReceived[msg.sender].totalBalance += msg.value;

Balance.totalBalance += msg.value

The first simplifies to the second line, where the Balance object is respective to the address fed in the first.

Updating individual payments as per ledger

Balance object, contains a mapping as a member, mapping (uint => Payment) payments; it maps an integer ("index") to an object Payment.

First we will create the individual transaction.

// create the payment: insit + assign
// payment = Payment(amt, time)
Payment memory payment = Payment(msg.value, now);      //memory cos ref. type

Now we need to book the transaction into our "ledger", which is the mapping payments

// booking .numPayment
// x= 0, on init
	balanceReceived[msg.sender].payments[x] = payment  //(Payment(msg.value, now))

the key x is:
	balanceReceived[msg.sender].numPayments (= 0, on init.)
	
nesting it:
	balanceReceived[msg.sender].payments[balanceReceived[msg.sender].numPayments] = payments

Loading the transaction is easy enough, from the first line. But how to create a running sequence?

We would need an index/counter -> numPayments -> key value of mapping payments.

  • uint numPayments init as 0, so our counter starts at 0 for the first payment.

After booking a transaction, increment numPayments in preparation for the next transaction

  • balanceReceived[msg.sender].numPayments++;

Might wonder, why not use array? Gas costs; we rarely touch arrays.

PreviousmappingsNextArrays

Last updated 2 years ago