web3 - overview of smart contract | PHP implementation of ETH 4

Posted by HughbertD on Sat, 26 Feb 2022 17:26:06 +0100

Smart contract overview

Smart contract is the software running on the blockchain. It is often compared to "vending machine", because it is easy to understand: the vending machine accepts and executes external instructions. When the customer selects the goods and pays, the vending machine will release the goods to the customer without additional manual intervention:

The concept of smart contract was first proposed by computer scientist and Cryptologist Nick Szabo in 1994, but there was no suitable environment to implement it at that time. Due to the traceability, tamper resistance and irreversibility of transactions on the blockchain, smart contracts can also conduct secure transactions without a third-party intermediary, which makes the implementation of automated smart contracts possible.

Due to the built-in virtual machine and development language of Ethereum, the efficiency and difficulty of developing smart contracts on Ethereum blockchain are greatly improved. Therefore, when we talk about smart contracts now, we are basically talking about smart contracts on Ethereum.

In this part of the course, we will learn the following:

Develop ERC20 token contract with solidity, compile solidity smart contract with command line tool, write php code of deployment contract, and interact with smart contract in php code

Development and interaction of smart contract

Learn the design of ERC20 token smart contract and implement it with solid development language, and then deploy and interact with php.

Before running the preset code, start the node emulator at the 1# terminal first:

~$ ganache-cli

Compile the contract and execute the following command:

~/repo/chapter5$ ./build-contract.sh

Deploy contract execution php script:

~/repo/chapter5$ php deploy-contract.php

Access the contract execution php script:

~/repo/chapter5$ php access-contract.php

ERC20 token specification

At present, almost all tokens used to raise funds for ICO are based on the same technology: Ethereum ERC-20 standard. These tokens are actually smart contracts that implement ERC20 standard.

An ERC20 token contract should implement the following standard interfaces. Of course, you can also add additional interfaces according to your actual needs:

contract ERC20 {
   function totalSupply() constant returns (uint theTotalSupply);
   function balanceOf(address _owner) constant returns (uint balance);
   function transfer(address _to, uint _value) returns (bool success);
   function transferFrom(address _from, address _to, uint _value) returns (bool success);
   function approve(address _spender, uint _value) returns (bool success);
   function allowance(address _owner, address _spender) constant returns (uint remaining);
   event Transfer(address indexed _from, address indexed _to, uint _value);
   event Approval(address indexed _owner, address indexed _spender, uint _value);
}

totalSupply()

This function should return the total amount of token supply in circulation. For example, you are going to issue 1 million tokens for your website.

balanceOf()

This function should return the token balance of the specified account address.

approve()

Using this function for authorization, the authorized account can call the name of the account for transfer.

transfer()

This function allows you to call the account for transfer operation and send a specified number of tokens to another account.

transferFrom()

This function allows a third party to transfer money. The transfer out account must be authorized to the calling account by calling the approve() method.

Transfer event

This event must be triggered after each successful transfer. The parameters are: transfer out account, transfer in account and transfer token quantity.

Approval event

This event must be triggered every time authorization is performed. Parameters: authorizer, authorized person and authorization limit.

In addition to the above interfaces that must be implemented, ERC20 also stipulates several optional states so that wallets or other applications can better identify Tokens:

Name: token name. For example: HAPPY COIN. Symbol: token symbol. For example: HAPY, display this name in wallet or exchange. decimals: decimal places. The default value is 18. Wallet app will use this parameter. For example, if your token scale is set to 2, 1002 tokens will be displayed as 10.02 in your wallet.

Token contract status design

The design core of smart contract is the design of state, and then the corresponding operation is designed around the state. Token contracts are no exception.

First of all, we need to have a status to record the total amount of token issuance, which is usually fixed when the contract is deployed, but you can also define additional non-standard interfaces to operate this status, such as additional issuance:

In solidity, we can use a uint256 type variable to record the total number of releases:

uint256 totalSupply;

Well, more is better. But there is no bigger type than uint256.

Next, we need to have a status to save the token balance of all accounts:

