Skip to the content.

← Back to Homepage

General Notes : The doc explaining the syntax part along with some extra explanations that help understand the concept. Based on Solidity Docs, ETH StackExchange and more.

Types :

  1. uint8, uint16, uint<X> are unsigned ints with X bits of capacity.
  2. int8, int32, int<X> are signed integers with X bits of capacity.
  3. bools are true/false. Default is false.
  4. address type is a valid address. Comes in 2 flavors, normal address and address payable. Difference is that payable type has 2 extra methods. transfer and send.
  5. Struct is also a type. But note that it is just a template. We need to declare somewhere else potentially inside a mapping or something to instantiate the actual variable like, let there be struct S template and we need to do something like S a; to create an object a of type S.
  6. Note that strings and bytesX types are Big endian. While other value types are little endian. bytesX[k] gives kth byte, it is read only.
  7. Mapping can have any type as key except user defined types like Array, Struct and Mapping. The value can be anything, even user defined types
  8. Mappings can only be defined in storage, i.e as state variables. Even if something like a struct contains a mapping, that struct can’t be instantiated inside a function as memory does not allow mappings even if they are inside some allowed type.
  9. Enums are just like C. Can have a value of only one of it’s members at a time. Members are indexed from 0. Returning enums returns uint as ABI does not have the concept of Enum. Also, defualt value of enum is first member. Can assign integer i to enum who’s value becomes ith member. Out of bounds assignment raises a panic error. Max 256 members.
contract Enum {
    // Enum representing shipping status
    enum Status {
        Pending,
        Shipped,
        Accepted,
        Rejected,
        Canceled
    }
    Status public status;
    // Returns uint
    // Pending  - 0
    // Shipped  - 1
    // Accepted - 2
    // Rejected - 3
    // Canceled - 4
    
    // Since enum types are not part of the ABI, the signature of "getChoice"
    // will automatically be changed to "getChoice() returns (uint8)"
    // for all matters external to Solidity.
    function getChoice() public view returns (status) {
        return status;
    }

    // Update status by passing uint into input
    function set(Status _status) public {
        status = _status;
    }
}
  1. Struct is a user defined type. Similar to enums, structs can also be imported. Structs are similar to classes. To instantiate a new object, use the constructor. Bear in mind that all new objects are created in memory(as only place we can do this is inside a function) and if the struct were to contain mapping, solidity throws an error.
  2. Arguments to constructor must follow parameter order or specify {key:value} object if order isn’t followed.
  3. Array is similar to other langs. uint[] myArris the way to declare dynamic arrays. uint[10] tenArrayis fixed array. Note that static array values are initialized to zero . Note that functions can also return arrays. return staticOrDynamicArrayis the way. Note that if arrays grow too big, return takes more gas.
  4. bytes.concat(...) returns (bytes memory) can be used to concat variable number of bytes and also bytesX types into a single bytes.
  5. Note that memory can have dynamic arrays via new but can’t use resize methods like push and pop on memory arrays.
  6. using delete myArray[i] does not shrink the array. delete just changes values to default value.
  7. You can compare two dynamic length bytes or string by using keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))
  8. string does not have length property to access it’s length. So to make it usable in code that relies on length, cast it to bytes with bytes(string) and then use it.
  9. Functions pointers are also a supported type. function takeUint(uint) external is also a valid type. It can be assigned and also equality checked with other function pointers.

Variable Scopes :

There are 3 types of variable scopes.

  1. local, declared and used inside functions. Destroyed after execution. Not stored on blockchain
  2. state, declared in the contract scope, stored on blockchain. Accessible by contract functions
  3. global, accessed by all. Like msg.sender and block.timestamp.

Immutability :

  1. Constant. Declared once and can’t be changed
  2. Immutable. Can be changed only on contract instantiation i.e during inside of constructor.

delete operator :

  1. delete operator assigns default value to the operand. It does not remove the element.
  2. Using it like delete arr[3] assigns default value to 3rd element. Array size is not reduced.
  3. delete ing static arrays sets all values to zero. delete ing dynamic arrays sets the length of array to zero.
  4. mappings can’t be deleted as EVM just stores values at keccak256(key). And EVM does not know what keys are beforehand.
  5. deleteing struct resets members to their default values.

Truncation and padding :

  1. Note that because bytes and string are Big endian, any paddings are added after and any truncations are done from first value.
  2. A simple logic is that, paddings are obviously added after the value and truncations are done by fitting values from start and leaving that don’t fit.
bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // e will be 0x12
  1. Other types are little endian and it is reverse. See above

Accounts :

Transactions :

Storage, Memory and Stack :

Calls :

Self Destruct :

  1. Solidity does not support default exports like JS.
  2. Importing in this way, import "filename" is a bad choice as namespace will be polluted. Can be used with standard third party files like OpenZeppelin/Uniswap
  3. import "filename" as symbolName lets us access it’s contents like symbolName.variable which is better.

Storage vs Memory vs Calldata :

  1. Storage is persistent till network lives. Memory is persistent till function call completes. Calldata is persistent till network lives, but is not modifyable and is not tied to the contract. More on that later..
  2. Storage is the actual data taking space when ETH client is downloaded. So it is a disk write. So it costs more gas
  3. Memory is data during execution. It can only be created inside functions as local variables. Miner’s RAM/Stack is where memory data lives. After each function call completes it is wiped. Costs less gas than storage
  4. Calldata is the input data generated by the user and sent as a transaction. All the parameters the user intended to give the contract are located in the calldata. It’s the data field in a transaction.

Why does it matter to the contract/EVM?

  1. It gives the flexibility to choose where we store data. Memory is cheaper and can be used to read and write intermediate computations.
  2. Functions can also be typed and can be told to find which data from where

Copies and References :

  1. Note that calldata can only be read. It is part of a signed ethereum transaction, you can’t change it. So uint calldata imaginaryNum = 6 does not make sense.
  2. Assigning between storage and memory creates copies.
  3. Assigning between memory to memory or storage to storage creates References! Be careful with the reference traps. Refer to above code for traps

Here comes the stack :

  1. EVM is a stack based machine. Sure, permanent storage and memory exist but it can’t just be working with them alone. Computers are register based machines, they store intermediate values in these registers and use them to compute further.
  2. EVM instead of registers uses stack.
  3. How does it matter to contract? Note that value types like uint, bool when declared inside a function are created on the stack!
  4. Similar to memory, stack is newly created when an external call is made. Hence the word stacktrace.
  5. Security Note : EVM allows an opcode that let’s you swap last 16th value. If a function invokes other function, the stack might propagate and the inner function may access the caller’s variables. Turns out, No! Stack is available only between external calls.

Functions :

  1. Functions in solidity can take in and also return multiple values.
  2. Destructuring ` (a, b, c) = returnsThree()` is also present in solidity. But note that brackets aren’t squared like in JS
  3. Functions cannot use maps for inputs or outputs
  4. If a function returns something, needs to be specified in it’s signature. like function myFunc returns (uint). For functions returning nothing, don’t specify the returns keyword and type, something like returns (void) isn’t valid in solidity.
  5. return statement can be omitted if name is specified after returns keyword. Below code is perfectly returns though there’s no return statement.
function assigned()
        public
        pure
        returns (
            uint x,
            bool b,
            uint y
        )
    {
        x = 1;
        b = true;
        y = 2;
    }
  1. View functions mean that they only read. No state change. Pure functions mean they neither read nor write from state. Note that the constraints apply only on state. You can still create memory objects and interact with the user’s calldata in a normal way.

  2. Functions are also a valid value type. They can be equality checked as can be passed as parameters to other functions.

    contract Oracle {
        struct Request {
            bytes data;
            function(uint) external callback;
        }
        Request[] private requests;
       
        function storeRequest(bytes memory data, function(uint) external callback) public {
            requests.push(Request(data, callback));
        }
           
        function triggerRequest(uint index) external {
        	bytes memory dataToCall = requests[index].data;
        	requests[index].callback(dataToCall); // The function stored during storeRequest is called
        }
    }
    
  3. External function types are stored as a 24 byte value where the first 20 bytes are the address to invoke the function on and the next 4 bytes are the **function selector ** of the function. These are accesible on the type, i.e callback.address and callback.selector inside contracts.

  4. Internal function types can only be passed to internal functions since they are just an internal JUMP.

  5. Functions can also be attached to types using the using functionName for Type syntax where functionName is a function at the file level. The global extension can also be used for user defined types where they are defined, to extend the effect globally i.e inherited or imported. Library functions can also be attached as using {LibraryName.functionName} for someType.

  6. // File restrictednumber.sol
    type RestrictedNumber is int256;
    using {plusOne, minusOne} for RestrictedNumber global; // This extends the effect to places where RestrictedNumber is used
        
    function plusOne(RestrictedNumber x) pure returns (RestrictedNumber)
    {
        unchecked {
            return RestrictedNumber.wrap(RestrictedNumber.unwrap(x) + 1);
        }
    }
        
    function minusOne(RestrictedNumber x) pure returns (RestrictedNumber)
    {
        unchecked {
            return RestrictedNumber.wrap(RestrictedNumber.unwrap(x) - 1);
        }
    }
        
    /// This is a creation function that ensures that
    /// values are small enough. The idea is that the function
    /// RestrictedNumber.wrap should only be used in the file
    /// that defines the type, so that we have control over
    /// the invariants.
    function createRestrictedNumber(int256 value) pure returns (RestrictedNumber)
    {
        // Ensure that the number is "small".
        // Larger constants like 2**200 would also work.
        require(value <= 100 && -value <= 100);
        return RestrictedNumber.wrap(value); // Wrap creates the user defined type from underlying
    }
    
  7. // File owned.sol
    import {RestrictedNumber} from "./restrictedNumber.sol";
        
    contract Owned {
        RestrictedNumber public ownerCount;
        mapping(address => bool) public isOwner;
        
        constructor() {
            _addOwner(msg.sender);
        }
        
        function addOwner(address owner) external {
            require(isOwner[msg.sender]);
            _addOwner(owner);
        }
        
        function removeOwner(address owner) external {
            require(isOwner[msg.sender]);
            require(isOwner[owner]);
            // Because of <global>, we do not have to add
            //  <using for> in the contract to use the
            // 	<minusOne> function.
            ownerCount = ownerCount.minusOne();
            isOwner[owner] = false;
        }
        
        function _addOwner(address owner) internal {
            require(!isOwner[owner]);
            ownerCount = ownerCount.plusOne();
            isOwner[owner] = true;
        }
    }
    

