Solidity Tutorial (3)

Contents

一、为什么用到私有链?

在以太坊的共有链上部署智能合约、发起交易需要花费以太币。而通过修改配置,可以在本机搭建一套以太坊私有链,因为与公有链没关系,既不用同步公有链庞大的数据,也不用花钱购买以太币,很好地满足了智能合约开发和测试的要求,开发好的智能合约也可以很容易地切换接口部署到以太坊公有链上。

二、开源工具和语言

1、brewMacOS包管理器

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

2、安装Goethereum

liyuechun:Downloads yuechunli$ brew install go
liyuechun:Downloads yuechunli$ brew tap ethereum/ethereum
liyuechun:Downloads yuechunli$ brew install ethereum

3、geth运行以太坊节点

liyuechun:Downloads yuechunli$ git clone https://github.com/ethereum/go-ethereum
liyuechun:Downloads yuechunli$ cd go-ethereum
liyuechun:Downloads yuechunli$ make geth

4、Solidity以太坊智能合约语言

brew install solidity

备注:安装时间可能有点长,请耐心等待… 备注:安装时间可能有点长,请耐心等待… 备注:安装时间可能有点长,请耐心等待…

如果碰见下面的错误,请移步:http://blog.csdn.net/Sico2Sico/article/details/71082130

The GitHub credentials in the macOS keychain may be invalid.
Clear them with:
  printf "protocol=https\nhost=github.com\n" | git credential-osxkeychain erase
Or create a personal access token:
  https://github.com/settings/tokens/new?scopes=gist,public_repo&description=Homebrew

三、建立私链

1. 创建一个文件夹来存储你的私链数据

liyuechun:1015 yuechunli$ mkdir privatechain
liyuechun:1015 yuechunli$ pwd
/Users/liyuechun/Desktop/1015
liyuechun:1015 yuechunli$ ls
privatechain
liyuechun:1015 yuechunli$ 

2. 使用geth来加载

geth --networkid 123 --dev --datadir data1 --rpc --rpcaddr 192.168.1.102 --rpcport 8989 --port 3000 --allow-insecure-unlock

各选项含义如下:

  • --identity:指定节点 ID;
  • --rpc:表示开启 HTTP-RPC 服务;
  • --rpcaddr:HTTP-RPC 服务ip地址;
  • --rpcport:指定 HTTP-RPC 服务监听端口号(默认为 8545);
  • --datadir:指定区块链数据的存储位置;
  • --port:指定和其他节点连接所用的端口号(默认为 30303);
  • --nodiscover:关闭节点发现机制,防止加入有同样初始配置的陌生节点。
  • --allow-insecure-unlock:允许解锁。

执行上面的命令,你应该能看到下面的信息:

INFO [10-15 03:14:50] IPC endpoint opened: /Users/liyuechun/Desktop/1015/privchain/geth.ipc
INFO [10-15 03:14:50] HTTP endpoint opened: http://127.0.0.1:8545

如果你切换到data1文件夹里面,你会看到geth, geth.ipc, 和 keystore

liyuechun:1015 yuechunli$ cd data1/
liyuechun:data1 yuechunli$ ls
geth        geth.ipc    keystore
liyuechun:data1 yuechunli$ 
  • 保持节点的运行,不要关闭终端,重新打开一个终端,使用geth attach连接节点,并且打开geth console
liyc1215:Desktop liyuechun$ cd privatechain/
liyc1215:privatechain liyuechun$ ls
data1
liyc1215:privatechain liyuechun$ cd data1/
liyc1215:data1 liyuechun$ ls
geth        geth.ipc    keystore
liyc1215:data1 liyuechun$ geth attach ipc:./geth.ipc 
Welcome to the Geth JavaScript console!

instance: Geth/v1.9.16-stable/darwin-amd64/go1.14.4
coinbase: 0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba
at block: 0 (Thu Jan 01 1970 08:00:00 GMT+0800 (CST))
 datadir: /Users/liyuechun/Desktop/privatechain/data1
 modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0

