简介

当使用区块链上的智能合约管理资产时,我们希望合约语言保证资产能够被灵活管理的同时安全的转移。liquid 提供一种安全的资产模型,允许用户定义资产类型,资产类型模拟现实中资产的行为,提供一系列安全特性,包括用户声明的资产类型在 liquid 合约中不可以被复制、代码块中的资产实例在生命周期结束前必须被存储到某个账户、资产存储在用户的账户内、自带溢出检查。

资产类型声明

Liquid 线性资产模型提供合约中资产的定义语法,用户在合约中定义资产后,liquid 自动解析用户定义的资产名、资产类型、发行者、描述信息、发行总量等属性,为用户生成资产类型定义、资产类型内置接口、资产注册调用代码、合约支持的资产接口的 Rust 代码。使用方式如下:

  1. #[liquid(asset(
  2. issuer = "0x83309d045a19c44dc3722d15a6abd472f95866ac", // 发行者的账户地址
  3. fungible = true,
  4. total = 1000000000,
  5. description = "资产描述"
  6. ))]
  7. struct SomeAsset;
  • 结构体 SomeAsset 上的#[liquid(asset(…))]用于声明结构体是资产类型,声明为资产类型的结构体不需要有定义
  • issuer 指定资产类型 Erc20Token 的发行者,只有发行者有权限发行新的 Erc20Token 资产
  • total 指定资产发行的总量,用户不指定则默认为 64 位无符号整数的最大值
  • fungible 表示资产是否是同质资产,true 表示同质资产,false 表示非同质资产,用户不指定则默认为 true
  • description 是对资产类型的描述
  • 合约中可以声明多种资产类型
  • 同质资产只有数值的不同,非同质资产代表某种唯一的、不可替代的商品。

代码生成

同质资产

当用户声明资产类型时,fungible 为 true,则表明该资产类型是同质资产类型,同质资产类型提供下述接口。

  • total_supply 签名:pub fn total_supply() -> bool; 功能:获取资产总发行量
  • issuer 签名:pub fn issuer() -> address; 功能:获取发行者账户地址
  • description 签名:pub fn description() -> &'a str; 功能:获取资产描述信息
  • balance_of 签名:pub fn balance_of(owner: &address) -> u64; 功能:获取某个账户中此资产的总量
  • issue_to 签名:pub fn issue_to(to: &address, amount: u64) -> bool; 功能:为某个充值资产,只有签发者账号可操作
  • withdraw_from_caller 签名:pub fn withdraw_from_caller(amount: u64) -> Option<Self>; 功能:从调用者账户中取出资产,构造相应的对象
  • withdraw_from_self 签名:pub fn withdraw_from_self(amount: u64) -> Option<Self>; 功能:从合约账户中取出资产,构造相应的对象
  • value 签名:pub fn value(&self) -> u64; 功能:获取某个资产对象的值
  • deposit 签名:pub fn deposit(mut self, to: &address); 功能:将资产存储到某个账户中,资产生命周期结束前,必须调用此函数

非同质资产

当用户声明资产类型时,fungible 为 false,则表明该资产类型是非同质资产类型。

  • total_supply 签名:pub fn total_supply() -> bool; 功能:获取资产总发行量
  • issuer 签名:pub fn issuer() -> address; 功能:获取发行者账户地址
  • description 签名:pub fn description() -> &'a str; 功能:获取资产描述信息
  • balance_of 签名:pub fn balance_of(owner: &address) -> u64; 功能:获取某个账户中此资产的总个数
  • tokens_of 签名:pub fn tokens_of(owner: &address) -> Vec<u64>; 功能:获取某个账户中所有此种资产的 id
  • issue_to 签名:pub fn issue_to(to: &address, amount: u64) -> bool; 功能:为某个充值资产,只有签发者账号可操作
  • withdraw_from_caller 签名:pub fn withdraw_from_caller(id: u64) -> Option<Self>; 功能:从调用者账户中取出资产,构造相应的对象
  • withdraw_from_self 签名:pub fn withdraw_from_self(id: u64) -> Option<Self>; 功能:从合约账户中取出资产,构造相应的对象
  • uri 签名:pub fn uri(&self) -> &String; 功能:获取资产对象的 uri
  • id 签名:pub fn id(&self) -> u64; 功能:获取资产对象的 id
  • deposit 签名:pub fn deposit(mut self, to: &address);

