传送门: 柏链项目学院


第三课 智能合约自动化测试

之前课程回顾

我们之前介绍了go语言调用exec处理命令行,介绍了toml配置文件的处理,以及awk处理文本文件获得ABI信息。我们的代码算是完成了从智能合约到go语言的自动编译,同时也可以自动提取到ABI信息。

具体可以参考:

第一课 go语言与智能合约调用的来龙去脉

第二课 智能合约自动化编译

本节主要工作

  • go语言模版编程
  • 目标代码生成
  • 目标代码的调用代码生成
  • 配置文件的自动化生成

原有代码优化

想要自动化生成测试代码,首先你要知道目标代码长什么样儿,也就是之前我们写的部署合约,调用合约的代码。仔细思考,其实会发现分了两层内容,第一层内容就是针对每一个合约函数都要有一个调用函数生成,假设合约函数为X,我们把调用该函数的函数叫CallX,我们想要完成测试,又需要在main函数内能找到调用CallX的入口。先一步一步来,先完成一个小目标,那就是生成这些个CallX,

再来梳理一下我们之前调用合约的代码。

const keydata = `{"address":"791443d21a76e16cc510b6b1684344d2a5ce751c","crypto":{"cipher":"aes-128-ctr","ciphertext":"bbccbf9deb8c907d9f245767fffb57880c4cfd265dde9372d7278a8e963043bd","cipherparams":{"iv":"95e8f925fe0f3460f0ca3ccebc481b14"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"2036300fae07d954a10e70a2f87876fa198b310dc16a72f3fab3265978e7d798"},"mac":"009ce0ecd11d5f563d2f7bd41eb957f1ca3b517f31653dd18fac20e77b7feb5c"},"id":"7f42641c-580a-4398-b00c-74ef2710bcae","version":3}`

func CallDeploy() error {
//链接到以太坊,cli就是backend
cli, err := ethclient.Dial("http://localhost:8545")
if err != nil {
fmt.Println("failed to dial ", err)
return err
}
//创建身份,需要私钥= pass+keystore文件
auth, err := bind.NewTransactor(strings.NewReader(keydata), "123")
if err != nil {
fmt.Println("failed to NewTransactor auth", err)
return err
}
auth.GasLimit = 400000
addr, ts, pd, err := DeployPdbank(auth, cli, "yekai233")
if err != nil {
fmt.Println("failed to DeployPdbank", err)
return err
}
bkname, _ := pd.BankName(nil)
fmt.Println("addr=", addr.Hex(), "bkname=", bkname, "hash=", ts.Hash().Hex())
return err
}

对于keydata来说,我们不能写死了,需要从keystore文件读取这部分数据。http://localhost:8545这样的连接地址最好通过配置文件来搞定,但思路要清楚的是,我们这个代码是将来要执行的,这个代码它需要一个自己的配置文件,并非是我们当前工程的配置文件,直白一点的说就是我们需要给未来的代码宝宝们自动的生成一个配置文件。记得有这么回事儿,我们先不管这一点,先以能完成任务为前提。

我们还是来单独形成一个函数,用于专门形成签名,它需要读取keystore文件,这个文件可以根据对应的账户地址到以太坊keystore目录得到,这样就容易多了!

//设置签名
func MakeAuth(addr, pass string) (*bind.TransactOpts, error) {
keystorePath := "{{.Keydir}}"
fileName, err := GetFileName(string([]rune(addr)[2:]), keystorePath)
if err != nil {
fmt.Println("failed to GetFileName", err)
return nil, err
} file, err := os.Open(keystorePath + "/" + fileName)
if err != nil {
fmt.Println("failed to open file ", err)
return nil, err
}
auth, err := bind.NewTransactor(file, pass)
if err != nil {
fmt.Println("failed to NewTransactor ", err)
return nil, err
}
return auth, err
}
func GetFileName(address, dirname string) (string, error) { data, err := ioutil.ReadDir(dirname)
if err != nil {
fmt.Println("read dir err", err)
return "", err
}
for _, v := range data {
if strings.Index(v.Name(), address) > 0 {
//代表找到文件
return v.Name(), nil
}
} return "", nil
}