> personal.listAccounts
["0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba"]
> 

这是一个交互式的 JavaScript 执行环境,在这里面可以执行 JavaScript 代码,其中 > 是命令提示符。在这个环境里也内置了一些用来操作以太坊的 JavaScript 对象,可以直接使用这些对象。这些对象主要包括:

  • eth:包含一些跟操作区块链相关的方法;
  • net:包含一些查看p2p网络状态的方法;
  • admin:包含一些与管理节点相关的方法;
  • miner:包含启动&停止挖矿的一些方法;
  • personal:主要包含一些管理账户的方法;
  • txpool:包含一些查看交易内存池的方法;
  • web3:包含了以上对象,还包含一些单位换算的方法。

3. 相关api命令

查看账户

> personal.listAccounts
["0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba"]
> 

创建账户

> personal.newAccount('liyuechun') 
"0x3125e07931e2ab1486c60e296b93d1a5b4b2567a"

PS:里面的liyuechun是你账户的密码,输入你自己喜欢的密码。

查看账户

> personal.listAccounts
["0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba", "0x3125e07931e2ab1486c60e296b93d1a5b4b2567a"]
> 

4. web3参考文档

  • https://web3js.readthedocs.io/en/v1.2.9/web3-eth.html#accounts
  • https://ethereumbuilders.gitbooks.io/guide/content/en/ethereum_javascript_api.html
  • https://web3py.readthedocs.io/en/stable/web3.miner.html
> web3.eth.accounts
["0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba", "0x3125e07931e2ab1486c60e296b93d1a5b4b2567a"]
> web3.eth.coinbase 
"0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba"
> 

5. 编写智能合约代码

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

contract HelloWorld { 

    string public greet;

    function setGreeting(string memory greeting) public{

        greet = greeting;
    }

    function hi() public pure returns(string memory) {

        return "HelloWorld";
    }

}

6. 获取智能合约字节码和abi

代码拷贝到https://remix.ethereum.org,编译,然后拷贝字节码和ABI。

  • 字节码
608060405234801561001057600080fd5b506103d7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063a413686214610046578063a99dca3f14610101578063cfae321714610184575b600080fd5b6100ff6004803603602081101561005c57600080fd5b810190808035906020019064010000000081111561007957600080fd5b82018360208201111561008b57600080fd5b803590602001918460018302840111640100000000831117156100ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610207565b005b610109610221565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561014957808201518184015260208101905061012e565b50505050905090810190601f1680156101765780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61018c61025e565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101cc5780820151818401526020810190506101b1565b50505050905090810190601f1680156101f95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061021d9291906102fc565b5050565b60606040518060400160405280600a81526020017f48656c6c6f576f726c6400000000000000000000000000000000000000000000815250905090565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102f45780601f106102c9576101008083540402835291602001916102f4565b820191906000526020600020905b8154815290600101906020018083116102d757829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061033d57805160ff191683800117855561036b565b8280016001018555821561036b579182015b8281111561036a57825182559160200191906001019061034f565b5b509050610378919061037c565b5090565b61039e91905b8082111561039a576000816000905550600101610382565b5090565b9056fea264697066735822122097b464d91714dce638c1cf5fd703125d2617a7d3417399c767b632b8b59110ee64736f6c63430006060033
  • ABI
[
  {
    "inputs": [

    ],
    "name": "greet",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [

    ],
    "name": "hi",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "pure",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "greeting",
        "type": "string"
      }
    ],
    "name": "setGreeting",
    "outputs": [

    ],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

7. 在bejson中转义成字符串

http://www.bejson.com