Visibility :

  1. Public : Can be called internally(from inside current contract) and also externally(other contracts or EOAs)
  2. private : Can only be called from inside of a the contract internally.
  3. internal : Only internally from inside a contract or by children inheriting it. Something on the lines of Java’s protected.
  4. external : Can only be called externally. To call from inside the contract, use this.f()

About return values :

  1. Calling functions as an EOA/User :
    • Only constant i.e view or pure functions can return values to users.
    • Non constant i.e state changing functions return a TXN HASH and cost gas
    • The only way to extract any values from state changing function to user/UI is Listening to events.
  2. Calling functions of a contract from another contract :
    • Both constant and non constant functions can return values when the caller is a contract.
    • Note that constant functions too cost a little when called from a contract .
    • There’s always a cost incurred when calling a smart contract. Constant or Non Constant. But, becuase the nodes you use or third party APIs run actual nodes, they return the data and don’t make an EVM call and hence no gas.
    • Because the contract running or the EVM for that matter doesn’t have the concept of API nodes, it always bills the caller for any function. Hence, It costs gas to call constant functions of a contract from other contract.

require, revert and assert:

  1. There are two kinds of Errors that EVM can throw. Error and Panic.
  2. Consider Error like a soft check exception you can throw in your contracts. Like Input validation or return values or state checks
  3. Panic is the actual compiler/EVM screaming that something terribly bad has happened. Likes of them are divide by zero, overflows, negative indices, large memory allocation. There’s also a special one called compiler inserted panic. More on that later.
  4. Error is the exception thrown by your code. Your code intended to throw that error and is not some fatal error like panic thrown by EVM.
  5. Also note that assert consumes all gas and throws while require refunds left over gas and throws.
  6. So consider require as your intended check while assert is a sanity check. Failing assert should mean an overlooked edge case or a bug in code while failing require means just not being eligible.

Ways you can throw Error :

  1. Now, require syntax is require(checkReturnsTrueOrFalse, Error). The checkReturnsTrueOrFalse should evaluate to a bool.
  2. If it evaluates to a false, an Error is thrown with Error as description.
  3. Syntax for revert is revert(descriptionString) or just revert(). This also throws an error.

When to use require vs revert :

if(complexErrorCheck == false){
	revert customError();
}

Ways a Panic can occur :

  1. Remeber compiler inserted panic from above, let’s talk about that.
  2. Along with examples like above, panic can occur in two extra compiler/user invoked ways
    • Panic 0x00 is thrown by compiler inserted panic
    • Panic 0x01 is thrown by Failing an assert condition!

Assert :

  1. Assert syntax is assert(onFalseThrowPanic). If the check becomes false, a panic Panic 0x01 is thrown.

