GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - tinychain/dmcscript: A TypeScript to WebAssembly compiler 🚀

BranchesTags
Open more actions menu
 
 

Latest commit

 

History

723 Commits
723 Commits

Folders and files

NameName
Last commit message
Last commit date
.github
.github
 
 
bin
bin
 
 
cli
cli
 
 
dist
dist
 
 
examples
examples
 
 
lib
lib
 
 
media
media
 
 
scripts
scripts
 
 
snap
snap
 
 
src
src
 
 
std
std
 
 
tests
tests
 
 
.gitattributes
.gitattributes
 
 
.gitignore
.gitignore
 
 
.travis.yml
.travis.yml
 
 
CODE_OF_CONDUCT.md
CODE_OF_CONDUCT.md
 
 
CONTRIBUTING.md
CONTRIBUTING.md
 
 
LICENSE
LICENSE
 
 
NOTICE
NOTICE
 
 
README.md
README.md
 
 
gas扣除逻辑.png
gas扣除逻辑.png
 
 
index.d.ts
index.d.ts
 
 
index.js
index.js
 
 
package-lock.json
package-lock.json
 
 
package.json
package.json
 
 
tsconfig-base.json
tsconfig-base.json
 
 
tsconfig-docs.json
tsconfig-docs.json
 
 
tslint.json
tslint.json
 
 
webpack.config.js
webpack.config.js
 
 

Repository files navigation

Damoclis 智能合约开发文档

[TOC]

快速入门

安装

从Github拉取一份合约项目模板:

git clone https://github.com/damoclis/dmc-smart-contract-boilerplate

进入项目目录,安装依赖库:

npm install

全局安装dmcscript:

npm install dmcscript -g

编写Hello World合约

在src下,创建helloworld目录,并在该合约目录下新建一个合约文件helloworld.ts。

编写一个简单的Hello World合约,示例如下:

import { Contract } from "dmc-lib";

class HelloWorld extends Contract {
  // 定义合约方法`hi`
  @action
  hi(msg: string): void {
    // 打印日志
    Prints(msg);
  }
}

在上面的例子中,我们自定义了一个合约类HelloWorld,继承自内置合约类Contract。同时,声明了一个合约方法hi(msg:string):void。所有的合约方法均需要使用装饰器@action来声明,编译器将会解析并导出对应的abi。

Prints是内置API,作用是在运行节点的终端打印相关信息。

编译与部署

下面,我们使用dmcscript编译合约:

dsc src/helloworld/helloworld.ts  
  -o build/helloworld/helloworld.wasm 
  -t build/helloworld/helloworld.wat 
  --abi build/helloworld/helloworld.abi
  • wasm为合约的字节码文件,用于部署到区块链上。
  • wat为合约字节码的文本描述文件。
  • abi为合约的abi文件,用于部署到区块链上。

TODO 部署

资产查询与转移

我们尝试为上面的HelloWorld合约添加更丰富的功能,实现一个资产转移的例子:

import { Contract, RequireAuth, Prints, Address, Asset } from "dmc-lib";

class HelloWorld extends Contract {
  // 定义合约方法`hi`
  @action
  hi(msg: string): void {
  	// 打印日志
  	Prints(msg);
  }
  
  // 定义合约方法`transfer`
  @action
  transfer(from: Address, to: Address, val: Asset): void{
  	// 判断当前合约发送方地址是否与`from`参数一致
  	RequireAuth(from);
  	// 从`from`向`to`转移资产`val`
  	from.transfer(to, val);
  }
}

上面我们定义了一个transfer合约方法,用于将数值为val的资产从from转移到to。

这里我们涉及了两个重要的内置类型:

  • Address: 地址类型,用于表示账户地址并提供相关方法。
  • Asset: 资产类型,用于表示原生资产并提供相关方法。

RequireAuth(addr: Address)常用于判断操作的权限,若入参地址与本次合约调用的发送方不一致,则会终止合约执行。类似API还有hasAuth(addr: Address): bool,但它返回bool值,不会终止合约执行。例如,Bob发送了一笔交易调用了transfer合约方法,若入参from的地址与Bob不一致,则RequireAuth(from)将失败,终止合约。

