0%

以太坊私链部署智能合约

编写智能合约

以下编写了一个简单的存储智能合约,不是 NFT 智能合约。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract SimpleStorage {
uint storedData;

function set(uint x) public {
storedData = x;
}

function get() public view returns (uint) {
return storedData;
}

function add(uint x) public {
uint y = x + storedData;
storedData = y;
}
}

编译智能合约

Remix 上compile刚刚写好的智能合约,然后会出现以下界面,ABI 和 Bytecode 待会需要用到

部署智能合约

这里实现两种部署智能合约的方式,一种是在 geth 控制台部署,一种是使用 Go 实现

geth 控制台部署

1、解锁账户
24-4-15 更新: personal.unlockAccount 被弃用,且新方法不需要

1
personal.unlockAccount(eth.coinbase)

2、录入智能合约的 bytecode

1
var code = "0x" +  "608060405234801561001057600080fd5b50610218806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631003e2d21461004657806360fe47b1146100625780636d4ce63c1461007e575b600080fd5b610060600480360381019061005b9190610106565b61009c565b005b61007c60048036038101906100779190610106565b6100b8565b005b6100866100c2565b6040516100939190610142565b60405180910390f35b60008054826100ab919061018c565b9050806000819055505050565b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100e3816100d0565b81146100ee57600080fd5b50565b600081359050610100816100da565b92915050565b60006020828403121561011c5761011b6100cb565b5b600061012a848285016100f1565b91505092915050565b61013c816100d0565b82525050565b60006020820190506101576000830184610133565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610197826100d0565b91506101a2836100d0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156101d7576101d661015d565b5b82820190509291505056fea2646970667358221220cbc928270d8a2ea9cd6f6c4b97793588c6a480da09388d19c6fb1f05d75e718464736f6c634300080f0033"

3、录入智能合约的 ABI
ABI 需要先经过压缩转义

1
var abi=JSON.parse('[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]')

4、部署智能合约

1
2
3
4
5
6
7
var gas = eth.estimateGas({data: code}) //先估算需要多大的gas
//创建智能合约类
var myContract = eth.contract(abi)
//创建部署智能合约的交易
var deploymentTx = {from: '0x890fb1799fe7fa4b8e01ccf343e088d946fcd556', data: code, gas: gas};
//创建合约实例
var myContractInstance = myContract.new(deploymentTx)

创建成功后,控制台中会出现以下提示

1
INFO [04-15|11:43:39.794] Submitted contract creation              hash=0xf2503d2c684283a9afb6ab47233a4f2fb01f156135c429586b924a0756d0a8d8 from=0x890fb1799Fe7fA4b8E01Ccf343e088D946fCd556 nonce=1 contract=0xa5D8278723e8808151c2066198619fc02818b526 value=0

在控制台输入 myContractInstance,能获得以下信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
abi: [{
inputs: [{...}],
name: "add",
outputs: [],
stateMutability: "nonpayable",
type: "function"
}, {
inputs: [],
name: "get",
outputs: [{...}],
stateMutability: "view",
type: "function"
}, {
inputs: [{...}],
name: "set",
outputs: [],
stateMutability: "nonpayable",
type: "function"
}],
address: undefined,
transactionHash: "0x727fb18ce42f5f368a349908eee34eb36ae1e3c7fd2cb4f280db8b0ed44de6de"
}

24-4-10 更新: 在输入第一行命令的时候,发现报以下错误。原因: PUSH0 opcode 是在 solidity 0.8.20 被引入的,而私链由于还未到 Shanghai 硬分叉,此时还不支持 PUSH0 opcode

5、启动节点挖矿
因为在私链上没有其他节点挖矿,而部署智能合约也是一笔交易,所以需要节点挖矿使这笔交易打包上链,智能合约才部署成功

1
2
miner.start()
miner.stop()

6、调用智能合约
第一种调用用于需要花费 gas 的函数,第二种则用于不需要花费 gas 的函数,第一种调用由于也是一笔交易,所以需要节点挖矿上链

1
2
contract.function_name.sendTransaction("parameter_1","parameter_2",{from:"account_address"})
contract.function_name.call("parameter_1","parameter_2")

使用 Go 部署

这里想要实现的效果是,每次新创建一个账户的时候,都会自动部署该智能合约,这一功能用 Go 实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package main