Modifiers :

  1. Function modifiers are special code that can be injected before a function on which the modifier is applied is called,.
  2. They can be used for things like Access control, some input checks and most importantly Re-Entrancy Guards.
  3. Actually, modifiers are invoked before executing the function. But, the modifier can choose Where to pass the power back to it’s function. The _; is used to execute the actual function. Check below
contract Mutex {
    bool locked;
    modifier noReentrancy() {
        require(
            !locked,
            "Reentrant call."
        );
        locked = true;
        _;      // Look over here. Notice the _;
        locked = false;
    }

    /// This function is protected by a mutex, which means that
    /// reentrant calls from within `msg.sender.call` cannot call `f` again.
    /// The `return 7` statement assigns 7 to the return value but still
    /// executes the statement `locked = false` in the modifier.
    function f() public noReentrancy returns (uint) {
        (bool success,) = msg.sender.call("");
        require(success);
        return 7;
    }
}
  1. In the above code when f function is called, first noReentracy modifier is executed. While execution, the modifier performs it’s logic and then calls it’s function using _;. The _; is just a placeholder for modifier to pass the power to it’s function. Note that additional logic can also be performed after the function executes.
  2. The modifier above also does some state changes after function has executed.
  3. So modifiers seem to be a wrapper function around actual functions. Hence also note that arguments to function are also forwarded to the modifiers.
  4. But arguments need to be passed to modifiers in the function signature like function someFunction(address _user) notSpecificAddress("0x......"), here the value is hardcoded but it can be a state variable or an argument passed down by the function.
  5. Note that the placement of _; is key to writing secure code. So for starters, include it at end of custom modifers.

Events :

  1. Events are inheritable members of contracts. The syntax is
event Deposit(
        address indexed _from,
        bytes32 indexed _id,
        uint _value
    );
  1. And to emit them,

emit Deposit(msg.sender, _id, msg.value);

  1. Any web3 JS library can then subscibe to these events.
  2. You can add the attribute indexed to up to three parameters which adds them to a special data structure called topics which can be used to create filters in webJS libraries to listen to events.
  3. Parameters without indexed atrtribute are ABI Encoded and stored in logs.
  4. Note that the hash of the event is already one of topics. So if you declared the event as anonymous, then a field is left free and you can have total four indexed parameters.
  5. But the catch is that anonymous events can’t be filtered easily, you need to know the contract address to listen to anonymous events.

Purpose of Events?

Constructors :

  1. Constructors of a contract run before deployment of the contract. The contract deployment cost rises linearly with size of the code. But note that constructor code and internal functions code used only in constructor is not billed in gas.

  2. State variables are initialized to default values even before running the constructor.

  3. A contract needs to implement all it’s parent contract’s constructor compulsorily. Else it is declared abstract.

  4. 2 ways to do that,

    • Specify in the inheritance list itself. Better suited when args are constants

    • // SPDX-License-Identifier: GPL-3.0
      pragma solidity >=0.7.0 <0.9.0;
           
      contract Base {
          uint x;
          constructor(uint _x) { x = _x; }
      }
           
      // Either directly specify in the inheritance list...
      contract Derived1 is Base(7) {
          constructor() {}
      }
      
    • Specify in the modifiers of constructor. Used when args are actually args to child constructors

      // SPDX-License-Identifier: GPL-3.0
      pragma solidity >=0.7.0 <0.9.0;
           
      contract Base {
          uint x;
          constructor(uint _x) { x = _x; }
      }
           
           
      contract Derived2 is Base {
          constructor(uint _y) Base(_y * _y) {}
      }
      
  5. Inheritance is linearized see C3 Linearization below . Which means always specify the inheritance directive contract child is grandParent, Parent like that. Doing something like contract child is Parent, grandParent does not work. Simply put, **List highest parents first in inheritance tree. TOP to BOTTOM becomes **

    contract child is Top1, Top2, Top3, oneLevelAboveChild .

  6. Note Parent constructors are run not in modifier specified way. They always run in the same linearized manner. See below

    contract Base1 {
        constructor() {}
    }
       
    contract Base2 {
        constructor() {}
    }
       
    // Constructors are executed in the following order:
    //  1 - Base1
    //  2 - Base2
    //  3 - Derived1
    contract Derived1 is Base1, Base2 {
        constructor() Base1() Base2() {}
    }
       
    // Constructors are executed in the following order:
    //  1 - Base2
    //  2 - Base1
    //  3 - Derived2
    contract Derived2 is Base2, Base1 {
        constructor() Base2() Base1() {} // Modifier order does not 																				// matter. Follows linearised 																			//order
    }
       
    // Constructors are still executed in the following order:
    //  1 - Base2
    //  2 - Base1
    //  3 - Derived3
    contract Derived3 is Base2, Base1 {
        constructor() Base1() Base2() {} // Same here
    }
    
  7. Becuase Base1 and Base2 don’t have any inheritance relation, the linearization order is the order we have given in the is <> directive **. Hence constructors also run in that order i.e **linearised order. Had they been related in a inheritance relation, we need to make sure out is <> directive is Most Base like to most child like and constructors run in that order.