数据持久化

数据持久化是智能合约必不可少的功能。我们希望合约中对数据的持久化是显式调用的,并与传统的数据库交互方式类似。

首先我们定义一个数据表Person。它是数据持久化的基本单元:

// 声明数据表person,定义每列数据的字段。底层为k-v型存储。
// 简单来说:在合约的数据表`person`中,存储若干数据格式为`Person`的记录。
@database("person")
class Person implements Serializable {
  @key
  name: string
  age: u32;
}

Serializable是内置的序列化接口,所有需要持久化的数据类均须声明实现该接口,但无需开发者手动实现,编译器会帮助自动补全代码。

在Damoclis中,合约层的数据库模型如下:

  • Contract
    • table 1
      • data1
      • data2
      • ...
      • dataN
    • table 2
    • table 3
    • ...

@database("person")装饰器用于表明该数据类存储在person表内,编译器会识别并生成对应ABI。

@key装饰器用于表明该数据项的主键。数据库底层实际为kv存储,k为@key声明的主键,v为整个数据对象的序列化字符串。

然后,在合约类中添加下列方法:

@action
store(): void {
	const person = new Person();
	person.name = "bob";
	person.age = 16;
	// 创建一个数据项实例
	// 第一个参数为数据库所在合约,第二个参数为数据表名称
	const db = new Database<Person>(this.receiver, "person");
	// 写操作只对合约本身的数据表生效
	db.store(person);
}

完整代码如下:

import { Contract, Database } from "../../src/database";

// 声明数据表person的数据类
@database("person")
class Person implements Serializable {
  @key
  name: string
  age: u32;
}

class DBTest extends Contract {
  _db: Database<Person>

  constructor() {
  	 // 初始化数据库实例
    this._db = new Database<Person>(this.self, "person");
  }

  @action
  store(): void {
    const person = new Person();
    person.name = "bob";
    person.age = 16;
    // 创建一个数据项实例
    // 写操作只对合约本身的数据表生效
    this._db.store(person);
  }
}

返回值

Damoclis设计了返回值机制,将合约的运行结果返回给区块链,并保存在交易回执中。

class ReturnTest extends Contract {
	// 定义add方法,返回一个u64类型的值。
	@action
	add(a: u64, b: 64): u64{
		return Safemath.add(a, b);
	}
}

对于合约方法return的类型,我们支持以下三种:

  • 整数类型:i32/i64,u32/u64。
  • 字符串类型: string。
  • 拥有bytes成员变量的复杂类型。如Bytes,Address, Asset或其他自定义类型。 //目前暂时只支持内置类型的返回

返回值以Bytes数组的形式保存在交易回执中。其数据格式为:| 1 byte flag | bytes |。flag == 0表示bytes部分是普通的bytes或字符串;flag == 1表示bytes部分是整数的大端字节数组,可进行相应解码。

跨合约调用

跨合约调用也是智能合约的必备功能。在Damoclis中,对合约的调用是以Action为单元。下面是一个调用其他合约的例子:

// contractA.ts

import { Contract, Action } from "dmc-lib";

class ContractA extends Contract {
	@action
	call(target: Address):void {
		// 构建需要发送的内联action
		const action = new Action(target, Asset.zero, "hi", [Builtin.fromString("hello")]);
		// 发送action,调用对方合约方法
		action.send();
	}
}

// contractB.ts

import { Contract, Prints } from "dmc-lib";

class ContractB extends Contract {
	@action
	hi(msg: string):void {
		Prints(msg);
	}
}

上面演示了contractA.call通过跨合约调用ContractB.hi的例子。这里我们提供了一个内置的Action类,用于表示一个action,其构造函数为Action(对方合约地址,发送资产数量,合约方法,[参数, ...]),详见Action一章。

Builtin是内置类型,用于创建可序列化的合约方法参数。参数数组仅支持一层的数组嵌套。详见Action一章。

上下文变化

Contract类初始化时会为三个成员变量复制:

  • sender - 交易发送方
  • contract - 合约调用方
  • receiver - 交易接收方

一次普通的交易调用合约,上述变量赋值按以下规则:

sender = 交易发送方
contract = receiver = ContractA的地址

在上面的例子中,跨合约调用将使对方合约的上下文更改如下:

sender = ContractA.sender
contract = ContractA的地址
receiver = ContractB的地址

核心概念

账户模型

在Damoclis中,所有账户的唯一标识是地址。地址由用户公钥取前20 Bytes生成。在合约中的权限检查本质上是比较地址是否一致。

交易结构

在Damoclis中,调用合约方法的最小单元是Action。一笔交易中可包含多个Action,执行中若有某个Action执行失败,则回滚整个交易。

在一笔交易执行中,可能会触发多个跨合约调用,我们称为inline_action。它们也被看做是正常的Action,并会在交易执行结束后,收集并保存在交易的inlineActions字段中(该字段不参与交易哈希值计算,故不会改变交易的id)。这么做使得所有对合约的变更操作都留下日志,方便日后的追溯。

代币单位

Damoclis中的原生代币dom,总量为100亿。它还有unit,munit,kunit三个单位,换算公式如下:

Symbol Value
unit 1
kunit $10^{3}$ unit
muint $10^{6}$ unit
dom $10^{8}$ unit
maximum $10^{10}$ dom
为了方便在合约中的使用,我们提供了Asset类,其构造函数提供两个入参:数量与单位。
const a1 = new Asset(1000, UNIT);
const a2 = new Asset(1, KUNIT);

a1 == a2; // true

dmc-lib文档

Contract

Contract类

为合约编译的入口,编写的合约必须继承Contract类。

使用示例

import {Contract} from "dmc-lib"

类属性

sender: Address;

receiver: Address;

contract: Address;

actionName: string;

get访问器 描述
get self(): Address 返回receiver字段

主要类方法

Action

方法

HasAuth(addr: Address): bool

HasAuth方法用于确认该地址是否为合约的sender。

RequireAuth(addr: Address): void

和HasAuth方法类似,但如果该地址不是合约的sender,则直接抛出异常。

Builtin类

Builtin类主要用于action中的payload等,方便数据的序列化传输。实现了Serializable接口。

使用示例

import {Builtin} from "dmc-lib"

类属性

_val: DataStream;

get访问器 描述
get datastream(): DataStream 返回类属性中的_val
get bytesLen(): u32 返回_val的长度

主要类方法

类方法 描述
constructor(val: DataStream) 根据指定的DataStream创建Builtin类
static fromI32(val: i32): Builtin 根据一个i32类型的值创建Builtin类
static fromU32(val: u32): Builtin 根据一个u32类型的值创建Builtin类
static fromI64(val: i64): Builtin 根据一个i64类型的值创建Builtin类
static fromU64(val: u64): Builtin 同上...
static fromI8(val: i8): Builtin
static fromU8(val: u8): Builtin
static fromI16(val: i16): Builtin
static fromU16(val: u16): Builtin
static fromVari32(value: i32): Builtin
static fromVaru32(val: u32): Builtin
static fromAddress(addr: Address): Builtin
static fromString(str: string): Builtin
static fromBytes(bytes: Bytes): Builtin
static fromHash160(bytes: Bytes): Builtin
static fromHash256(bytes: Bytes): Builtin
static fromHash512(bytes: Bytes): Builtin
static fromArray(params: Builtin[]): Builtin

注意:调用fromArray时,不支持嵌套的数组。

Action类

Action类中主要包含调用action相关的方法。实现了Serializable接口。

使用示例

import {Action} from "dmc-lib"

类属性

_to: Address;

_value: Asset;

_method: string;

_payload: Builtin[];

_extra: string;

主要类方法

类方法 描述
constructor(to: Address, value: Asset, method: string, payload: Builtin[] = [], extra: string = "") 构造一个Action
static getValue(): Asset 获取action的value字段
send(): void 发送当前action

注意:调用send时,方法名不得为"__DEPLOY__"。

Address

Address类

Address类主要是对于160位地址的封装。实现了Serializable接口。可以直接使用==与!=进行比较。

使用示例

import {Address} from "dmc-lib"

类属性

_value: Bytes;