In solidity, a mapping table corresponding to an integer (representing the balance) from the account address can be used to represent this state:

mapping(address => uint256) balances;

The last important status is the authorization relationship. We need to record three information: authorized account, authorized account and authorization limit:

Obviously, this requires a nested mapping table:

mapping (address => mapping (address => uint256)) public allowed;

As for token name, token symbol and decimal places, simply use variables of string and uint8 types:

string public name;             
string public symbol;

Read the tutorial and answer the following questions: what states will transfer() change? Which states will transferFrom() use and which states will be modified? Which states will be modified by approve()? uint8 public decimals;

Implementation of token contract method

(demo:repo\chapter5\contract\EzToken.sol)

After defining the core state, the implementation of the interface specified in ERC20 is very simple. However, before implementing these interfaces, let's take a look at the constructor:

constructor(
    uint256 _initialAmount,
    string _tokenName,
    uint8 _decimalUnits,
    string _tokenSymbol
) public {
    balances[msg.sender] = _initialAmount;               
    totalSupply = _initialAmount;                        
    name = _tokenName;                                   
    decimals = _decimalUnits;                            
    symbol = _tokenSymbol;                               
}

Well, it's very simple to save the four parameters passed in: total amount of issuance, token name, number of decimal places and token symbol. In the above implementation, the account of the deployment contract will hold all tokens at the beginning.

You can adjust the parameters and implementation logic of the constructor according to your needs.

transfer(to,value)

It is easy to understand that the operation state of the transfer() function is balances. The implementation logic is straightforward, and the code is as follows:

function transfer(address _to, uint256 _value) public returns (bool success) {
    require(balances[msg.sender] >= _value);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    emit Transfer(msg.sender, _to, _value); 
    return true;
}

Since transfer() transfers out tokens from the calling account, first check whether the token balance of the calling account is sufficient. Next, you can adjust the account balances of both parties respectively, and then trigger the Transfer event.

approve(spender,value)

The state of the approve() function operation is allowed. The implementation logic is also straightforward. The above code:

function approve(address _spender, uint256 _value) public returns (bool success) {
    allowed[msg.sender][_spender] = _value;
    emit Approval(msg.sender, _spender, _value); 
    return true;
}

After modifying the allowed mapping table, you can trigger the Approval event.

transferFrom(from,to,value)

The logic of the transferFrom() method is relatively complex. It needs to operate the balances state and the allowed state at the same time:

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
    uint256 allowance = allowed[_from][msg.sender];
    require(balances[_from] >= _value && allowance >= _value);
    balances[_to] += _value;
    balances[_from] -= _value;
    if (allowance < MAX_UINT256) {
        allowed[_from][msg.sender] -= _value;
    }
    emit Transfer(_from, _to, _value); //solhint-disable-line indent, no-unused-vars
    return true;
}

The code first checks the allowed status to determine whether the calling account is authorized to Transfer out the account and whether the authorized amount is sufficient for this Transfer. Then check whether the balance of the Transfer out account is enough for this Transfer. After these conditions are met, directly adjust the balance of the Transfer out account and Transfer in account in the balances status, and adjust the corresponding authorization limit in the allowed status. Finally, trigger the Transfer event.

balanceOf(owner)

The balanceOf() method only queries the balance status, so it is a view function that does not consume gas:

function balanceOf(address _owner) public view returns (uint256 balance) {
    return balances[_owner];
}
allowance(owner,spender)

The allowance() method queries the authorization limit of the account pair. Obviously, it is also a view function that does not modify the status:

function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
    return allowed[_owner][_spender];
}

Refer to the tutorial and write the token contract eztoken Sol to realize ERC20 specification.

Compile token contract

In order to interact with the contract in the Php code, we need to compile the contract written by solid first to get EVM bytecode and binary application interface (ABI).

Bytecode is the code that finally runs in Ethereum virtual machine. It looks like this:

608060405234801561001057600080fd5b5060405...

When we deploy the EVM code, it is just like the 16 byte code of the virtual machine. When we deploy the EVM code, it corresponds to the 16 byte code of the virtual machine.

