概述
本项目为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 *Createjson:"create"}
type Create struct {
Item *Itemjson:"item"
}type QueryMsg struct {// 合约查询时可调用
Find *Findjson:"find"
}type Find struct {
Key stringjson:"key"
}type MigrateMsg struct{}// 合约升级时调用
type QueryResponse struct {
Item *Itemjson:"item"
}
2. 在state.go中定义合约用到的存储和数据结构
引入存储的定义,不同存储有不同的Key。
type Item struct {
Key stringjson:"key"
Value stringjson:"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. 实现方法
合约本质上是对链存储的操作。
合约操作原语包含对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 *TransactionInfojson:"transaction_info,omitempty"
}
type BlockInfo struct {
Height uint64json:"height"
Time uint64json:"time,string"
ChainID stringjson:"chain_id"
}
type ContractInfo struct {
Address string
}
type TransactionInfo struct {
Index uint32json:"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
}