_len: u32;

get访问器 描述
get bytes(): Bytes 返回类的字节形式
get buffer(): usize 返回地址字节存储_value的缓冲区地址

主要类方法

类方法 描述
static fromHex(hex: string): Address 从字符串构造Address
static fromBytes(raw: Bytes): Address 从字节数据构造Address
hex(): string 返回地址的16进制字符串形式
toString(): string 返回地址的字符串形式
getBalance(): Asset 获取当前地址的余额
transfer(to: Address, value: Asset): bool 向to所指地址转账,返回转账是否成功

Asset

Asset类

Asset类是对资产的封装,实现了Serializable接口。

使用示例

import {Asset} from "dmc-lib"

类属性

amount: u64

get访问器 描述
static get zero(): Asset 返回数值为0的资产
get bytes(): Bytes 返回类的字节形式

注意: amount为uint单位下的货币数量

主要类方法

类方法 描述
constructor(amt: u64 = 0, sy: u8 = UNIT) 根据传入的货币单位与数量构造Asset类

此外,Asset类支持的运算符操作包括:

  • 比较运算:>, >=, <, <=, ==, !=
  • 算数运算:+, -, *, /

Chain

Chain类

提供了一些获取区块链信息的方法。

使用示例

import {Chain} from "dmc-lib"

主要类方法

类方法 描述
static getBlockHash(height: u64): Bytes 获取区块哈希
static getBlockHeight(): u64 获取区块高度

Database

Database类

用于数据持久化存储。存储内容为key-value对。存储对象必须实现Serializable接口。

使用示例

import {Database} from "dmc-lib"

类属性

_contract: Address;

_table: string;

主要类方法

类方法 描述
constructor(contract: Address, table: string) 根据contract以及table构造一个Database类
get(key: string): T 根据key获取对应的value
store(obj: T): void 存储obj对象
update(obj: T): void 更新存储内容
exist(key: string): bool 判断key对应的value是否存在
iterator(): Iterator<T> 获取迭代器
remove(key: string): void 根据key删除对应的存储内容

Iterator类

用于对数据存储进行迭代。存储对象必须实现Serializable接口。

使用示例

import {Iterator} from "dmc-lib"

类属性

_db: Database<T>;

_itr: i32;

主要类方法

类方法 描述
constructor(db: Database<T>, itr: i32) 构造一个迭代器。迭代器通常通过Databse里的iterator方法获得。
get(): T 获取存储对象
next(): void 将迭代器指向下一个对象地址
end(): bool 判断是否迭代完毕

Print

方法

Prints(msg: string): void

打印出一串字符串。

Printi(val: i64): void

打印出一个i64类型的整数。

Printui(val: u64): void

打印出一个u64类型的整数。

PrintHex(data: Bytes): void

将字节串以16进制字符串的形式输出。

System

方法

Assert(test: bool, msg: string): void

断言,若test结果为false,会将msg作为错误信息报错。

AssertExit(test: bool, code: i32): void

若test结果为false,会以code作为错误码报错,并退出合约。

Now(): u64

返回当前时间戳。

GenesisTime(): u64

返回创世时间戳。

Transaction

Transaction类

使用示例

import {Transaction} from "dmc-lib"

主要类方法

类方法 描述
static getTxHash(): Bytes 获取交易哈希
static getSignature(): Bytes 获取交易签名

Crypro

Crypto类

使用示例

import {Crypto} from "dmc-lib"

主要类方法

类方法 描述
static sha256(data: Bytes): Bytes 将数据进行sha256加密
static sha512(data: Bytes): Bytes 将数据进行sha512加密
static ripemd160(data: Bytes): Bytes 将数据进行ripemd160加密
static recoverKey(sign: Bytes): Bytes 恢复公钥
static assertSha256(origin: Bytes, hash: Bytes): void 验证sha256加密结果
static assertSha512(origin: Bytes, hash: Bytes): void 验证sha512加密结果
static assertRipemd160(origin: Bytes, hash: Bytes): void 验证ripemd160加密结果
static assertRecoverKey(sign: Bytes, pub: Bytes): void 验证RecoverKey结果