ABI is a JSON object describing the contract interface, which is used to invoke contracts in other development languages. ABI describes the language characteristics of each method, state and event in the contract. For example, the transfer() method in token contract is described in ABI as follows:

The rich information provided by ABI is the key material to realize the binding of other languages. Any time you interact with a contract, you need to hold the ABI information of the contract.

solc, the official compiler of solidity, is a command-line program. It will behave differently according to different running options. For example, the following -- bin and -- ABI options require the solc compiler to generate bytecode files and ABI files at the same time and output them to the build directory:

~$ mkdir -p contract/build
~$ solc contract/EzToken.sol --bin --abi \
                   --optimize --overwrite \
                   -o contract/build/

After successful compilation, two files will be obtained in the build directory, which is the basis of our next work:

~$ ls contract/build
EzToken.abi EzToken.bin

Reference tutorial: compiling contract eztoken with solc Sol checks the generated ABI file, finds the description corresponding to the balanceOf() function, and compares it with its solidity declaration to understand the information coverage in ABI.

Deploy token contract

(demo: \repo\chapter5\deploy-contract.php)

With the bytecode and ABI of the contract, you can use the Web3\Contract class to deploy the contract to the chain.

First load ABI and bytecode:

$abi = file_get_contents(' contract / build / EzToken.abi ');
$bytecode = ' 0 x ' . file_get_contents(' contract / build / EzToken.bin ');

Then create the Web3\Contract instance, you need to import the Web3\Provider object and the ABI information of the contract, then call the bytecode() method to set the bytecode:

$contract = new Web3\Contract($web3->provider,$abi);
$contract->bytecode($bytecode);

Why not pass bytecode directly into the constructor?

This is because bytecode is only required for deployment. Once the contract deployment is completed, you only need the ABI and deployment address of the contract to interact with the contract.

Everything is ready. Now you only need to call the new() method of the contract object to deploy. For example, the following code completes the issuance of 10 million happiness coins:

$cb = new Callback();
$opts = [
  ' 
	FROM
		' => $accounts[0],
  ' gas ' => ' 0 x200b20 '  //2100000
];
$contract->new(10000000,' HAPPY TOKEN ',0,' HAPY ',$opts,$cb);
$txhash = $cb->result;

It is easy to understand that the parameter values declared by the constructor of the contract will be passed in sequence during deployment, that is, the constructor of the contract will be executed only when the contract is deployed. In addition, since the deployment contract is a transaction, we need to declare the deployment account and gas usage.

Since the deployment account is the first account of the node, it will hold all the tokens initially issued.

Next, wait for the transaction receipt, because there is the specific address of the contract deployment in the receipt:

$timeout = 60;
$interval = 1;
$t0 = time();
while(true){
  $this->eth->getTransactionReceipt($txhash,$cb);        
  if(isset($cb->result)) break;
  $t1 = time();
  if(($t1 - $t0) > $timeout) break;
  sleep($interval);  
}

If you get the receipt, the deployment is successful. However, if you want to interact with the newly deployed contract object immediately, you also need to set its address:

$contract->at($receipt->contractAddress);

Most importantly, copy down the address or record it in a file, otherwise you won't be able to access the contract next.

file_put_contents('./ contract / build / EzToken.addr ',$receipt->contractAddress);

Refer to the tutorial and write a php script to realize the following functions: deploy the token contract using the first account of the node, issue 1 million happiness coins, code HAPY, output the deployment address of the contract on the console, and save the deployment address of the contract to a file

Access token contract

(demo:repo\chapter5\access-contract.php)

To access a contract already deployed on the chain, you only need its ABI and deployment address.

Similarly, first load ABI and the previously saved address:

$abi = file_get_contents('./ contract / build / EzToken.abi ');
$addr = file_get_contents('./ contract / build / EzToken.addr ');

Then build the Contract object and set its deployment address:

$contract = new Web3\Contract($web3->provider,$abi);
$contract->at($addr);

Then you can call the method of the contract. There are two situations.

