Substrate Contract SDK for Golang As a part of Himalia
PatractGo is a Golang contract SDK. In addition to supporting the basic interactive API with the contract, it mainly supports the development of micro-services based on the contract status. For example, for common ERC20 contracts, a service can be developed based on PatractGo to synchronize all transfer information to the database, and based on the database to implemente the statistics and processing of transfer data. In addition, developers can also develop some command-line tools based on Golang to assist in testing and debugging.
PatractGo will be based on Centrifuge's GSRPC, which is a Go sdk for Substrate.
Element Group for disscusion: https://app.element.io/#/room/#PatractLabsDev:matrix.org
Most contract behaviors are highly related to context. In addition to interacting with the chain, user-oriented contract applications also need to provide users with current relevant context status information:
+--DAPP-Front-End--------------+ +---Chain-------------------------+
| | | |
| +----+ +------------------+ | | +-------+ +-------+ |
| | | | | | Commit | | | | | |
| | | | Polkadot-JS +------------> Node +---->+ Node | |
| | +->+ | | Tx | | | | | |
| | | | | | | +-------+ +----+-++ |
| | | +------------------+ | | ^ | |
| | UI | | +---------------------------------+
| | | +------------------+ | | |
| | | | | | +--DAPP-Server--------------------+
| | | | | | Push | +--------+ +-----v-------+ |
| | +<-+ Model +<-----------+ +-----+ | |
| | | | | | | | Server | | PatractGo | |
| | | | +------------> +-----+ | |
| +----+ +------------------+ | Query | +----+---+ +-----+-------+ |
+------------------------------+ | | | |
| | +-----v-------+ |
| | | | |
| +-------->+ DataBase | |
| | | |
| +-------------+ |
| |
+---------------------------------+
PatractGo is mainly responsible for implementing micro-services in a DApp. Unlike querying the state of the chain API, PatractGo can monitor the calls and events generated by the specified contract. Developers can obtain the state storage based on this information to maintain consistent state with the chain. Through data services based on a typical API-DB architecture, the front-end DApp can efficiently and concisely obtain the state on the chain as context information.
Based on the API of chain nodes, PatractGo obtains block information and summarizes and filters it, and sends contract-related messages and events based on metadata analysis to the handler protocol specified by the developer. For example, for a typical ERC20 contract, the developer can use the channel to subscribe to all transfer events that occur, and then synchronize them into the database, so that other microservices can provide services corresponding to the token data of the account, such as querying the current token holding distribution and other logics.
Therefor, PatractGo will achieve the following support:
- Complete the secondary packaging of the contract module interface, complete operations such as
put_code
,call
,instantiate
, etc. - Parse the metadata.json information of the contract, and support the automatic generation of http service interface for the metadata corresponding contract
- Scanning and monitoring support of the contract status on the chain for statistics and analysis
- Basic command line tool support for native interaction with the contract, mainly used to test the security of the contract
- SDK development examples for ERC20 contract support
PatractGo based on GSRPC, So we need install some depends:
First is subkey(This dependency could be removed after GSRPC marge pr#114):
cargo install --force subkey --git https://github.com/paritytech/substrate --version 2.0.0
subkey --version
For Now, the sdk examples will connect to the europa, and also need cli tools:
> git clone --recurse-submodules https://github.com/patractlabs/europa.git
## or do following commands
> git clone https://github.com/patractlabs/europa.git
> cd europa/vendor
> git submodule update --init --recursive
> cargo build
For some examples, we can simply run a europa node:
europa --dev --tmp
use for external port:
europa --dev --tmp --ws-external
We advise developers to use jupiter for debugging their code.
# build jupiter
git clone --recurse-submodules https://github.com/patractlabs/jupiter.git
cd jupiter
cargo build
For jupiter or europa, please read their README to provide their suitable "extending types" for go sdk. More information refers to go-substrate-rpc-client#hdr-Types
PatractGo consists of the following packages:
patractgo/metadata
contract metadata processing, and metadata-based contract processingpatractgo/rpc/native
re-encapsulation of the contract module interface to provide the contract-related interaction based on chain RPCpatractgo/rpc
implement the interaction with the contract based on metadatapatractgo/rest
implements an http service based on metadata to interact with the contractpatractgo/observer
Monitoring and Scanning support for contract status on the chainpatractgo/contracts/erc20
supports ERC20 contracts and examplespatractgo/tools
some tools for contracts develop
Currently, we haven't designed the module which could auto-gen code for a contract based on a metadata, thus we provide
patractgo/contracts/erc20
as an example to show how to warp a contract as a go source file.
This auto contract code generator feature would be developed with java-patract
repo later (in next version), for their have same logic to generate the code for contracts.
PatractGo use go test
to test, but as need europa environments, so need run test in one process:
Test Environment need add europa
to bash PATH.
go test -v -p 1 ./...
The unittest will start a europa process for test, if need use a europa start by localhost, can use this:
europa --dev --tmp --ws-external
go test ./contracts/erc20/ -v -p 1 -v -args "extern" -run TestTransfer
This will run TestTransfer
test to europa
PatractGo
divides into 3 parts:
- Contracts: provides the functions to react with contracts.
- Rest: provides a way to generate an offline signature for contracts.
- Observer: listens contract events and parse events by contract metadata.
Also can read: Transfer test.
Currently, contracts
part just parses metadata and uniforms interface to react with contracts. Based on the generic interface,
the auto contract code generator could be implemented easily in the future. Thus, if developers want to react with their contracts by PatractGo
,
they need to create some wrapper functions for contracts, just like what PatractGo
do in contracts/erc20
.
This process is duplicated for every contract, we would provide auto contract code generator to simplify this process.
Put Code:
// read the code wasm from file
codeBytes, err := ioutil.ReadFile("/path/to/contracts.wasm")
if err != nil {
return err
}
// create the api
cApi, err := rpc.NewContractAPI(env.URL())
// read the abi(metadata) for contract
metaBz, err := ioutil.ReadFile("/path/to/contracts_metadata.json")
cApi.WithMetaData(metaBz)
// create context with from auth, like Alice
ctx := api.NewCtx(context.Background()).WithFrom(authKey)
// put code
_, err = cApi.Native().PutCode(ctx, codeBytes)
// do next steps
Get Code:
codeHash := readCodeHash() // get code hash
var codeBz []byte
if err := cApi.Native().Cli.GetStorageLatest(&codeBz,
"Contracts", "PristineCode",
[]byte(codeHash), nil); err != nil {
return err
}
// codeBz is now code
var endowment uint64 = 1000000000000
// Instantiate
_, contractAccount, err := cApi.Instantiate(ctx,
types.NewCompactBalance(endowment),
types.NewCompactGas(test.DefaultGas),
contracts.CodeHashERC20,
types.NewU128(totalSupply),
)
api will return contractAccount, which can use to call the contract.
For a contract, we can read or exec messages:
Read:
Read the total_supply
of ERC20 contract, no request params:
var res types.U128
err := a.CallToRead(ctx,
&res,
a.ContractAccountID,
[]string{"total_supply"},
)
Read the balance_of
of AccountID for ERC20 contract:
req := struct {
Address types.AccountID
}{
Address: owner,
}
var res types.U128
err := a.CallToRead(ctx,
&res,
ContractAccountIDForERC20,
[]string{"balance_of"},
req,
)
Exec:
Call transfer
:
toParam := struct {
Address AccountID
}{
Address: to,
}
valueParam := struct {
Value U128
}{
Value: amt,
}
return a.CallToExec(ctx,
a.ContractAccountID,
types.NewCompactBalance(0),
types.NewCompactGas(test.DefaultGas),
[]string{"transfer"},
toParam, valueParam,
)
We can use rest
to get unsigned raw byte data for contract call, it can help to build an offline signature for contract.
can use this for example: rest
start the rest server:
go run ./examples/rest
to get data:
curl -X POST \
'http://localhost:8899/erc20/exec/transfer?isOffline=true&contract=5HKinTRKW9THEJxbQb22Nfyq9FPWNVZ9DQ2GEQ4Vg1LqTPuk' \
-H 'content-type: application/json' \
-d '{
"nonce":1,
"chain_status":{
"spec_version":1,
"tx_version":1,
"block_hash":"0xc20f241b61039e5685d118c7fbc8b27210153c21eee7686a9466f22e01281114",
"genesis_hash":"0xc20f241b61039e5685d118c7fbc8b27210153c21eee7686a9466f22e01281114"
},
"contract":"5HKinTRKW9THEJxbQb22Nfyq9FPWNVZ9DQ2GEQ4Vg1LqTPuk",
"origin":"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"gas_limit":"500000000000",
"args":{
"to":"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
"value":"100000000"
}
}'
For a contract, we need observer events for the contract, can use observer
to build a contract events observer service:
example: observer