import (
"context"
"crypto/ecdsa"
"fmt"
"log"
"math/big"
"net/http"
"strings"
"bytes"
"io/ioutil"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)

// 将智能合约的ABI复制到这里,需要先经过压缩转义
var contractABI = "
[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"add\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"x\",\"type\":\"uint256\"}],\"name\":\"set\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"

func main() {
// Geth 节点的 HTTP-RPC 地址
rpcURL := "http://localhost:8545"

// 创建新账户的密码
newPassword := "your_password"

// 构造 JSON-RPC 请求体,创建新账户
payload := strings.NewReader(`{"jsonrpc":"2.0","method":"personal_newAccount","params":["` + newPassword + `"],"id":1}`)
resp, err := http.Post(rpcURL, "application/json", payload)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()

// 解析创建新账户的响应
var newAccountResp struct {
Result string `json:"result"`
Error *struct {
Message string `json:"message"`
} `json:"error"`
}
if err := json.NewDecoder(resp.Body).Decode(&newAccountResp); err != nil {
log.Fatal(err)
}
if newAccountResp.Error != nil {
log.Fatalf("Failed to create new account: %s", newAccountResp.Error.Message)
}

// 解析新账户地址
newAccountAddress := common.HexToAddress(newAccountResp.Result)
fmt.Printf("New account address: %s\n", newAccountAddress.Hex())

// 连接到本地的 Geth 节点
client, err := rpc.Dial(rpcURL)
if err != nil {
log.Fatal(err)
}
defer client.Close()

// 获取当前 gas 价格
var gasPrice *big.Int
if err := client.CallContext(context.Background(), &gasPrice, "eth_gasPrice"); err != nil {
log.Fatal(err)
}

// 部署智能合约
auth := bind.NewKeyedTransactor(newAccountAddress.Hex())
auth.Value = big.NewInt(0) // 部署时发送的以太币数量
auth.GasLimit = uint64(3000000) // 指定 gas 限制
auth.GasPrice = gasPrice // 使用当前 gas 价格
input := "0x" + "6080604052348015600e575f80fd5b506101e48061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c80631003e2d21461004357806360fe47b11461005f5780636d4ce63c1461007b575b5f80fd5b61005d600480360381019061005891906100fb565b610099565b005b610079600480360381019061007491906100fb565b6100b3565b005b6100836100bc565b6040516100909190610135565b60405180910390f35b5f8054826100a7919061017b565b9050805f819055505050565b805f8190555050565b5f8054905090565b5f80fd5b5f819050919050565b6100da816100c8565b81146100e4575f80fd5b50565b5f813590506100f5816100d1565b92915050565b5f602082840312156101105761010f6100c4565b5b5f61011d848285016100e7565b91505092915050565b61012f816100c8565b82525050565b5f6020820190506101485f830184610126565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610185826100c8565b9150610190836100c8565b92508282019050808211156101a8576101a761014e565b5b9291505056fea26469706673582212209645cec1c57b3e0ea64b23d942988cdc634242f2c015ac31b32aa6ddac78ade164736f6c63430008190033"
address, tx, _, err := bind.DeployContract(auth, abi.JSON(contractABI), common.FromHex(input), client)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Contract address: %s\n", address.Hex())

// 等待智能合约部署完成
_, err = bind.WaitDeployed(context.Background(), client, tx)
if err != nil {
log.Fatal(err)
}

// // 输出新创建的账户地址和私钥
// fmt.Printf("New account address: %s\n", newAccountAddress.Hex())
// fmt.Printf("Private key: %x\n", crypto.FromECDSA(privateKey))
}

调用智能合约

安装 abigen 工具

1
go get -u github.com/ethereum/go-ethereum

生成合约绑定代码

1
abigen --sol SimpleStorage.sol --pkg main --out SimpleStorage.go

在 main 函数中添加智能合约绑定和调用智能合约

NewSimpleStorage 不是一个标准函数,而是在使用 abigen 时创建的一个函数,用于创建智能合约的绑定对象

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建智能合约绑定
contract, err := NewSimpleStorage(address, client)
if err != nil {
log.Fatal(err)
}

// 调用智能合约的 get 函数
result, err := contract.Get(nil)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Result of get function: %s\n", result.String())
赏个鸡腿🍗