go合约

276 阅读3分钟

概述

本项目为go示例合约,实现了对key/value对的增删查改的功能

开发环境

go版本:不限
IDE:goland或Idea

合约项目目录说明

src目录:存放合约代码。需要编写的文件为contract.go、msg.go、state.go。

contract.go存放合约各方法的实现。msg.go存放合约接收消息的定义。state.go存放合约使用的存储定义和数据结构定义。

合约主体

智能合约模块支持灵活定义合约方法并对外暴露,以提供区块链模块执行,具体步骤如下:
1. 在msg.go中必须定义合约接收的消息类型

type InstantiateMsg struct{} //合约实例化时调用

type ExecuteMsg struct {//合约执行时调用
Create *Create json:"create"

}

type Create struct {
Item *Item json:"item"
}

type QueryMsg struct {// 合约查询时可调用
Find *Find json:"find"
}

type Find struct {
Key string json:"key"
}

type MigrateMsg struct{}// 合约升级时调用

type QueryResponse struct {
Item *Item json:"item"
}

2. 在state.go中定义合约用到的存储和数据结构

引入存储的定义,不同存储有不同的Key。

type Item struct {
Key string json:"key"
Value string json:"value"
}

读写存储的定义:

func LoadItem(storage std.Storage, key string) (*Item, error) {
data := storage.Get([]byte(key))
if data == nil {
return nil, errors.New("item not found")
}

var item Item
err := item.UnmarshalJSON(data)
if err != nil {
return nil, err
}
return &item, nil
} //读权限的存储访问

func SaveItem(storage std.Storage, item *Item) error {
bz, err := item.MarshalJSON()
if err != nil {
return err
}

storage.Set([]byte(item.Key), bz)

return nil
}//写权限的存储访问

在contract.go中可以通过如下方式调用:

value, err := LoadItem(deps.Storage, msg.Key)//查询

err := SaveItem(deps.Storage, create.Item)//存储

3. 在contract.go中写合约各消息类型的实现方法

合约结构体必须包含Instantiate合约方法,用于合约初始化时执行相应逻辑

func Instantiate(_ *std.Deps, _ types.Env, _ types.MessageInfo, _ []byte) (*types.Response, error) {
return &types.Response{}, nil
}

合约结构体必须包含Execute合约方法,用于合约执行时处理收到相应消息类型。

func Execute(deps *std.Deps, env types.Env, info types.MessageInfo, data []byte) (*types.Response, error) {
msg := ExecuteMsg{}
err := msg.UnmarshalJSON(data)
if err != nil {
return nil, err
}

switch {
case msg.Create != nil:
return executeCreate(deps, env, info, msg.Create)

default:
return nil, types.GenericError("Unknown HandleMsg")
}
}

合约结构体必须包含Query合约方法,用于合约查询时处理收到相应消息类型。

func Query(deps *std.Deps, _ types.Env, data []byte) ([]byte, error) {
msg := new(QueryMsg)
err := msg.UnmarshalJSON(data)
if err != nil {
return nil, err
}

var res std.JSONType
switch {
case msg.Find != nil:
res, err = query(deps, msg.Find)
default:
err = types.GenericError("Unknown QueryMsg")
}
if err != nil {
return nil, err
}

bz, err := res.MarshalJSON()
if err != nil {
return nil, err
}
return bz, nil
}

4. 实现方法

合约本质上是对链存储的操作。

image.png

合约操作原语包含对Storage的增删查改,Storage的定义如上所示。
常用接口如下:
1.func (s ExternalStorage) Get(key []byte) (value []byte) {} :数据查询接口
2.func (s ExternalStorage) Range(start, end []byte, order Order) (iter Iterator) {} :范围查询接口 3.func (s ExternalStorage) Set(key, value []byte) {} : 数据存储接口
4.func (s ExternalStorage) Remove(key []byte) {} :数据删除接口

5. 合约与链的交互

获取链相关信息

合约实现方法中包含参数Env,其结构如下所示

type Env struct {
Block BlockInfo
Contract ContractInfo
Transaction *TransactionInfo json:"transaction_info,omitempty"
}

type BlockInfo struct {
Height uint64 json:"height"
Time uint64 json:"time,string"
ChainID string json:"chain_id"
}

type ContractInfo struct {
Address string
}

type TransactionInfo struct {
Index uint32 json:"index"
}

如在合约方法中获取合约调用者地址可以如下操作:

sender:=types.MessageInfo.Sender

合约方法返回消息类型定义如下:

type Response struct {

Messages []SubMsg json:"messages,emptyslice"

Data []byte json:"data,omitempty"

Attributes []EventAttribute json:"attributes,emptyslice"

Events []Event json:"events,emptyslice" }

其中messages用于跨模块与跨合约操作

Attributes用于写日志

data用于返回值供链使用
示例如下: 跨合约调用

func (r Response) AddWasmMsg(contractName string, msg []byte) Response {
	m := CosmosMsg{
		Wasm: &WasmMsg{
			Execute: &ExecuteMsg{
				ContractAddr: contractName,
				Msg:          msg,
			},
		},
	}
	sm := NewSubMsg(m)
	r.Messages = append(r.Messages, sm)
	return r
}

将合约的EventAttribute打印到Attributes

func (r Response) AddAttribute(key string, value string) Response {
   m := EventAttribute{
   	key,
   	value,
   }
   r.Attributes = append(r.Attributes, m)
   return r
}

将b返回给链,链可以获取到b进行其他操作

func (r Response) SetData(b []byte) Response {
 r.Data = b
 return r
}