For those transaction functions that modify the contract status, such as transfer(), use the send() method of the contract object. For example, transfer some tokens to the second node account:

$opts = [
  ' 
FROM
	' => $accounts[0],
  ' gas ' => ' 0 x200b20 '
];
$contract->send(' transfer ',$accounts[1],$opts,$cb);
echo ' tx HASH : ' . $cb->result . PHP_EOL;

The transaction function always returns the transaction receipt.

Note that because we use the contract deployed by the first node account, it now holds all tokens.

If you want to call a read-only function in the contract, such as balanceOf(), use the call() method of the contract object. For example, get the token balance of the first node account:

$opts = []; //No gas consumption
$contract->call(' balanceOf ',$accounts[0],$opts,$cb);
$balance = $cb->result[' balance ']->toString();
echo ' balance ' . $balance . PHP_EOL;

Note that since solidity supports the function to return multiple values, the return result of the call() method is always an associative array, and the name of each key corresponds to the name of the outputs part of the called contract method in ABI.

Referring to the tutorial and sample code, write php to realize the following functions: transfer 100 tokens to the second account of the node

Overview of notification mechanism

Notification mechanism is important for any application development because it provides the ability to notify changes in another direction. Ethereum is no exception. Its notification mechanism enhances the communication ability between smart contracts and external applications.

Ethereum's notification mechanism is based on logs. For example, if the smart contract triggers an event, the event will be written to the Ethereum log; If the external application subscribes to this event, the external application can pull it after the event appears in the log, as shown in the figure:

It should be noted that Ethereum's notification mechanism is not a Push mode, but a Pull mode that requires periodic polling by external applications. The external application subscribes to the interested logs by creating a filter in the node, and then obtains the latest logs by detecting the change of the filter.

In this part of the course, we will learn the following:

Use the block filter to listen for new block generation events and new transaction events, use the pending transaction filter to listen for pending transaction events, use the subject filter to listen for contract events, and analyze the logs generated by contract events

Listen for new block events

Use the block filter to listen for new block generation events. The process is as follows:

First, eth_ The newblockfilter call is used to create a new block filter:

$cb = new Callback();
$web3->eth->newBlockFilter($cb); 
$fid = $cb->result;

Then call eth_. Getfilterchanges performs periodic checks:

while(true){
  $web3->eth->getFilterChanges($fid,$cb);
  $blocks = $cb->result;
  foreach($blocks as $hash) { 
      echo $hash . PHP_EOL;
  }
  sleep(2);
}

For block filters, ETH_ The getfilterchanges call returns an array of block hash values. If you want to get the details of the block, you can use eth_getBlockByHash call.

Referring to the tutorial and sample code, write two php scripts to realize the following functions respectively: listen for new block generation events, print new block information, and transfer 1kwei from the first account to the second account of the node. Run two scripts on two different terminals and observe the output of the monitoring script.

Listen for new transaction events

To listen for new confirmation transactions, block filters are also used. The process is as follows:

In fact, its implementation mechanism is to further obtain the details of the new block after capturing the new block event.

Therefore, like listening for new block events, ETH is executed first_ The newblockfilter call is used to create a new block filter:

$cb = new Callback();
$web3->eth->newBlockFilter($cb);
$fid = $cb->result;

Then call eth_. Getfilterchanges performs periodic checks. For each block hash returned by this call, further call eth_getBlockByHash to get the details of the block:

while(true){
  $web3->eth->getFilterChanges($fid,$cb); $blocks = $cb->result;
  foreach($blocks as $hash) {
    $web3->eth->getBlockByHash($hash,true,$cb);  $block = $cb->result;
    foreach($block->transactions as $tx) var_dump($tx);
  }
  sleep(2);
}

eth_ The second parameter of getblockbyhash call is used to declare whether the complete transaction object needs to be returned. If it is set to false, only the hash of the transaction will be returned.

Referring to the tutorial and sample code, write two php scripts to realize the following functions respectively: monitor the new transaction generation event, display the new transaction information, transfer 1gwei from the first account to the second account of the node, run the above scripts on the two terminals respectively, and view the monitoring output.