[{\"inputs\":[],\"name\":\"greet\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"hi\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"greeting\",\"type\":\"string\"}],\"name\":\"setGreeting\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]

7. 通过abi创建合约对象

> var abi = JSON.parse('[{\"inputs\":[],\"name\":\"greet\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"hi\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"greeting\",\"type\":\"string\"}],\"name\":\"setGreeting\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]')
> var myContract = web3.eth.contract(abi)

8. 检查coinbase账号余额

> account1 = web3.eth.coinbase
"0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba"
> web3.eth.getBalance(account1)
1.15792089237316195423570985008687907853269984665640564039457584007913129639927e+77
> 

9. 解锁coinbase账号,我们通过coinbase账号来付费部署合约

coinbase账号默认密码为空。

> personal.unlockAccount(account1, "") 
true
> 

10. 预估手续费

> bytecode = "608060405234801561001057600080fd5b506103d7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063a413686214610046578063a99dca3f14610101578063cfae321714610184575b600080fd5b6100ff6004803603602081101561005c57600080fd5b810190808035906020019064010000000081111561007957600080fd5b82018360208201111561008b57600080fd5b803590602001918460018302840111640100000000831117156100ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610207565b005b610109610221565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561014957808201518184015260208101905061012e565b50505050905090810190601f1680156101765780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61018c61025e565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101cc5780820151818401526020810190506101b1565b50505050905090810190601f1680156101f95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061021d9291906102fc565b5050565b60606040518060400160405280600a81526020017f48656c6c6f576f726c6400000000000000000000000000000000000000000000815250905090565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102f45780601f106102c9576101008083540402835291602001916102f4565b820191906000526020600020905b8154815290600101906020018083116102d757829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061033d57805160ff191683800117855561036b565b8280016001018555821561036b579182015b8281111561036a57825182559160200191906001019061034f565b5b509050610378919061037c565b5090565b61039e91905b8082111561039a576000816000905550600101610382565b5090565b9056fea264697066735822122097b464d91714dce638c1cf5fd703125d2617a7d3417399c767b632b8b59110ee64736f6c63430006060033"

> web3.eth.estimateGas({data: bytecode})
Error: invalid argument 0: json: cannot unmarshal hex string without 0x prefix into Go struct field CallArgs.data of type hexutil.Bytes
    at web3.js:6347:37(47)
    at web3.js:5081:62(37)
    at <eval>:1:21(7)

> bytecode = "0x608060405234801561001057600080fd5b506103d7806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063a413686214610046578063a99dca3f14610101578063cfae321714610184575b600080fd5b6100ff6004803603602081101561005c57600080fd5b810190808035906020019064010000000081111561007957600080fd5b82018360208201111561008b57600080fd5b803590602001918460018302840111640100000000831117156100ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610207565b005b610109610221565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561014957808201518184015260208101905061012e565b50505050905090810190601f1680156101765780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61018c61025e565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101cc5780820151818401526020810190506101b1565b50505050905090810190601f1680156101f95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061021d9291906102fc565b5050565b60606040518060400160405280600a81526020017f48656c6c6f576f726c6400000000000000000000000000000000000000000000815250905090565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102f45780601f106102c9576101008083540402835291602001916102f4565b820191906000526020600020905b8154815290600101906020018083116102d757829003601f168201915b505050505081565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061033d57805160ff191683800117855561036b565b8280016001018555821561036b579182015b8281111561036a57825182559160200191906001019061034f565b5b509050610378919061037c565b5090565b61039e91905b8082111561039a576000816000905550600101610382565b5090565b9056fea264697066735822122097b464d91714dce638c1cf5fd703125d2617a7d3417399c767b632b8b59110ee64736f6c63430006060033"
> web3.eth.estimateGas({data: bytecode})
265238
> 

备注:字节码前面需要添加0x。手续费大概为265238gas

11. 部署合约,为了方便理解,设置一个回调函数

> contractInstance = myContract.new({data: bytecode,gas: 1000000, from: account1}, function(e, contract){
  if(!e){
    if(!contract.address){
      console.log("Contract transaction send: Transaction Hash: "+contract.transactionHash+" waiting to be mined...");
    }else{
      console.log("Contract mined! Address: "+contract.address);
      console.log(contract);
    }
  }else{
    console.log(e)
  }
})

如下所示:合约创建成功。

Contract transaction send: Transaction Hash: 0xae19a2404b80cd83466f2aac17e016fe9a84229bfadfe6b80417792b46bfaf33 waiting to be mined...
{
  abi: [{
      inputs: [],
      name: "greet",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }, {
      inputs: [{...}],
      name: "setGreeting",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: undefined,
  transactionHash: "0xae19a2404b80cd83466f2aac17e016fe9a84229bfadfe6b80417792b46bfaf33"
}
> Contract mined! Address: 0x47ad8096a084b00e464450b6c7f85954e2e12a6b
[object Object]

12. 检查合约是否部署成功

> contractInstance
{
  abi: [{
      inputs: [],
      name: "greet",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }, {
      inputs: [{...}],
      name: "setGreeting",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: "0x47ad8096a084b00e464450b6c7f85954e2e12a6b",
  transactionHash: "0xae19a2404b80cd83466f2aac17e016fe9a84229bfadfe6b80417792b46bfaf33",
  allEvents: function(),
  greet: function(),
  setGreeting: function()
}
> 

13. 调用合约方法

> contractInstance.setGreeting("liyuechun",{from:account1});
"0x4426849ae95fee4d7795d91329a1e1a9e45ea3af8668f751b3b716ae1a01e8eb"

14. 查询交易信息

> eth.getTransactionReceipt("0x4426849ae95fee4d7795d91329a1e1a9e45ea3af8668f751b3b716ae1a01e8eb")
{
  blockHash: "0x6c6b97309e7d8a7f0a1a8b6a5e58d573cf00311d63ee88588e1c853113af5ebf",
  blockNumber: 22,
  contractAddress: null,
  cumulativeGasUsed: 43264,
  from: "0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba",
  gasUsed: 43264,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: "0x510ef229f55776ec439aa6167523ccd0c5b88cd7",
  transactionHash: "0x4426849ae95fee4d7795d91329a1e1a9e45ea3af8668f751b3b716ae1a01e8eb",
  transactionIndex: 0
}

15. 转账

> web3.eth.getBalance(web3.eth.accounts[0]);
1.15792089237316195423570985008687907853269984665640564039457584007913129639927e+77
> web3.eth.getBalance(web3.eth.accounts[1]);
0
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(1,"ether")})
"0x3c7983c2976191aec473952ea3587dfad3d60009c9db37dbf4277ed9ad700788"
> web3.eth.getBalance(web3.eth.accounts[1]);

1000000000000000000
> web3.eth.getBalance(web3.eth.accounts[0]);

1.15792089237316195423570985008687907853269984665640564039456584007913129639927e+77
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(10,"ether")})
"0x38e5199cc3b9dec6e33e7783fe5833bbc5f12292b6bec63f49e3d5bb0166c01f"
> web3.eth.getBalance(web3.eth.accounts[1]);

11000000000000000000
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(10,"ether")})
"0x2a877b5c1942d16456f41391e8ab36f12d5d8caf6f7de6f76a854eb498cc1e53"
> web3.eth.getBalance(web3.eth.accounts[1]);

21000000000000000000
> web3.fromWei(web3.eth.getBalance(eth.accounts[1]), 'ether')
21

16. 查看转账信息

> eth.getTransactionReceipt("0x2a877b5c1942d16456f41391e8ab36f12d5d8caf6f7de6f76a854eb498cc1e53")
{
  blockHash: "0x136dee4f228cfa46903584b095e471062adc528aa500d17e973718b68ebe9cb5",
  blockNumber: 25,
  contractAddress: null,
  cumulativeGasUsed: 21000,
  from: "0xa4ebe3ac6a5d625acc62c1cb3a0e20071489efba",
  gasUsed: 21000,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: "0x3125e07931e2ab1486c60e296b93d1a5b4b2567a",
  transactionHash: "0x2a877b5c1942d16456f41391e8ab36f12d5d8caf6f7de6f76a854eb498cc1e53",
  transactionIndex: 0
}
>