Solidity and smart contracts

1, Introduction to Solidity

1. Introduction

solidity is a contract oriented high-level programming language created to realize smart contracts, which can run on Ethereum virtual machine (EVM). It is a static language. The types included include not only the standard types in common programming languages, but also the unique types of Ethereum such as address. solidity source files are usually written in sol as the extension.

2. Language characteristics

Its syntax is close to Javascript and is an object-oriented language. However, as a decentralized contract running on the network in a real sense, it has many differences. Here are some:

  • The underlying layer of Ethereum is based on account, not UTXO, so there is a special type of Address. The code used to locate the user, locate the contract and locate the contract (the contract itself is also an account).
  • Because the language embedded framework supports payment, it provides some keywords, such as payable, which can directly support payment at the language level, and is super simple.
  • The storage uses the blockchain on the network, and each state of the data can be permanently stored. Therefore, it is necessary to determine whether the variable uses memory or blockchain.
  • The running environment is on a decentralized network, which will emphasize the calling mode of contract or function execution. Because a simple function call turns into code execution in a node on the network, which feels distributed.
  • The last big difference is its exception mechanism. Once an exception occurs, all execution will be withdrawn. This is mainly to ensure the atomicity of contract execution and avoid inconsistent data in the intermediate state.

3. Compiler