Listening to pending transaction events

Pending transactions refer to those submitted to the node but not confirmed by the network, so it will not be included in the block. Use the pending transaction filter to listen for new pending transaction events. The process is as follows:

Execute eth first_ The newpendingtransactionfilter call is used to create a new pending transaction filter:

$cb = new Callback();
$web3->eth->newPendingTransactionFilter($cb); $fid = $cb->result;

Then again, call eth_getFilterChanges performs periodic checks. For example, the following code will print the information of the new pending transaction:

while(true){
  $web3->eth->getFilterChanges($cb); $ptxs = $cb->result;
  foreach($ptxs as $hash) echo $hash . PHP_EOL;
  sleep(2);
}

For pending transaction filters, ETH_ The result returned by the getfilterchanges call is the hash of a new set of pending transactions generated since the last call. You can use eth_getTransactionByHash calls to view the details of the specified transaction.

Referring to the tutorial and sample code, write two php scripts to realize the following functions: monitor new pending transaction events, display pending transaction information, transfer 1gwei from the first account to the second account of the node, run the above scripts at the two terminals respectively, and view the monitoring output.

Monitoring contract events

The monitoring of contract events is realized through the subject filter. The process is as follows:

Execute eth first_ Call newfilter to create a topic filter and get the number of the filter:

$cb = new Callback();
$web3->eth->newFilter([],$cb);
$fid = $cb->result;

eth_ The newfilter call can receive an option parameter to filter the log type of listening. This option can specify some listening filter conditions, such as the contract address to listen to. However, in the above code, we use an empty associative array without setting these parameters, which means that we will listen to all logs.

After creating the theme filter, you can also use eth_ The getfilterchanges call checks periodically to see if a new log is generated. This call will return an array of all new logs since the last call:

while(true){
  $web3->eth->getFilterChanges($cb);
  $logs = $cb->result;
  foreach($logs as $log) {
      var_dump($log);  
  }
  sleep(2);
}

Each log is mapped to a StdClasst object in php, but obviously, the captured log needs further decoding to get the event parameters:

Refer to the tutorial and write two php scripts to realize the following functions respectively: monitor the transfer event of token contract, transfer a token from the first account to the third account of the node every 3 seconds, run the above scripts at the two terminals respectively, and observe the monitoring output.

Filtering logs using themes

When we create topic filters and view log data, we come across a concept: topic. Ethereum uses themes to distinguish different events.

The subject is actually the hash signature of the event. For example, for the Transfer event of token contract, its signature is calculated as follows:

Use the encodeEventSignature() method of Web3\Contract\Ethabi class to calculate the event signature. We can get an ethabi instance from the ethabi attribute of the contract object. For example:

$contract = new Contract($web3->provider,$abi);
$ethabi = $contract->ethabi;
$topic = $ethabi->encodeEventSignature(' Transfer ( address, address, ) ');
echo ' topic : ' . $topic . PHP_EOL;

Since the parameter information of the event has been recorded in abi, the abi information can also be directly passed in:

$topic = $ethabi->encodeEventSignature($abi->events[' Transfer ']);
echo ' topic : ' . $topic . PHP_EOL;

When creating a topic filter, you can use this topic to adjust the listening behavior:

while(true){
  $contract->eth->getFilterChanges($fid,$cb);
  $logs = $cb->result;

if(count(KaTeX parse error: Expected 'EOF', got '&' at position 7: logs) &̲gt;0) { ...logs as KaTeX parse error: Expected '}', got 'EOF' at end of input: ... { if(log->topics[0] == KaTeX parse error: Expected '}', got 'EOF' at end of input: ... var_dump(log);
} else {
echo ' skip log ' . PHP_EOL;
}
}
}

sleep(2);
}

Referring to the tutorial and sample code, write two php scripts to realize the following functions:
Listen to the Approval event of token contract
The Approval event and Transfer event are triggered respectively
Run the above scripts on the two terminals respectively to check whether the listening output is consistent with the expectation.

Topics: PHP Blockchain Ethereum