于是,我们将原来写的测试代码进行完善一下!

此外,连接这里也规范一下,使用init函数来搞定!

var testclient *ethclient.Client

func init() {
cli, err := CreateCli("http://localhost:8545")
if err != nil {
log.Panic("failed to connect to eth", err)
}
testclient = cli
}
func CallBankName() (error) {
instance, err := NewPdbank(common.HexToAddress("0xD55E88D9156355C584982Db2C96dD1C2c63788C2"), testclient)
if err != nil {
fmt.Println("failed to get contract instance", err)
return err
}
data,err := instance.BankName(nil)
if err != nil {
fmt.Println("failed to get Balances", err)
return err
}
fmt.Println(data,err)
return nil
} func CallWithdraw(addr, pass string) (*types.Transaction, error) { instance, err := NewPdbank(common.HexToAddress("0xD55E88D9156355C584982Db2C96dD1C2c63788C2"), testclient)
if err != nil {
fmt.Println("failed to get contract instance", err)
return nil, err
}
auth, err := MakeAuth(addr, pass)
if err != nil {
fmt.Println("failed to makeAuth", err)
return nil, err
}
auth.Value = big.NewInt(0)
ts,err := instance.Withdraw(auth,big.NewInt(10000))
if err != nil {
fmt.Println("failed to call ", err)
return nil, err
}
fmt.Println(ts.ChainId(), ts.Hash().Hex(), ts.Nonce())
return ts , err
}

这样代码看上去舒服多了,接下来考虑目标代码的生成办法。

go语言与模版编程

go语言模版编程需要用到template包,go语言的模版呢实际上也提供了两大类模版处理。一类是文本的,一类是html的。鉴于我们的目标代码是go语言,所以本次使用基于文本的模版处理。

下面是一个最简单的模版处理的例子,{{.Count}}和{{.Material}}相当于是这个模版要填的两个空。

type Inventory struct {
Material string
Count uint
}
sweaters := Inventory{"wool", 17}
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
if err != nil { panic(err) }
err = tmpl.Execute(os.Stdout, sweaters)
if err != nil { panic(err) }
  • New 方法创建一个模版
  • Parse 是指定模版的内容
  • Execute 是执行,两个参数分别是写入的方向以及模版实例化的内容

一个复杂一些的例子

// Define a template.
const letter = `
Dear {{.Name}},
{{if .Attended}}
It was a pleasure to see you at the wedding.{{else}}
It is a shame you couldn't make it to the wedding.{{end}}
{{with .Gift}}Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
` // Prepare some data to insert into the template.
type Recipient struct {
Name, Gift string
Attended bool
}
var recipients = []Recipient{
{"Aunt Mildred", "bone china tea set", true},
{"Uncle John", "moleskin pants", false},
{"Cousin Rodney", "", false},
} // Create a new template and parse the letter into it.
t := template.Must(template.New("letter").Parse(letter)) // Execute the template for each recipient.
for _, r := range recipients {
err := t.Execute(os.Stdout, r)
if err != nil {
log.Println("executing template:", err)
}
}

我们可以发现一些特点:

  • {{.Name}}这样的pipeline(官方学名)一定能在结构体中找到这个名字
  • 可以使用if和with做流程处理

于是把我们的目标代码,可以制作成定制化的模版!

创建一个用于存放模版定义的文件,template_code.go

package templates