使用举例

声明资产

资产类型的声明需要在合约的 mod 中,用户只需要定义资产所需的属性和类型名,由 liquid 生成资产类型的实现和方法。资产类型由名称区分,在链上唯一,重复声明同名资产类型会导致合约回滚。资产类型声明后,由发行者通过资产的发行借口发行到不同账户,每个账户的状态空间中会存储资产的相关信息。

下面的代码演示在合约中声明一个资产类型,合约中支持用户声明多个资产类型。

  1. mod contract {
  2. use liquid_lang::storage;
  3. /// Defines the storage of your contract.
  4. #[liquid(storage)]
  5. struct Contract {
  6. allowances: storage::Mapping<(address, address), u64>,
  7. }
  8. #[liquid(asset(
  9. issuer = "发行者的账户地址",
  10. fungible = true,
  11. total = 1000000000,
  12. description = "资产描述"
  13. ))]
  14. struct SomeAsset;
  15. #[liquid(methods)]
  16. impl Contract {
  17. // ...省略
  18. }
  19. }

合约中构造资产对象

资产类型提供withdraw_from_callerwithdraw_from_self两个方法用于构造资产类型的实例,withdraw_from_caller会从调用者的账户中构造资产实例,如果调用者账户中的此类资产不足,则构造失败。withdraw_from_self从合约账户自身构造资产类型,如果合约中的此类资产不足则构造失败。

  1. let amount = 500;
  2. if let Some(token) == SomeAsset::withdraw_from_caller(amount) {
  3. //do something
  4. } else{
  5. // process error
  6. }

资产实例的销毁

为保证资产实例不能被随意丢弃,资产实例的生命周期结束之前,必须妥善的将资产实例存储到某个账户中,否则会导致交易会滚。

  1. let amount = 500;
  2. let recipient:address = "0x11111".parse().unwrap();
  3. if let Some(token) == SomeAsset::withdraw_from_self(amount) {
  4. //do something
  5. token.deposit(&recipient);
  6. } else{
  7. // process error
  8. }

查询资产相关信息

资产类型提供一系列查询接口,方便用户查询资产相关信息。

  1. let total = SomeAsset::total_supply();
  2. let user : address = "0x11112".parse().unwrap();
  3. let balance = SomeAsset::balance_of(user);
  4. let description = SomeAsset::description().into();
  5. let issuer = SomeAsset::issuer();

合约托管资产

为方便用户对资产的灵活操作,用户可以将其持有的资产转给某个合约,由合约逻辑来管理资产。如下的例子,可以允许用户将自己的 SomeAsset 资产授权给其他用户使用。

  1. mod contract {
  2. use liquid_lang::storage;
  3. /// Defines the storage of your contract.
  4. #[liquid(storage)]
  5. struct Contract {
  6. allowances: storage::Mapping<address, u64>,
  7. owner: storage::Value<address>,
  8. }
  9. #[liquid(asset(
  10. issuer = "发行者的账户地址", fungible = true,
  11. total = 1000000000, description = "资产描述"
  12. ))]
  13. struct SomeAsset;
  14. #[liquid(methods)]
  15. impl Contract {
  16. pub fn new(&mut self, owner: address) {
  17. self.allowances.initialize();
  18. self.owner.initialize(owner);
  19. }
  20. pub fn approve(&mut self, spender: address, amount: u64) -> bool {
  21. match SomeAsset::withdraw_from_caller(amount) {
  22. None => false,
  23. Some(token) => {
  24. token.deposit(&self.env().get_address());
  25. let caller = self.env().get_caller();
  26. let allowance =
  27. *self.allowances.get(&spender).unwrap_or(&0);
  28. self.allowances
  29. .insert(&spender, allowance + amount);
  30. true
  31. }
  32. }
  33. }
  34. pub fn transfer(
  35. &mut self,
  36. recipient: address,
  37. amount: u64,
  38. ) -> bool {
  39. let caller = self.env().get_caller();
  40. let allowance = *self.allowances.get(&caller).unwrap_or(&0);
  41. if allowance >= amount {
  42. self.allowances.insert(&caller, allowance - amount);
  43. return match SomeAsset::withdraw_from_self(amount) {
  44. None => false,
  45. Some(token) => {
  46. token.deposit(&recipient);
  47. true
  48. }
  49. };
  50. }
  51. false
  52. }
  53. }
  54. }