At present, the best way to program solidness is to use Remix (address: https://remix.ethereum.org/ ), this is a web browser based IDE, which can be used online without installing anything. You can write a solid smart contract, and then compile, deploy and run the smart contract.

2, Solid source code and smart contract

The following steps are required for the source code of Solidity to become a workable smart contract:

1. The source code is compiled into Bytecode and binary interface specification (ABI) will be generated at the same time;

2. Deploy bytecode to Ethereum network through transaction, and a smart contract account will be generated if the deployment is successful;

3. Via Web3 Call the ABI + JS function in the intelligent contract to read the data.

3, Solidity value type

1. bool

Possible values are constant values true and false.

2. Integer (int/uint)

Represents signed or unsigned integers, respectively. Support from uint8 to uint256 and int8 to int256. Uint and int represent uint256 and int256 by default. The step supported by the variable is incremented by 8.

3. Floating point type (fixed/ufixed)

Represents a signed or unsigned floating-point type, respectively.

4. address

Store a 20 byte value (the size of the Ethereum address). The address type also has member variables and serves as the basis of the contract.

  • Balance (uint256) queries the Ethernet balance of the address, with Wei as the unit
  • transfer (uint256) sends a specified number of ethers to this address, with Wei as the unit. An exception is thrown when it fails
  • send (uint256) sends a specified number of etheric coins to the address, with Wei as the unit. If it fails, it returns false
pragma solidity ^0.4.0;

contract sendMoney{
    
    //The payable keyword means that we can recharge and transfer our contract address through this function.
    function pay() payable{
        
    }
    
    //Get the amount on the contract account
    function getBalance() view returns(uint){
        return this.balance;
    }
    
    //Contract address
    function getThis() view returns(address){
        return this;
    }
    
    //Get the amount of any account address
    function getRandomBanlance(address account) view returns(uint){
        // address account = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
        return account.balance;
    }
    
    //Transfer to designated account
    //You can directly enter account transfer(10 ether); On behalf of the account transfer
    //If there is no operation in the function, but there is payable attribute, MSG The value of value will be transferred to the contract account address
    //If the transfer amount is greater than 10 (eg:20), the remaining 10 will be transferred to the contract account and divided between the two accounts
    function transfer() payable{
        address account = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
        account.transfer(10 ether);
    }
    
    //Transfer to the contract account must have a rollback function
    function transfer2() payable{
        this.transfer(msg.value);
    }
    
    //Rollback function, no function name and return value
    function () payable{
        
    }
}

5. byte arrays

bytes1,..., bytes32, the allowable value is incremented by step 1. Byte means byte1 by default.

6. Enumeration (enum)

A user-defined type. The displayed conversion can be converted with integers. It is incremented from 0 by default. It is generally used to simulate the state of the contract. Enumeration type should have at least one member.

pragma solidity ^0.4.0;

contract enumTest{
    
    
    //enum must have members
    //Chinese characters are not allowed
    //Cannot add;
    // enum girl{}
    
    enum girl{a,b,c} //0,1,2...
    
    girl dateGirl = girl.a;
    
    function getEnum() view returns(girl){
        return girl.b;
    }
    
    //Purpose: to mark the transfer of status
    function first() returns(string){
        require(dateGirl == girl.a);
        dateGirl = girl.b;
        return "date with a";
    } 
    
    function second() returns(string){
        require(dateGirl == girl.b);
        return "date with b";
    }
    
}

7. function

4, Solid reference type

Array

Struct ure

pragma solidity ^0.4.0;

contract structTest{
    
    //Definition of structure
    struct student{
        
        uint grade;
        string name;
        
        mapping(uint => string) map;
    }
    
    //Definition of structure
    struct student2{
        
        uint grade;
        string name;
        // student2 stu;  The structure cannot contain itself, but it can be a dynamic length array or a mapping
        
        student2[] stu;
        mapping(uint => student2) map;
    }
    
    student a; //The default is storage type, which can only be used to operate the mapping type in our structure;
    
    //Initialization of structure
    function init() view returns(uint,string,string){
        //1. Ignore the mapping type when initializing the structure
        student memory s = student(100,"zhang");
        
        //2. The memory object cannot directly operate mapping in the struct structure
        // s.map[0] = "hi";
        //Assign the s object in memory to the a object
        a = s;
        //The mapping attribute in our structure can only be manipulated through the storage object
        a.map[0] = "hi";
        
        return(s.grade,s.name,a.map[0]);
    }
    
    
    //Initialization of structure 2
    function init2() view returns(uint,string){
        student memory s = student({grade:100,name:"zhang"});
        return(s.grade,s.name);
    }
    
    //internal
    function test(student s) internal{
        
        //memory cannot be assigned to storage
        // student stu = s;
        
    }
}




pragma solidity ^0.4.0;

contract struct1{
    
    struct student{
        uint grade;
        string name;
    }
    
    student stu;
    
    //The reference represents that the value on the blockchain will be modified
    function test(student storage s) internal{
        student storage a = s;
        a.name = "zhang";
    }
    
    function call() returns(string){
        test(stu);
        
        return stu.name;
    }
    
    
    
}
pragma solidity ^0.4.0;

contract struct2{
    
    struct student{
        uint grade;
        string name;
    }
    
    student stu;
    
    //The parameter passed a pointer reference
    function test(student memory s) internal{

        //Assign the value of s to stu on the blockchain
        stu = s;
        //Modifying the s of the function parameter only modifies the space in its memory, not the space on the blockchain, because it is two completely independent spaces
        s.name = "zhang";
    }
    
    function call() returns(string){
        //Open up space in memory
        student memory tmp = student(100,"tmp");
        
        test(tmp);
        
        return stu.name;
    }
    
}
pragma solidity ^0.4.0;

contract struct3{
    
    struct student{
        uint grade;
        string name;
    }
    
    student stu = student(100,"stu");
    
    //The s parameter is a reference
    function test(student storage s) internal{
        //A is a copy in memory. It copies the contents of stu referenced by s to a, an object in memory
        student memory a = s;

        //Trying to change the value of a will not change the value of stu because it belongs to two different spaces
        a.name = "zhang";
    }
    
    function call() returns(string){
        test(stu);
        
        return stu.name;
    }
    
    
    
}

Mapping

pragma solidity ^0.4.0;

contract Mapping{
    
    mapping(address => uint) idMapping;  // addr ==>id
    mapping(uint => string) nameMapping; // id ==> name
    
    uint public num = 0;
    
    function register(string name){
        
        address account = msg.sender;
        
        num++;
        idMapping[account] = num;
        nameMapping[num] = name;
        
    }
    
    function getIdByAddress(address addr) returns(uint){
        return idMapping[addr];
    }
    
    function getNameById(uint id) returns(string){
        return nameMapping[id];
    }  
}

5, Solid data location

All complex types (array, structure, mapping) have an additional attribute, which is the data location, to indicate whether the data is stored in memory or storage.

Depending on the context, the data has a default location most of the time, but it can also be modified by adding the keyword memory or storage after the type name.

The data location of function parameters (including returned parameters) is memory by default, the local variable is storage by default, and the status variable is storage by default.

6, Solidity function

1. Function visibility / access rights

  • Public: the public function is a part of the contract interface and can be called internally or through messages. For public state variables, a getter function is automatically generated
  • Private: the private function and state variable are used in the contract that currently defines them and cannot be used by derived contracts
  • The external:external function is part of the contract interface and can be invoked from other contracts and transactions. An external function f cannot be called internally (that is, f does not work, but this.f() can). External functions are sometimes more efficient when a large amount of data is received
  • Internal: internal functions and state variables can only be accessed internally (i.e. from within the current contract or from a contract derived from it), and cannot be called with this

2. Function state variability

  • view: status cannot be modified
  • Pure: a pure function. It is not allowed to access or modify the state
  • payable: allows you to receive Ethernet from a message call
  • constant: the same as view. Generally, it only modifies the state variable and does not allow assignment (except initialization)

3. Fallback function

fallback function is a special function in the contract: no name, no parameter, no return value;

If no other function matches the given function identifier (or no call data is provided) in the call of a contract, the function will be executed;

Every time the contract receives ether (no data), the fallback function is executed. In addition, in order to receive Ethernet, the fallback function must be marked payable. If such a function does not exist, the contract cannot receive Ethernet through regular transactions;

In the context, there are usually very few gas that can be used to complete the call of fallback function, so it is important to make the call of fallback function as cheap as possible.

7, Solid event

Events are the easy log infrastructure provided by Ethereum EVM. Events can be used to record operations, store them as logs, and realize some interactive functions.

When the defined event is triggered, we can store the event in the EVM transaction log, which is a special data structure in the blockchain; The log is associated with the contract, merged with the storage of the contract and stored in the blockchain; As long as a block can be accessed, its related logs can be accessed, but logs and event data cannot be accessed directly in the contract.

Simple payment verification SPV can be realized through log. If an external entity provides a contract with such proof, it can check whether the log really exists in the blockchain.

At most three parameters can be set to indexed to set whether to be indexed. When set to index, you can use this parameter to find logs, and even filter by specific values.

		event Transfer(address indexed from,address indexed to,uint256 value);

		.......

		//Transfer, internal call
    function _transfer(address _from,address _to,uint _value)internal{
        ......
        
        emit Transfer(_from,_to,_value);
        //assert requirement must be true
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }

8, Solid exception handling

Solid uses state recovery exceptions to handle exceptions. Such an exception will revoke all changes to the state in the current call and all of its subcalls and return the error to the caller.

The functions assert and require can be used to judge the condition and throw an exception when the condition is not met.

assert() is generally only used to test for internal errors and check constants.

		//Transfer, internal call
    function _transfer(address _from,address _to,uint _value)internal{
        ......
        
        //assert must be true (balance of transfer out address is required)
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }

require() should be used to ensure that valid conditions (such as input or contract status variables) are met, or to verify the return value of calling an external contract.

		//Transfer, internal call
    function _transfer(address _from,address _to,uint _value)internal{
        //Transfer to address 0 is prohibited
        require(_to != 0x0);
        
        require(balanceOf[_from]>=_value);
        //Prevent overflow
        require(balanceOf[_to]+_value >= balanceOf[_to]);
        ......
    }

revert() is used to throw exceptions, which can mark an error and return the current call.

9, Example contract

1. Crowdfunding contract

pragma solidity ^0.4.0;

//Completed all operations from creating crowdfunding events, donation and withdrawal

contract crowdFunding{
    
    struct funder{
        address fundaddress;//Donor's address
        uint Tomoney;//Money donated by donors
    }
    
    
    struct needer{
       address Neederaddress;//Address of beneficiary
       uint goal;//Target value of beneficiary
       uint amount;//How much money has the current raised
       uint funderAccount;//Donor id
       mapping(uint => funder) map;//Bind the id of the donor to the donor so that you can know who donated money to the current beneficiary
    } 
    
    uint neederAmount;//id number of beneficiaries
    mapping(uint => needer) needmap;//Bind the id of the beneficiary with the beneficiary, so that the beneficiary can be managed

    
    //Realize a crowdfunding event
    function NewNeeder(address _Neederaddress,uint _goal){
        //Bind beneficiary id to beneficiary
        neederAmount++;
        needmap[neederAmount] = needer(_Neederaddress,_goal,0,0);
    }
    
    //Address of the donor, id of the beneficiary
    function contribute(address _address,uint _neederAmount) payable{
        needer storage _needer = needmap[_neederAmount];
        //Increased funds raised
        _needer.amount += msg.value;
        //Increase in donations
        _needer.funderAccount++;
        //Bind beneficiary id to beneficiary
        _needer.map[_needer.funderAccount] = funder(_address,msg.value);
    }
    
    //When the funds raised meet the conditions, the transfer will be made to the address of the beneficiary
    //Beneficiary's id
    function isComplete(uint _neederAmount){
        needer storage _needer = needmap[_neederAmount];
        
        if(_needer.amount >= _needer.goal){
            _needer.Neederaddress.transfer(_needer.amount);
        }
    }
    
    function test() view returns(uint,uint,uint){
        return(needmap[1].goal,needmap[1].amount,needmap[1].funderAccount);
    }  
}

2. Token contract

pragma solidity ^0.4.16;

//Interface definition
interface tokenRecipient{
    function receiveApproval(address _from,uint256 _value,address _token,bytes _extraData) external;
}

contract TokenERC20{
    
    string public name;//Name of token
    string public symbol;//The symbol of token, that is, the abbreviation of token
    uint8 public decimals = 18;//Support several decimal places    
    uint256 public totalSupply;//The total amount of tokens issued. The total amount of tokens issued by all smart contracts is certain
    
    mapping(address => uint256) public balanceOf;//Enter an address to get the balance of the token at that address
    mapping(address => mapping(address => uint256)) public allowance;//Allow others to transfer to others in your name
    
    //event
    event Transfer(address indexed from,address indexed to,uint256 value);
    
    event Approval(address indexed _owner,address indexed _spender,uint256 _value);
    
    event Burn(address indexed from,uint256 value);
    
    //Constructor
    function constructor(uint256 initialSupply,string tokenName,string tokenSymbol) public {
        totalSupply = initialSupply * 10 ** uint256(decimals);
        
        balanceOf[msg.sender] = totalSupply;
        
        name = tokenName;
        
        symbol = tokenSymbol;
    }
    //Transfer, internal call
    function _transfer(address _from,address _to,uint _value)internal{
        //Transfer to address 0 is prohibited
        require(_to != 0x0);
        
        require(balanceOf[_from]>=_value);
        //Prevent overflow
        require(balanceOf[_to]+_value >= balanceOf[_to]);
        //Guarantee atomicity
        uint previousBalances = balanceOf[_from] + balanceOf[_to];
        
        balanceOf[_from]-=_value;
        
        balanceOf[_to]+=_value;

        emit Transfer(_from,_to,_value);
        //Must assert
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }
    
    function transfer(address _to,uint256 _value) public returns(bool success){
        _transfer(msg.sender,_to,_value);
        return true;
    }
    //Transfer from another person's account to another person's account
    function transferFrom(address _from,address _to,uint256 _value) public returns(bool success){
        //allowance[_from][msg.sender]: the amount from the account to the account of the contract caller
        require(_value <= allowance[_from][msg.sender]);
        allowance[_from][msg.sender]-=_value;
        _transfer(_from,_to,_value);
        return true;
    }
    //to grant authorization
    function approve(address _spender,uint256 _value) public returns(bool success){
        //Authorization limit given to spender by contract caller
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender,_spender,_value);
        return true;
    }
    
    function approveAndCall(address _spender,uint256 _value,bytes _extraData) public returns(bool success){
        tokenRecipient spender = tokenRecipient(_spender);
        if(approve(_spender,_value)){
            spender.receiveApproval(msg.sender,_value,this,_extraData);
            return true;
        }
    }
    
    function burn(uint256 _value) public returns(bool success){
        require(balanceOf[msg.sender] >= _value);
        //Caller account deduction_ value
        balanceOf[msg.sender] -= _value;
        //Total tokens minus_ value
        totalSupply -= _value;
        emit Burn(msg.sender,_value);
        return true;
    }
    //Destroy other people's tokens within the authorized limit
    function burnFrom(address _from,uint256 _value) public returns(bool success){
        require(balanceOf[_from] >= _value);
        require(_value <= allowance[_from][msg.sender]);
        balanceOf[_from] -= _value;
        allowance[_from][msg.sender] -= _value;
        totalSupply-=_value;
        emit Burn(_from,_value);
        return true;
        
    }
    
    
    
}

Added by dennismcdougall on Wed, 09 Mar 2022 12:28:55 +0200