const Main_tmpl = `package main

import (
"fmt"
"log"
"os" "gosol/contracts"
"io/ioutil"
"math/big"
"strings" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
) var testclient *ethclient.Client func init() {
cli, err := CreateCli("{{.Connstr}}")
if err != nil {
log.Panic("failed to connect to eth", err)
}
testclient = cli
} func GetFileName(address, dirname string) (string, error) { data, err := ioutil.ReadDir(dirname)
if err != nil {
fmt.Println("read dir err", err)
return "", err
}
for _, v := range data {
if strings.Index(v.Name(), address) > 0 {
//代表找到文件
return v.Name(), nil
}
} return "", nil
} //创建链接
func CreateCli(connstr string) (*ethclient.Client, error) {
cli, err := ethclient.Dial(connstr)
if err != nil {
fmt.Println("failed to dial provide", err)
return nil, err
}
return cli, err
} //设置签名
func MakeAuth(addr, pass string) (*bind.TransactOpts, error) {
keystorePath := "{{.Keydir}}"
fileName, err := GetFileName(string([]rune(addr)[2:]), keystorePath)
if err != nil {
fmt.Println("failed to GetFileName", err)
return nil, err
} file, err := os.Open(keystorePath + "/" + fileName)
if err != nil {
fmt.Println("failed to open file ", err)
return nil, err
}
auth, err := bind.NewTransactor(file, pass)
if err != nil {
fmt.Println("failed to NewTransactor ", err)
return nil, err
}
return auth, err
}
` const Deploy_sol_tmpl = `
func Deploy{{.ContractName}}() (common.Address, error) {
auth, err := MakeAuth("{{.FromAddr}}", "{{.Pass}}")
if err != nil {
fmt.Println("failed to makeAuth", err)
return common.HexToAddress(""), err
} //common.Address, *types.Transaction, *Pdbank, error
contractaddr, ts, _, err := contracts.{{.CallFunc}}
if err != nil {
fmt.Println("failed to deloy ",err)
return common.HexToAddress(""), err
}
fmt.Println(ts.ChainId(), ts.Hash().Hex(), ts.Nonce())
fmt.Println(contractaddr.Hex())
return contractaddr, err
} `

先搞定部署合约代码自动生成部分!

