Solidity contract smart contract overview

Contracts in Solidity are similar to classes in object-oriented languages. They contain persistent data in state variables, and functions that can modify these variables. Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables in the calling contract are inaccessible. A contract and its functions need to be called for anything to happen. There is no “cron” concept in Ethereum to call a function at a particular event automatically. In solidity Contract is similar to The concept of classes in object-oriented languages. A contract contains state variables, used to store data, and functions, used to modify these variables. When different contract instances call a function, an EVM function call will be executed. In this way, after the context is switched, the state variables cannot access each other in the process of calling the contract. The contract and the function of the contract need to be called to run. In Ethereum, there is the concept of cron, which is the way of automatic calling of scheduled tasks.

Visibility and Getters 

State Variable Visibility

public

internal

private

Function Visibility

external

External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f() does not work, but this.f() works).

public 

Public functions are part of the contract interface and can be either called internally or via message calls.

internal

private

Function Modifiers

Modifiers can be used to change the behavior of functions in a declarative way. For example, you can use a modifier to automatically check a condition prior to executing the function.

Constant and Immutable State Variables

State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time.

When constant is compiled, the value of the variable has been solidified and cannot be changed. Immutable is solidified after the contract is created, and can still be modified in the constructor of the contract.

Functions

Functions can be declared view in which case they promise not to modify the state.

Functions can be declared pure in which case they promise not to read from or modify the state.

Fallback Function

A contract can have at most one fallback function, declared using either fallback () external [payable] or fallback (bytes calldata input) external [payable] returns (bytes memory output) (both without the function keyword). This function must have external visibility. A fallback function can be virtual, can override and can have modifiers.

A smart contract has at most one fallback function, declared fallback () external [payable] 或者fallback (bytes calldata input) external [payable] returns (bytes memory output) , neither of which has the function keyword. The callback function must be an external visible attribute. A callback function can be virtual, can be rewritten, and can be modified with modifiers.