C3 Linearization :

  1. Very important check this. Note that as stated above, parent constructors are called in order of left to right of is directive. The below discussion is how super changes based on linearisation.
contract Granny {
    event Say(string, string);

    constructor() public {
        emit Say('I am Granny and I am cool!', 'no one');
    }

   function getName() public returns (string memory) {
        return 'Granny';
    }
}

contract Mommy is Granny {
    constructor() public {
        emit Say('I am Mommy and I call ', super.getName());
    }

    function getName() public returns (string memory) {
        return 'Mommy';
    }
}

contract Daddy is Granny {
    constructor() public {
        emit Say('I am Daddy and I call ', super.getName());
    }

    function getName() public returns (string memory) {
        return 'Daddy';
    }
}

contract Kiddo is Mommy, Daddy {
    constructor() public {
        emit Say('I am Kiddo and I call ', super.getName());
    }
}

The output becomes :

I am Granny and I am cool
I am Mommy and I call Granny
I am Daddy and I call Mommy // What in the world happened here?
I am Kiddo and I call Daddy
Magic? No, it’s C3 Linearization :
So, When a child function calls super.foo(), solidity searches for it in parents one level above in the order of C3 Linearisation.

Inheritance :

  1. If a contract inherits from multiple contracts and both those contracts inherit from same base contract (google diamond pattern), then only one copy of top most contract is created. See below. Similar to Virtual Inheritance from C++.
contract Owned {
	string name = "John"
}

contract Destructible is Owned {
	// string name = "Something"; not allowed as it shadows
	constructor(){
		name = "Joseph" // This directly points to parent's name
	}
}

contract Named is Owned, Destructible { // Inherits only a single 'Owned', not twice.
   
}

A case analysis :

  1. A contract has defined some functions as virtual. Another contract inherits from it.

    • This is the case with contracts like ERC20. They define some functionality and let children extend that functionality. Note that the extension is a Choice.
    • For example, the transfer function of ERC20 is virtual. Which means you can implement your own transfer function using override, while also using openzeppelin’s code by just saying super.transfer().
  2. A contract has declared some functions as virtual but did not define them.

    • This makes the above contract abstract. Any child inheriting them can’t be instantiated without defining that functionality.
    • Contrast to above, this isn’t a choice, it’s a burden on the child contract.
    • But why? Consider a case where there is an interface (more on interface later) or abstract contract called willDoSomething.
    • This tells the compiler/guarantees the user that contracts inheriting willDoSomething will actually do something.
    • This can be used to make sure that A standard is adhered and implemented. Hence the word IERC20 in libraries. IERC20 is the interface telling us the rules of the ERC20 standard. The interface itself IERC20 does not ** implement anything. It **Forces inheriting members to complete the functionality so that standards are met.
    • A little gotcha, abstract contracts cannot cannot override an implemented virtual function with an unimplemented one. In simple words, you can enforce your children to write/define an implementation, but can’t deny them the functionality passed from above.
  3. An abstract contract or interface is defined/imported and not inherited. Used as a variable inside a contract

    • This is the case when you want to assure compiler that it need not throw undefined errors.
    • Consider a case where your contract wanted to use Uniswap contracts inside your contract. How do you tell your compiler that Uniswap docs guarantee this functionality, so I’m gonna use it?
    • This is where interface comes in. See below :
    import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
       
    contract swapExample {
      ISwapRouter public immutable swapRouter;
       
      constructor(ISwapRouter _swapRouter) {
              swapRouter = _swapRouter;
          }
             
             
      function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) 
      {
        ISwapRouter.ExactInputSingleParams memory params = // Using interface's struct
        ISwapRouter.ExactInputSingleParams({
        tokenIn: DAI,
        tokenOut: WETH9,
        fee: poolFee,
        recipient: msg.sender,
        deadline: block.timestamp,
        amountIn: amountIn,
        amountOutMinimum: 0,
        sqrtPriceLimitX96: 0
        });
       
        // The call to `exactInputSingle` executes the swap.
        amountOut = swapRouter.exactInputSingle(params); // Using interface's function
       }
    }
       
    
    • Uniswap has developed and deployed their SwapRouter contract somewhere else but your compiler needs to have a reference to check what functions and structs/vars it supports or not.
    • For that purpose, uniswap has released an Interface so that you can import it and make the compiler not yell at you while also getting typed reference.

