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
  • Adding withdrawTo
  • Time-locked Withdrawals
  • Transfer v send v call
  • Problems with send() and transfer()
  • call()
  • A typical implementation
  • Issues with call()
  • Calling payable functions
  1. Compendium
  2. Solidity Basics
  3. Payable

Payable + withdraw

Adding a dumb withdraw function withdrawMoney()

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.1;

contract SendMoneyExample {
    
    //public state variable -> getter function auto-created.
    uint public balanceReceived;                

    function receiveMoney() public payable {
        balanceReceived += msg.value;          //will not account for withdrawls as it only increments.

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

    function withdrawMoney() public {
        //to = payable(msg.sender) works from 0.8.0
        address payable to = payable(msg.sender);  
        to.transfer(getBalance());
    }
}
  • address to = msg.sender;

    • to is an address, which we set to take the value of the sender's wallet address (msg.sender).

    • Sender in the sense of transaction hash. Someone else is creating and sending this transaction: "from". (fella who called the SC).

  • if we want to send money to this address, we need to make it explicitly payable.

    • address payable to = payable(msg.sender);

  • transfer is another property of an address object.

    • here we have stupidly opted to send all the SC's ether back to the sender/caller's address.

If you want to send ether to an address, the address must be of type address payable. That is why you are casting msg.sender as payable. What is payable is not the amount, but the address.

Notice we have no need to declare storage/memory for to, when we declare and initialize it within a function

address is a value type -> no need to specify data location.

Adding withdrawTo

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.1;

contract SendMoneyExample {

    uint public balanceReceived;                //public state variable -> getter function auto-created.

    function receiveMoney() public payable {
        balanceReceived += msg.value;          //will not account for withdrawls as it only increments.
    }
    
    function getBalance() public view returns(uint) {
        return address(this).balance;
    }

    function withdrawMoney() public {
        address payable to = payable(msg.sender);
        to.transfer(getBalance());
    }
    
    function withdrawMoneyTO(address payable _to) public {
        to.transfer(getBalance());
    }
    
}

in withdrawTO, we declare to as address and payable in the function parameter, so there is no need to do so in the body.

function will expect an address to be supplied on call, following which it will transfer all the SC's balances to the address -> not locked to sender's address.

Time-locked Withdrawals

block.timestamp is a global object.

  • contains the timestamp when a block was mined.

  • Not necessarily the current timestamp when the execution happens. It might be a few seconds off. But it's still enough to do some locking.

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

uint public balanceReceived;               
uint public lockedUntil;

function receiveMoney() public payable {
    balanceReceived += msg.value;
    lockedUntil = block.timestamp +1 minutes;         

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

function withdrawMoney() public {
    address payable to = payable(msg.sender);
    to.transfer(getBalance());
}

function withdrawMoneyTO(address payable _to) public {
    if(block.timestamp > lockedUntil){
        _to.transfer(getBalance());
    }
        
}
  • added uint public lockedUntil;

  • added to function receiveMoney(): lockedUntil = block.timestamp +1 minutes;

  • added to function withdrawMoneyTO: if condition to check time

Now, after sending money, click "withdrawMoney" - and nothing happens. The Balance stays the same until 1 Minute passed since you hit "receiveMoney".

call is the recommended way of sending ETH (do no use send or transfer)

Transfer v send v call

In a failure case, the transfer function reverts.

Send is similar to transfer. But if the payment fails, it will not revert. Instead, it returns false. The failure handling is left to the calling contract. (code continues execution)

For both send and transfer 2300 gas is forwarded to the receiving contract. Small amount sufficient to trigger an event for logging.

Problems with send() and transfer()

Both functions were considered the go-to solution for making payments to EOAs or other smart contracts. But since the amount of gas they forward to the called smart contract is very low, the called smart contract can easily run out of gas. This would make the payment impossible.

The problem here is that even if the receiving contract is designed carefully not to exceed the gas limit, future changes in the gas costs for some opcodes can break this design.

Therefore, the recommended way of sending payments is the function call().

After the Istanbul hardfork send and transfer methods have been deprecated.

call()

call is the recommended way of sending ETH from a smart contract.

A simple call-statement looks like that:

(bool success, bytes memory data)= receivingAddress.call{value: 100}("");

Let’s have a look on the right side. The value specifies how many wei are transferred to the receiving address. In the round brackets, we can add additional data like a function signature of a called function and parameters.

If nothing is given there, the fallback() function or the receive() function is called.

The return value for the call function is a tuple of a boolean and bytes array.

  • boolean is the success or failure status of the call

  • bytes array has the return values of the receiving contract's function called which need to be decoded.

A typical implementation

// example
(bool success, ) = msg.sender.call{value:amt}("");
require(success, "Transfer failed.");

If the transfer fails, success is assigned the value false, that means the require statement would evaluate the false and fail, therefore reverting everything does so far back to original state.

code cannot progress forth unless success is true.

using call, one can also trigger other functions defined in the contract and send a fixed amount of gas to execute the function. The transaction status is sent as a boolean and the return value is sent in the data variable.

(bool sent, bytes memory data) = _to.call{gas :10000, value: msg.value}("func_signature(uint256 args)");

Issues with call()

With call(), the EVM transfers all gas to the receiving contract, if not stated otherwise. This allows the contract to execute complex operations at the expense of the function caller.

Another issue is that it allows for so-called re-entrancy attacks. This means that the receiver contract calls the function again where the call() statement is given. If the sender contract is improperly coded, it can result in draining larger amounts of funds from it than planned. This issue requires more awareness by the contract authors.

Calling payable functions

weth.deposit{value: 1 ether}();
function deposit() public payable {
    balanceOf[msg.sender] += msg.value;
    emit Deposit(msg.sender, msg.value);
}

From Vault we call the payable function deposit, and pass msg.value within {value: 1 ether}

PreviousPayableNextmsg.value & payable functions

Last updated 2 years ago

payable(msg.sender) works from 0.8.0

https://ethereum.stackexchange.com/questions/64108/whats-the-difference-between-address-and-address-payable
https://kushgoyal.com/ethereum-solidity-how-use-call-delegatecall/