contract TestPayable { 
    uint x; 
    uint y; // This function is called for all messages sent to 
    // this contract, except plain Ether transfers 
    // (there is no other function except the receive function). 
    // Any call with non- empty calldata to this contract will execute 
    // the fallback function (even if Ether is sent along with the call). 
    // Any non-empty calldata call will trigger this callback function. 
    // If there are parameters inside the call, the fallback will be called , without parameters, call receive.fallback 
    () external payable { x = 1; y = msg.value; } // This function is called for plain Ether transfers, ie 
    // for every call with empty calldata. 
    // every The call of empty calldata will call this receive method.
    

    
    receive() external payable { x = 2; y = msg.value; }
}

 function callTestPayable(TestPayable test) public returns (bool) {
        (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1 and test.y becoming 0.
        (success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()"));
        require(success);
        // results in test.x becoming == 1 and test.y becoming 1.

        // If someone sends Ether to that contract, the receive function in TestPayable will be called.
        // Since that function writes to storage, it takes more gas than is available with a
        // simple ``send`` or ``transfer``. Because of that, we have to use a low-level call.
        (success,) = address(test).call{value: 2 ether}("");
        require(success);
        // results in test.x becoming == 2 and test.y becoming 2 ether.

        return true;
    }

Inheritance

Solidity supports multiple inheritance including polymorphism. Solidity supports multiple inheritance and multi-modality.

Polymorphism means that a function call (internal and external) always executes the function of the same name (and parameter types) in the most derived contract in the inheritance hierarchy. This has to be explicitly enabled on each function in the hierarchy using the virtual and override keywords. See Function Overriding for more details.

Polymorphism means that a function call (both internal and external) always executes the function of the same name (and parameter types) in the most derived contract in the inheritance hierarchy. This feature must be enabled explicitly on every function in the hierarchy using the virtual and override keywords. See Function Overriding for more details.

When a contractis from other contracts, only a single contract is created on the blockchain, and the code from all the box compiDSE is computer, Contract. This Means that all interzal calls to function ( super.f(..) will use JUMP and not a message call). When a contract inherits from other contracts, only one contract will be created on the chain, and all base contract codes will be compiled into the newly created contract. means that all internal calls to functions of the base contract will use internal function calls. (super.f(..) will use JUMP, not message calls).

Multiple inheritance example:

contract PriceFeed is Owned, Destructible, Named("GoldFeed") {
    function updateInfo(uint newInfo) public {
        if (msg.sender == owner) info = newInfo;
    }

    // Here, we only specify `override` and not `virtual`.
    // This means that contracts deriving from `PriceFeed`
    // cannot change the behavior of `destroy` anymore.
    function destroy() public override(Destructible, Named) { Named.destroy(); }
    function get() public view returns(uint r) { return info; }

    uint info;
}

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract owned {
    constructor() { owner = payable(msg.sender); }
    address payable owner;
}

contract Destructible is owned {
    function destroy() virtual public {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

contract Base1 is Destructible {
    function destroy() public virtual override { /* do cleanup 1 */ super.destroy(); }
}


contract Base2 is Destructible {
    function destroy() public virtual override { /* do cleanup 2 */ super.destroy(); }
}

contract Final is Base1, Base2 {
    function destroy() public override(Base1, Base2) { super.destroy(); }
}

If Base2 calls a function of super, it does not simply call this function on one of its base contracts. Rather, it calls this function on the next base contract in the final inheritance graph, so it will call Base1.destroy() (note that the final inheritance sequence is – starting with the most derived contract: Final, Base2, Base1, Destructible, owned). 

Base2 calls super, not simply calling a certain function in the base class. It calls the method of the base class next to the final inheritance tree, so it calls Base1.destory(). Note: The final inheritance sequence is: Final ->Base2 - > Base1 -> Destructible -> owned.

Function Overriding function rewriting

The overriding function may only change the visibility of the overridden function from  external to  public. The overriding function may only change the visibility of the overridden function. For example, from external to public.

The mutability may be changed to a more strict one following the order: nonpayable can be overridden by view and pureview can be overridden by purepayable is an exception and cannot be changed to any other mutability.

Mutability can be changed to stricter mutability in the following order: nonpayable can be overridden by view, pure, view can be overridden by pure. payable is an exception and cannot be overridden by any mutability.

Arguments for Base Constructors

// 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() {}
}

// or through a "modifier" of the derived constructor.
contract Derived2 is Base {
    constructor(uint _y) Base(_y * _y) {}
}

Multiple Inheritance and Linearization (Multiple inheritance and linearization, from right to left, python is from left to right.)

Another simplifying way to explain this is that when a function is called that is defined multiple times in different contracts, the given bases are searched from right to left (left to right in Python) in a depth-first manner, stopping at the first match . If a base contract has already been searched, it is skipped. Another simple way to explain this is: In multiple inheritance, when calling a function defined in multiple different contracts, the search order for base class functions It is from right to left in a depth-first manner (left to right in python). If a contract has already been searched, the search will be interrupted and popped up.

Note the order of constructor calls:

// SP
DX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

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() {}
}

// Constructors are still executed in the following order:
//  1 - Base2
//  2 - Base1
//  3 - Derived3
contract Derived3 is Base2, Base1 {
    constructor() Base1() Base2() {}
}

Abstract Contracts abstract contracts

Contracts need to be marked as abstract when at least one of their functions is not implemented.

A contract must be marked as abstract when at least one function is not implemented.

Contracts may be marked as abstract even though all functions are implemented.

A contract may be marked as abstract even if all functions are implemented.

Such abstract contracts can not be instantiated directly. This is also true, if an abstract contract itself does implement all defined functions.

If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it needs to be marked as abstract as well.

If a contract inherits an abstract contract and does not implement all abstract functions, the contract must also be marked as abstract.

An abstract contract cannot use an abstract function to override an implemented function.

Interfaces

  • They cannot inherit from other contracts, but they can inherit from other interfaces.

  • All declared functions must be external. All declared functions must be external.

  • They cannot declare a constructor. They cannot declare a constructor.

  • They cannot declare state variables. They cannot declare state variables.                                                                       

    All functions declared in interfaces are implicitly virtual, which means that they can be overridden. This does not automatically mean that an overriding function can be overridden again - this is only possible if the overriding function is marked virtual.       

    All functions defined in the interface are implicitly decorated with virtual, which means they can be overridden. This does not mean automatic, the rewriting function can be rewritten automatically, it must be decorated with virtual.                   

Libraries

Libraries are similar to contracts, but their purpose is that they are deployed only once at a specific address and their code is reused using the  DELEGATECALL ( CALLCODE until Homestead) feature of the EVM. Libraries and contracts are very similar, they are deployed at a specific address Above, their code is invoked and reused through EVM's feature delegatecall or callcode. The execution environment of liberies is the execution environment of the calling contract.

 Library functions can only be called directly Only when the libereries function is view or pure, it can be called directly. Lib functions that are not allowed to modify the state are called directly and must be called through delegatecall.

Libraries can be seen as implicit base contracts of the contracts that use them. They will not be explicitly visible in the inheritance hierarchy, but calls to library functions look just like calls to functions of explicit base contracts (using qualified access like L.f()). Of course, calls to internal functions use the internal calling convention, which means that all internal types can be passed and types stored in memory will be passed by reference and not copied. To realize this in the EVM, code of internal library functions and all functions called from therein will at compile time be included in the calling contract, and a regular JUMP call will be used instead of a DELEGATECALL.

Libraries can be regarded as an implicit base class inheritance contract, but lib will not appear in the inheritance hierarchy, but the function calling lib is the same as calling the function of the base class contract (the correct access should be Lf()) . Of course, to call an internal function, use the convention of calling an internal function. convention Convention. It means that all passed internal types are stored in memory and can be passed by reference instead of value copy. In order for EVM to implement this function, the internal function code of lib and the parameters called there will be included in the calling contract (calling contract) during compilation, and a regular JUMP will replace DELEGATECALL.

As the compiler does not know the address where the library will be deployed, the compiled hex code will contain placeholders of the form __$30bbc0abd4d6364515865950d3e0d10953$__

Because the compiler does not know the address where the library will be deployed, all compiled hexadecimal code will contain a placeholder symbol: __$30bbc0abd4d6364515865950d3e0d10953$__  (是libraries/bigint.sol: hash format of BigInt.)

Such bytecode is incomplete and should not be deployed. Placeholders need to be replaced with actual addresses. You can do that by either passing them to the compiler when the library is being compiled or by using the linker to update an already compiled binary. See  Library Linking  for information on how to use the commandline compiler for linking. Such byte code is incomplete and should not be deployed. Placeholders need to be replaced with real addresses. You can pass the real address to the compiler while the library is being compiled, or use the linker to update an already compiled bytecode to link to the real address.

In comparison to contracts, libraries are restricted in the following ways:

  • they cannot have state variables

  • they cannot inherit nor be inherited

  • they cannot receive Ether

  • they cannot be destroyed

(These might be lifted at a later point.) These restrictions may be lifted in the future.

Function Signatures and Selectors in Libraries

Similar to the contract ABI, the selector consists of the first four bytes of the Keccak256-hash of the signature.

Call Protection For Libraries

As mentioned in the introduction, if a library’s code is executed using a CALL instead of a DELEGATECALL or CALLCODE, it will revert unless a view or pure function is called.

Only view and pure library functions can be called by call, and others cannot, otherwise they will be reverted.

DelegateCall and callcode can call all library functions.

The EVM does not provide a direct way for a contract to detect whether it was called using CALL or not, but a contract can use the ADDRESS opcode to find out “where” it is currently running. The generated code compares this address to the address used at construction time to determine the mode of calling.

The EVM does not provide a direct way for contracts to identify whether a contract was called with a call or not. But a contract can use the ADDRESS opcode to identify where it is currently running. The generated code will compare this.address with the address in the constructor to identify the contract operation mode.

More specifically, the runtime code of a library always starts with a push instruction, which is a zero of 20 bytes at compilation time. When the deploy code runs, this constant is replaced in memory by the current address and this modified code is stored in the contract. At runtime, this causes the deploy time address to be the first constant to be pushed onto the stack and the dispatcher code compares the current address against this constant for any non-view and non-pure function.

This means that the actual code stored on chain for a library is different from the code reported by the compiler as deployedBytecode.

More specifically, the library runtime code always starts with a push structure (20-byte structure, all 0 during compilation), and when deployment starts, this 20-byte constant will be replaced by the current address And this modified code is stored in the contract. During runtime, the address during deployment becomes a priority constant and will be pushed to the stack, and the dispatcher code will compare the current address with the constant address for each non-view and non-pure method.

This means that the actual code stored on the library chain is different from what the compiler reports as deployedBytecode.

Using For

The directive using A for B; can be used to attach library functions (from the library A) to any type (B) in the context of a contract. These functions will receive the object they are called on as their first parameter (like the self variable in Python).

Using using A for B directly, you can assign all the functions of library A to contract B. These library functions will take B as their first parameter.

The effect of using A for *; is that the functions from the library A are attached to any type.

using A for *, attaches library A functions to all types.

The using A for B; directive is active only within the current contract, including within all of its functions, and has no effect outside of the contract in which it is used. The directive may only be used inside a contract, not inside any of its functions.

The using A for B command is only valid within the contract, including all functions within the contract, and has no effect on the outside of the contract. This instruction can only be written in the contract, not in the function.

Note that all external library calls are actual EVM function calls. This means that if you pass memory or value types, a copy will be performed, even of the self variable. The only situation where no copy will be performed is when storage reference variables are used or when internal library functions are called.

Note that all external library calls are actual EVM function calls. This means that if a memory or value type is passed, a copy will be performed, even of arguments. The only cases where no copy is performed are when using stored reference variables or calling internal library functions.

Calling external functions of the library, except for the storage type, is all value copying. Calls to library internal functions are all references by value.

Inline Assembly

Inline assembly is a way to access the Ethereum Virtual Machine at a low level. This bypasses several important safety features and checks of Solidity. This bypass is very important Solidity's security features and security checks. You should only use it for tasks that need it, and only if you are confident with using it. You should only use it when the task really needs it, and you are sure to use it.

An inline assembly block is marked by assembly { ... }

Guess you like

Origin blog.csdn.net/gridlayout/article/details/131268868