//1. 写到哪
outfile, err := os.OpenFile("build/solcall.go", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
if err != nil {
fmt.Println("failed to open file", err)
return err
}
defer outfile.Close()
//2. 写什么
_, err = outfile.WriteString(test_main_temp)
if err != nil {
fmt.Println("failed to write ", err)
return err
}
// 读取abi文件信息
abiInfos, err := readAbi("contracts/pdbank.abi")
if err != nil {
fmt.Println("failed to read abi", err)
return err
}
//fmt.Println(infos) //3. 写入部署合约代码
//定义部署模版
deploy_temp, err := template.New("deploy").Parse(test_deploy_temp)
if err != nil {
fmt.Println("failed to template deploy", err)
return err
}
var deploy_data DeployContractParams
deploy_data.DeployName = "DeployPdbank" for _, v := range abiInfos {
v.Name = strings.Title(v.Name) //标题优化,首字母大写, hello world - > Hello World
if v.Type == "constructor" {
// 如果是构造函数-部署函数
deploy_data.DeployParams = "(auth,testClient"
for _, vv := range v.Inputs {
//需要根据输入数据类型来判断如何处理:string,address,uint256
if vv.Type == "address" {
deploy_data.DeployParams += ",common.HexToAddress(\"0xD55E88D9156355C584982Db2C96dD1C2c63788C2\")"
} else if vv.Type == "uint256" {
deploy_data.DeployParams += ",big.NewInt(1000)"
} else if vv.Type == "string" {
deploy_data.DeployParams += ",\"yekai\""
} }
deploy_data.DeployParams += ")"
//模版的执行
err = deploy_temp.Execute(outfile, &deploy_data)
if err != nil {
fmt.Println("failed to template Execute ", err)
return err
}
}
}

接下来的代码还是相同的套路。

完整代码如下;

//temp_org.go
package templates const test_main_temp = `
package main import (
"fmt"
"gosolkit/contracts"
"io/ioutil"
"log"
"math/big"
"os"
"strings" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
) var testClient *ethclient.Client func init() {
cli, err := Connect("http://localhost:8545")
if err != nil {
log.Fatalln("failed to connect to eth", err)
}
testClient = cli
} func Connect(connstr string) (*ethclient.Client, error) {
return ethclient.Dial(connstr)
} //签名函数
func MakeAuth(addr, pass string) (*bind.TransactOpts, error) {
//1. 根据addr找到keystore目录下的文件
keyDir := "/Users/yk/ethdev/data/keystore"
infos, err := ioutil.ReadDir(keyDir)
if err != nil {
fmt.Println("failed to readdir", err)
return nil, err
}
//UTC--2019-03-16T13-00-48.032030904Z--791443d21a76e16cc510b6b1684344d2a5ce751c
//0x791443d21a76e16cc510b6b1684344d2a5ce751c
strAddr := ([]rune(addr))[2:]
for _, v := range infos {
strVname := []rune(v.Name())
if len(strVname) > len(strAddr) {
strVname2 := strVname[len(strVname)-len(strAddr):]
if strings.EqualFold(string(strAddr), string(strVname2)) {
//找到了匹配的文件
//fmt.Println(addr, v.Name())
//2. 做签名
reader, err := os.Open(keyDir + "/" + v.Name())
if err != nil {
fmt.Println("failed to open file", err)
return nil, err
}
defer reader.Close()
auth, err := bind.NewTransactor(reader, pass)
if err != nil {
fmt.Println("failed to NewTransactor auth", err)
return nil, err
}
return auth, err
}
}
} return nil, nil
}
`
const test_deploy_temp = `
func CallDeploy() error {
//创建身份,需要私钥= pass+keystore文件
auth, err := MakeAuth("0x791443d21a76e16cc510b6b1684344d2a5ce751c", "123")
if err != nil {
fmt.Println("failed to MakeAuth auth", err)
return err
}
addr, ts, _, err := contracts.{{.DeployName}}{{.DeployParams}}
if err != nil {
fmt.Println("failed to DeployPdbank", err)
return err
} fmt.Println("addr=", addr.Hex(), "hash=", ts.Hash().Hex())
return err
}
` const test_nogas_temp = `
func Call{{.FuncName}}() error { //使用之前部署得到的合约地址
instance, err := contracts.{{.NewContractName}}(common.HexToAddress("0xD55E88D9156355C584982Db2C96dD1C2c63788C2"), testClient)
if err != nil {
fmt.Println("failed to instance contract", err)
return err
}
//调用合约函数
{{.OutParams}} := instance.{{.FuncName}}{{.InputParams}}
fmt.Println({{.OutParams}}) return err
}
` const test_gas_temp = `
func Call{{.FuncName}}() error { //2. 构造函数入口 - 合约对象
instance, err := contracts.{{.NewContractName}}(common.HexToAddress("0xD55E88D9156355C584982Db2C96dD1C2c63788C2"), testClient)
if err != nil {
fmt.Println("failed to contract instance", err)
return err
}
//3. 设置签名
auth, err := MakeAuth("0x791443d21a76e16cc510b6b1684344d2a5ce751c", "123")
if err != nil {
fmt.Println("failed to MakeAuth auth", err)
return err
}
//4. 函数调用
auth.Value = big.NewInt(0)
ts, err := instance.{{.FuncName}}{{.InputParams}}
if err != nil {
fmt.Println("failed to Deposit ", err)
return err
}
fmt.Println(ts.Hash().Hex())
return err
}
`
//temp_main.go
package templates const test_run_main_temp = `
package main
import (
"fmt"
"os"
)
var funcNames = []string{%s}
` //1. 提供一个命令行帮助
const test_build_main_temp = `
func Usage() {
fmt.Printf("%s 1 -- deploy\n", os.Args[0])
num := 2
for _, v := range funcNames {
fmt.Printf("%s %d -- %s\n", os.Args[0], num, v)
num++
}
}
func main() {
if len(os.Args) < 2 {
Usage()
os.Exit(0)
}
if os.Args[1] == "1" {
CallDeploy()
}{{range.}} else if os.Args[1] == "{{.Num}}" {
Call{{.FuncName}}()
} {{end}}
}
`
//temp_impl.go
package templates import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"text/template"
) type DeployContractParams struct {
DeployName string
DeployParams string
} //无gas函数调用
type FuncNoGasParams struct {
FuncName string
NewContractName string
OutParams string
InputParams string
} //有gas函数调用
type FuncGasParams struct {
FuncName string
NewContractName string
InputParams string
} type InputsOutPuts struct {
Name string
Type string
} type FuncInfo struct {
FuncName string
Num int
} type AbiInfo struct {
Constant bool
Inputs []InputsOutPuts
Name string
Outputs []InputsOutPuts
Payable bool
StateMutability string
Type string
} func readAbi(abifile string) ([]AbiInfo, error) {
file, err := os.Open(abifile)
if err != nil {
fmt.Println("failed to open file ", err)
return nil, err
}
data, err := ioutil.ReadAll(file)
if err != nil {
fmt.Println("failed to read abi", err)
return nil, err
}
var abiInfos []AbiInfo
strdata := strings.Replace(string(data), "\\", "", -1)
err = json.Unmarshal([]byte(strdata), &abiInfos)
if err != nil {
fmt.Println("failed to Unmarshal abi", err)
return nil, err
}
return abiInfos, err
} func Impl_run_code() error {
//1. 写到哪
outfile, err := os.OpenFile("build/solcall.go", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
if err != nil {
fmt.Println("failed to open file", err)
return err
}
defer outfile.Close()
//2. 写什么
_, err = outfile.WriteString(test_main_temp)
if err != nil {
fmt.Println("failed to write ", err)
return err
}
// 读取abi文件信息
abiInfos, err := readAbi("contracts/pdbank.abi")
if err != nil {
fmt.Println("failed to read abi", err)
return err
}
//fmt.Println(infos) //3. 写入部署合约代码
//定义部署模版
deploy_temp, err := template.New("deploy").Parse(test_deploy_temp)
if err != nil {
fmt.Println("failed to template deploy", err)
return err
}
var deploy_data DeployContractParams
deploy_data.DeployName = "DeployPdbank" //定义nogas函数的模版
nogas_temp, err := template.New("nogas").Parse(test_nogas_temp)
if err != nil {
fmt.Println("failed to template nogas_temp", err)
return err
} var func_nogas_data FuncNoGasParams
func_nogas_data.NewContractName = "NewPdbank" //定义有gas模版
hasgas_temp, err := template.New("hasgas").Parse(test_gas_temp)
if err != nil {
fmt.Println("failed to template hasgas_temp", err)
return err
} var func_gas_data FuncGasParams
func_gas_data.NewContractName = "NewPdbank" //对abi进行遍历处理
for _, v := range abiInfos {
v.Name = strings.Title(v.Name) //标题优化,首字母大写, hello world - > Hello World
if v.Type == "constructor" {
// 如果是构造函数-部署函数
deploy_data.DeployParams = "(auth,testClient"
for _, vv := range v.Inputs {
//需要根据输入数据类型来判断如何处理:string,address,uint256
if vv.Type == "address" {
deploy_data.DeployParams += ",common.HexToAddress(\"0xD55E88D9156355C584982Db2C96dD1C2c63788C2\")"
} else if vv.Type == "uint256" {
deploy_data.DeployParams += ",big.NewInt(1000)"
} else if vv.Type == "string" {
deploy_data.DeployParams += ",\"yekai\""
} }
deploy_data.DeployParams += ")"
//模版的执行
err = deploy_temp.Execute(outfile, &deploy_data)
if err != nil {
fmt.Println("failed to template Execute ", err)
return err
}
} else {
//处理其他函数
if len(v.Outputs) > 0 {
//不需要gas函数
func_nogas_data.FuncName = v.Name func_nogas_data.InputParams = "(nil"
for _, vv := range v.Inputs {
//需要根据输入数据类型来判断如何处理:string,address,uint256
if vv.Type == "address" {
func_nogas_data.InputParams += ",common.HexToAddress(\"0xD55E88D9156355C584982Db2C96dD1C2c63788C2\")"
} else if vv.Type == "uint256" {
func_nogas_data.InputParams += ",big.NewInt(1000)"
} else if vv.Type == "string" {
func_nogas_data.InputParams += ",\"yekai\""
} }
func_nogas_data.InputParams += ")"
//输入参数
num := 0
strOutPuts := ""
for _, _ = range v.Outputs {
strOutPuts = fmt.Sprintf("%sdata%d,", strOutPuts, num)
num++
}
strOutPuts += "err"
func_nogas_data.OutParams = strOutPuts //模版的执行
err = nogas_temp.Execute(outfile, &func_nogas_data)
if err != nil {
fmt.Println("failed to template nogas Execute ", err)
return err
}
} else {
//需要消耗gas
func_gas_data.FuncName = v.Name
func_gas_data.InputParams = "(auth"
for _, vv := range v.Inputs {
//需要根据输入数据类型来判断如何处理:string,address,uint256
if vv.Type == "address" {
func_gas_data.InputParams += ",common.HexToAddress(\"0xD55E88D9156355C584982Db2C96dD1C2c63788C2\")"
} else if vv.Type == "uint256" {
func_gas_data.InputParams += ",big.NewInt(1000)"
} else if vv.Type == "string" {
func_gas_data.InputParams += ",\"yekai\""
} }
func_gas_data.InputParams += ")"
//模版的执行
err = hasgas_temp.Execute(outfile, &func_gas_data)
if err != nil {
fmt.Println("failed to template hasgas Execute ", err)
return err
}
}
}
} return nil
} func Impl_main_code() error {
//1. 写到哪
outfile, err := os.OpenFile("build/main.go", os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
if err != nil {
fmt.Println("failed to open file", err)
return err
}
defer outfile.Close()
// 读取abi文件信息
abiInfos, err := readAbi("contracts/pdbank.abi")
if err != nil {
fmt.Println("failed to read abi", err)
return err
}
funcNames := ""
//"abc","def","
num := 0
var funcInfos []FuncInfo
var funcInfo FuncInfo
// 2- 第一个函数
for _, v := range abiInfos {
if v.Type != "constructor" {
if num == 0 {
//第一个
funcNames += fmt.Sprintf(`"%s"`, v.Name)
} else {
funcNames += fmt.Sprintf(`,"%s"`, v.Name)
}
num++
funcInfo.FuncName = strings.Title(v.Name)
funcInfo.Num = num + 1
funcInfos = append(funcInfos, funcInfo)
}
}
main_str1 := fmt.Sprintf(test_run_main_temp, funcNames)
_, err = outfile.WriteString(main_str1)
if err != nil {
fmt.Println("failed to write to main.go", err)
return err
} //建立一个模版,输出内容
main_temp, err := template.New("main").Parse(test_build_main_temp)
if err != nil {
fmt.Println("failed to template main", err)
return err
}
err = main_temp.Execute(outfile, funcInfos)
if err != nil {
fmt.Println("failed to Execute main", err)
return err
}
return err
} func Run() {
Impl_run_code()
Impl_main_code()
}

在main函数内增加此部分调用

package main

import (
"fmt"
"gosolkit/templates"
"os"
) func Usage() {
fmt.Printf("%s 1 -- compiler code\n", os.Args[0])
fmt.Printf("%s 2 -- build test code\n", os.Args[0])
} func main() {
if len(os.Args) < 2 {
Usage()
os.Exit(0)
}
if os.Args[1] == "1" {
CompilerRun()
} else if os.Args[1] == "2" {
//build test code
templates.Run()
} else {
Usage()
os.Exit(0)
} }

如果觉得代码看着太累,可以来看视频教程

Go语言打造以太坊智能合约开发框架


Go语言打造以太坊智能合约测试框架(level3)的更多相关文章

  1. Go语言打造以太坊智能合约测试框架(level1)

    传送门: 柏链项目学院 Go语言打造以太坊智能合约测试框架 前言 这是什么? 这是一个基于go语言编写的,自动化测试以太坊智能合约的开发框架,使用此框架,可以自动化的部署合约,自动测试合约内的功能函数 ...

  2. Go语言打造以太坊智能合约测试框架(level2)

    传送门: 柏链项目学院 第二课 智能合约自动化编译 前期内容回顾 之前我们的介绍的是如何通过solc编译智能合约,并且调用智能合约,本节我们继续实践,将智能合约的代码自动化编译以及abi文件生成搞定. ...

  3. 以太坊智能合约Hello World示例程序

    简介 以太坊(Ethereum)是一提供个智能合约(smart contract)功能的公共区块链(BlockChain)平台. 本文介绍了一个简单的以太坊智能合约的开发过程. 开发环境 在以太坊上开 ...

  4. 以太坊智能合约 Solidity 的常用数据类型介绍

    目录 目录 1.数组 1.1.对数组的增删改查操作. 2.String.Bytes.Mapping的使用 3.Enums 和 Structs 的简单应用 4.Ether 单位和 Time 单位 5.A ...

  5. 如何通过以太坊智能合约来进行众筹(ICO)

    前面我们有两遍文章写了如何发行代币,今天我们讲一下如何使用代币来公开募资,即编写一个募资合约. 写在前面 本文所讲的代币是使用以太坊智能合约创建,阅读本文前,你应该对以太坊.智能合约有所了解,如果你还 ...

  6. 以太坊智能合约介绍,Solidity介绍

    以太坊智能合约介绍,Solidity介绍 一个简单的智能合约 先从一个非常基础的例子开始,不用担心你现在还一点都不了解,我们将逐步了解到更多的细节. Storage contract SimpleSt ...

  7. 深入以太坊智能合约 ABI

    开发 DApp 时要调用在区块链上的以太坊智能合约,就需要智能合约的 ABI.本文希望更多了解 ABI,如为什么需要 ABI?如何解读 Ethereum 的智能合约 ABI?以及如何取得合约的 ABI ...

  8. rpc接口调用以太坊智能合约

    rpc接口调用以太坊智能合约 传送门: 柏链项目学院   在以太坊摸爬滚打有些日子了,也遇到了各种各样的问题.这几天主要研究了一下如何通过rpc接口编译.部署和调用合约.也遇到了一些困难和问题,下面将 ...

  9. 使用web3.js监听以太坊智能合约event

    传送门: 柏链项目学院 使用web3.js监听以太坊智能合约event   当我们在前端页面调用合约时发现有些数据不会立即返回,这时还需要再调用更新数据的函数.那么这样的方法使用起来非常不便,监听ev ...

随机推荐

  1. Windows Server 2016 安装虚拟机版黑群晖

    硬件配置 Dell R730 CPU: Intel(R) Xeon(R) CPU E5-2603 v4 @1.70GHz(6 cores) Ram: 16Gb HDD: 系统-600GB SAS X2 ...

  2. 关于OSError: [WinError 10038] 在一个非套接字上尝试了一个操作。

    在使用socket的时候,写了一个while循环,就报错了.结果如下: OSError: [WinError 10038] 在一个非套接字上尝试了一个操作. 代码 import socket impo ...

  3. 多线程总结之旅(1):线程VS进程

    一.进程:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,也就是应用程序的执行实例,进程是系统进行资源分配和调度的一个独立单位.每个进程是由私有的虚拟地址空间.代码.数据和其它各种系统资 ...

  4. 在linux系统部署Svn

      步骤一:安装subversion 1) 连接xshell,在xshell输入命令:yum install subversion 2) 查看安装svn服务的版本:svnserve --version ...

  5. 快速构建SPA框架SalutJS--项目工程目录 一

    起因 刚进公司那会儿,接的是一个微信APP应用,SPA是前人搭起来的,用到的技术主要是backbone和zepto.后来那人走了,就卤煮一个人把项目接了下来.项目越是到后面,越发觉了诸多弊端,不停的增 ...

  6. 双因素认证(2FA)教程

    所谓认证(authentication)就是确认用户的身份,是网站登录必不可少的步骤. 密码是最常见的认证方法,但是不安全,容易泄露和冒充. 越来越多的地方,要求启用 双因素认证(Two-factor ...

  7. 永久更改pip源和创建虚拟环境

    一.永久更改pip源 1.windows 编辑%APPDATA%/pip中的pip.ini(若无则新建): C:\Users\v_xmmxhli λ cd %APPDATA%/pip C:\Users ...

  8. Vue(day6)

    一.webpack中常用的文件loader & 插件 由于版本存在变动,以下安装和配置都有可能发生变化,请以官方文档为准. 1.html-webpack-plugin插件 html-webpa ...

  9. python进程和线程(六)

    协程 协程,又称微线程,纤程.英文名Coroutine.顾名思义,协程是协作式的,也就是非抢占式的程序(线程是抢占式的).协程的关键字是yield,一看到这个就想到了生成器对不对?那就顺便回顾一下生成 ...

  10. Java的数组,栈,队列

    import java.util.Arrays; public class Array<E> { private E[] data; private int size; //构造函数,传入 ...