Interface vs abstract contract :

  1. Both are used to force reference implementation of children as told above.
  2. Differences :
    • abstract contract is still a contract. It can **have state variables, inherit from other contracts, have a contructor and even define some modifiers ** . All that a normal contract can do, just instantiation can’t be done.
    • interface cannot do all that. interface purely is a template forcing inheriting children to implement it. So just a template, nothing like a contract. So it cannot have state, cannot define ANY function implemented, cannot have a contructor, cannot declare modifiers and all functions must be virtual and external although virtual keyword is implicit.
    • But the template extenstion is still possible. abstract contracts can inherit whatever they want and interfaces can only inherit other interfaces.

Function Overriding :

  1. Functions in base contracts need to specify that they can be overriden by child functions using the virtual modifier.
  2. Functions in child contracts need to specify which functions they are overriding by using the same signature as base contract function. I.e same name and return type, and also need to specify the override modifier.

  3. You can also always explicitly call a function of a contract in the inheritance path by specifying which contract you are referring to twoLevelsUpParent.foo().
  4. Override needs to specify which contract’s functions you are overriding when multiple inheritance is in play. See below
contract A {
    uint public x;
    function setValue(uint _x) public virtual {
        x = _x;
    }
}

contract B {
    uint public y;
    function setValue(uint _y) public virtual {
        y = _y;
    }
}

contract C is A, B {
    function setValue(uint _x) public override(A,B) {
        A.setValue(_x);
    }
}
  1. Functions can be both virtual and override at the same time. So they can implement/extend some functionality via override and also force children to extend this functionality through virtual.
  2. Note that you can’t override parent’s external function and then try something like super.this.f(). Simply put, you can’t call parent’s external functions if you’ve already overriden them in the child.

Payable :

Receiving Ether :

Sending Ether :

You can send ether with 3 methods :

  1. address.transfer(etherAmount) : Tries to send etherAmount to an address. If balance of contract is less than etherAmount or runs out of gas, the transactions is reverted and exception is thrown. Sends only 2300 gas.
  2. address.send(etherAmount) : Tries to send etherAmount to an address. If fails, returns false, does NOT throw an exception. So always check the return values here. Sends only 2300 gas.
  3. address.call{gas : gasForwarded, value : etherAmount}(payload) : The payload is an encoded string, trying to call a contract. Returns true if successful else false. Note that call also returns the return data of a callee if it’s a contract. Sends specified gas, if not specified all is transferred.

Delegatecall :

  1. Syntax is
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

// NOTE: Deploy this contract first
contract B {
    // NOTE: storage layout must be the same as contract A
    uint public num;
    address public sender;
    uint public value;

    function setVars(uint _num) public payable {
        num = _num;
        sender = msg.sender;
        value = msg.value;
    }
}

contract A {
    uint public num;
    address public sender;
    uint public value;

    function setVars(address _contract, uint _num) public payable {
        // A's storage is set, B is not modified.
        (bool success, bytes memory data) = _contract.delegatecall(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
    }
}
  1. Executes the code of contract B with the storage of contract A. This functionality is used mostly in upgradeable contracts.
  2. Retains and forwards the msg.sender and msg.value . this in external contact/library refers to callee’s context.
  3. When an EVM call occurs for external functions(delegatecall), storage references are not copied as delegatecall shares the state. But memory objects are copied and sent as memory is not shared in delegatecall.

Libraries :

  1. They are pre deployed code which can be used without increasing the contract code size.
  2. If all the functions of library are internal, then the library is included in the contract code. So there’s no delegatecall if all functions are internal, just a JUMP is used.
  3. Only if the library contains external functions, ** then it needs to be **linked during deployment and comes the delegatecall.
  4. Also, library functions not modifying state i.e view/pure can be called directly. Makes sense, no need of delegatecall when no state changes.
  5. For example, if we use a library called Math and Heap which have external functions, then we need to link it during compile time with name and deployed address :
solc --libraries "file.sol:Math=0x1234567890123456789012345678901234567890 file.sol:Heap=0xabCD567890123456789012345678901234567890"

Global Units :

assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 ether == 1e18);
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days

Contract Meta :

ABI Usage :