附录

合约内置类型

以下均采用小端存储

i8、u8:一个字节

bool: 等同u8 一个字节

i16、u16:两个字节

i32、u32:四个字节

i64、u64:八个字节

Asset类型:等同u64 八个字节

Address:等同于20个字节的字节数组

Hash160: 等同于20个字节的字节数组

Hash256: 等同于32个字节的字节数组

Hash512: 等同于64个字节的字节数组

PublicKey: 等同于20个字节的字节数组

Signature: 等同于96个字节的字节数组

Bytes:字节数组

Bytes类型支持的类方法:

类方法名 描述
static fromHex(hex:string): Bytes 从16进制字符串生成字节数组
static fromU8Array(arr: Array<u8>): Bytes 从u8数组转为字节数组
static fromString(str: string): Bytes 从utf8字符串转为字节数组
toHex(): string 从字节数组转为16进制字符串
swapEndian(): Bytes 字节数组的大小端转换
cloneBytes(): Bytes 复制一份字节数组
toU8Array(): Array<u8> 从字节数组转为u8数组
concat(b2: Bytes): Bytes 将b2连接到该字节数组的后面
wrapDataStream(): DataStream 将该字节数组包装到一个数据流中(会在一开始加上数组长度)
toString(): string 将字节数组转为utf8字符串
...... 其他操作TypedArray的方法,请查看代码补全或源代码

DataStream:该类型为对Bytes数组的包装,实现了Serializable接口(该接口可以自动补全,参见:快速入门-数据持久化)的类序列化刀DataStream中,也可从DataStream中反序列化解析出类的内容。具体可以参见源码。

数据的编解码方式

  • 对于定长类型,比如u8、u16、i32等整数,以及Asset类型,直接按照小端存储。例如:
    • u32:0x 01 00 00 00 表示32位无符号整数1
    • Asset: 0x 00 04 00 00 00 00 00 00表示一个Asset类型,其内的token数量以最小单位表示为1024
  • 对于不定长类型,包括string、Bytes等等,其编解码规则均为:先利用一个varUint32保存字节长度,再保存具体内容,varUint32的编解码方式见后。例如:
    • string: 0x 05 68 65 6c 6c 6f 表示字符串"hello"
    • Bytes: 0x 0A 00 00 00 00 1A 4D 01 50 3C 2B 表示一个字节数组,长度为10,内容为: 0x 00 00 00 00 1A 4D 01 50 3C 2B
  • 对于Address、Signature等类型,其长度虽然固定,但其本质仍为字节数组,故其编解码方式与字节数组一致,例如:
    • Address: 0x 14 10 11 12 13 14 15 16 17 18 19 00 00 00 00 00 00 00 00 00 00 表示一个地址,其内容为:10 11 12 13 14 15 16 17 18 19 00 00 00 00 00 00 00 00 00 00
  • 对于实现了Serializable接口的类,其编解码方式按照类内数据的定义顺序进行,例如:
class Person implements Serializable {
  name: string;
  age: u64;
  constructor(name:string, age:u64){
    this.name=name;
    this.age=age;
  }
}
let data=new Person("hello",1)

data编码后的内容为: 0x 05 68 65 6c 6c 6f 01 00 00 00 00 00 00 00

varuint32的编解码方式

readVarint32(): u32 {
        var value: u32 = 0;
        var shift: u32 = 0;
        do {
            var b = this.read<u8>();
            value |= <u32>(b & 0x7f) << (7 * shift++);
        } while (b & 0x80);
        return value;
    }

writeVarint32(value: u32): void {
    do {
        let b: u8 = <u8>value & <u8>0x7f;
        value >>= 7;
        b |= ((value > 0 ? 1 : 0) << 7);
        this.write<u8>(b);
    } while (value);
}

gas扣除逻辑

gas扣除逻辑

About

A TypeScript to WebAssembly compiler 🚀

assemblyscript.org

Resources

Readme
Apache-2.0 license

Code of conduct

Code of conduct

Contributing

Contributing
Activity
Custom properties

Stars

0 stars

Watchers

0 watching

Forks

0 forks
Report repository

Releases

Packages

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.