当前位置:主页 > 列表页 > 正文

如何在CUDOS网络上搭建智能合约?(第2部分:创建项目)

2021-11-25 23:25 | 出处: CUDOS


去中心化云计算网络CUDOS是一个第一层区块链,由智能合约管理,能够以分布式的方式将区块链与安全的外部资源对接,为区块链引入高性能计算以及解决区块链可扩展问题。在这一系列文章中,我们将从基础知识入手,逐步讲解如何在CUDOS网络上搭建智能合约。

CUDOS的测试网激励计划正在进行中,感兴趣的开发者可以参与到测试其公测网络Somniorum的过程中,并赢得相应的奖励。

第一部分中,您完成了CosmWasm环境和CosmWasm合约架构的设置。现在是时候开始创建一个项目了,这也是您将在本部分学到的内容。

创建一个项目

根文件夹:CW-plus(所有路径都是以CW-plus作为根定义的)S。

  • 删除/contracts里面的所有文件夹

  • 前往Cargo.toml并删除所有[profile.release.package.<名字>]的文件。

  • 使用codegen-uints=1incremental = false添加一个新的[profile.release.stake-cw20]

[workspace]

members = [“packages/*”, “contracts/*”]

[profile.release.package.stake-cw20]

codegen-units = 1

incremental = false

[profile.release]

rpath = false

lto = true

overflow-checks =true

opt-level = 3

debug = false

debug-assertions =false

这里,stake-cw20是您将在下一步中创建的项目的名称。如果仔细研究下这个cargo.toml,你会发现该cargo的workspace配置。

codegen-units标记控制着库文件被分成多少个代码生成单元。它需要一个大于0的整数。

  • /contracts中创建一个新的cargo lib:stake-cw20。

cargo new stake-cw20— lib

  • 创建后,进入stake-cw20/ 并更新cargo.toml。

  • 它定义了这个项目所需的所有包。

  • 复制cw20-base/cargo.toml中的lib、dependency和dev-dependency文件。完成所有修改后,你的cargo.toml看起来会是这样:

[package]

name = “stake-cw20”

version = “0.1.0”

edition = “2018”

# See more keys andtheir definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]

crate-type =[“cdylib”, “rlib”]

[features]

backtraces =[“cosmwasm-std/backtraces”]

# use libraryfeature to disable all instantiate/execute/query exports

library = []

[dependencies]

cw0 = { path =“../../packages/cw0”, version = “0.8.0” }

cw2 = { path =“../../packages/cw2”, version = “0.8.0” }

cw20 = { path =“../../packages/cw20”, version = “0.8.0” }

cw-storage-plus = {path = “../../packages/storage-plus”, version = “0.8.0” }

cosmwasm-std = {version = “0.16.0” }

schemars = “0.8.1”

serde = { version =“1.0.103”, default-features = false, features = [“derive”] }

thiserror = {version = “1.0.23” }

[dev-dependencies]

cosmwasm-schema = {version = “0.16.0” }

智能合约开发

在src文件夹内创建以下文件夹:

  • contracts.rs
  • error.rs
  • msg.rs
  • state.rs
  • lib.rs

Error.rs

首先让我们定义智能合约中的错误信息。用以下代码更新error.rs:

  • 这里你将使用thiserror,在Rust中创建自定义错误信息。
  • derive宏是为枚举或结构提供一些预定义特征的简单方法。
  • 在后续的使用中你将更多了解以下提及的错误。

usecosmwasm_std::StdError;

usethiserror::Error;

#[derive(Error,Debug, PartialEq)]

pub enumContractError {

#[error(“{0}”)]

Std(#[from]StdError),

#[error(“Unauthorized”)]

Unauthorized {},

#[error(“Cannot set to own account”)]

CannotSetOwnAccount{},

#[error(“Invalid zero amount”)]

InvalidZeroAmount{},

#[error(“Allowance is expired”)]

Expired {},

#[error(“No allowance for this account”)]

NoAllowance {},

}

State.rs


在定义了信息之后,你将定义合约拥有的状态(state)。可以把state想成典型应用程序中的数据库,你将在那里存储所有的数据。

useschemars::JsonSchema;

useserde::{Deserialize, Serialize};

usecosmwasm_std::{Addr, Timestamp, Uint128};

usecw_storage_plus::{Item, Map};

usecw20::{AllowanceResponse, Logo};

#[derive(Serialize,Deserialize, Clone, PartialEq, JsonSchema, Debug)]

#[serde(rename_all =“snake_case”)]

pub struct TokenInfo{

pub name: String,

pub symbol: String,

pub decimals: u8,

pub total_supply:Uint128,

pub minter: Addr,

}

#[derive(Serialize,Deserialize, Clone, PartialEq, JsonSchema, Debug)]

#[serde(rename_all =“snake_case”)]

pub structStakeHolderInfo {

pub amount: Uint128,

pub rewards:Uint128,

}

pub constTOKEN_INFO: Item<TokenInfo> = Item::new(“token_info”);

pub const LAST_DISTRIBUTE_TIME:Item<Timestamp> = Item::new(“last_distributed_time”);

pub const BALANCES:Map<&Addr, Uint128> = Map::new(“balance”);

pub constALLOWANCES: Map<(&Addr, &Addr), AllowanceResponse> = Map::new(“allowance”);

pub constSTAKE_HOLDERS: Map<&Addr, StakeHolderInfo> = Map::new(“stake_holder”);

pub constTOTAL_STAKE: Item<Uint128> = Item::new(“total_stake”);

usestmt

  • schemars::JsonSchema: 这是生成一种特定类型的JSON schema(JSON模式)的最简单方式。
  • serde::{Deserialize, Serialize}:
  • 这是最流行的lib之一,它可以给你derive obj。
  • 如果你仔细看一下本地的cargo.toml(stake-cw20/cargo.toml),你会发现
  • serde = { version = “1.0.103”,default-features = false, features = [“derive”] }
  • 这意味着你在你的项目中启用了‘derive’的使用。
  • 序列化:你可以使用to_string()方法将给定的对象转换为字符串。它类似于JS中的Serialization。
  • 反序列化:你可以将一个对象的字符串化版本转换为一个实际的对象。

  • cosmwasm_std:
  • 这是一个强制性的库,是CosmWasm合约的核心和灵魂。
  • 它提供了所有的CosmWasm数据结构,如地址、环境(env)和依赖(deps)。我们将在后面的章节中进一步了解这些。

  • cw_storage_plus:
  • 这是另一个重要的库,用来为你提供所有与存储有关的数据结构。
  • CosmWasm鼓励大家使用这个库,因为它是明确为区块链存储创建的。
  • Cw20:这些也是具有预定义CW20规格的Rust合约,这意味着它们包含一些预定义的特质、结构和枚举,这将提高速度,减少错误,并帮助我们创建可以进入生产的CW20合约。(更多信息)

derive stmt

derive stmt的基本功能是为一个给定的类型提供标准的特征实现。这有助于减少代码和消除错误。

States

如果你熟悉Rust,那么你已经了解了结构。如果你不熟悉Rust,请先看看结构。你会在一个状态(state)下发现Items<>和Maps<>类型。让我们来详细了解一下它们:

  • Items:
  • Items是储存单一类型数值的变量。
  • 只需提供适当的类型,以及未被任何其他item使用的数据库密钥。
  • 我使用const fn来创建Item,使它被定义为一个全局的编译时常量,而不是每次都必须构建的一个函数,这样可以节省时间。
  • 在我们的合约中,你需要创建三个item:
  • TOKEN_INFO: Item<TokenInfo>:它用于存储CW20代币的名称、符号、十进制、总供应量和铸币者地址。
  • LAST_DISTRIBUTE_TIME: Item<Timestamp>:它用于存储时间戳,确定最后已知的奖励分配时间。这有助于及时分配奖励。
  • TOTAL_STAKE: Item<Uint128>:储存人们质押的总金额,你在此基础上计算奖励。

  • Maps:
  • Map相当于表格,由键类型和相应的值类型对组成。
  • 和上面一样,你可以使用const fn来创建Item,使它被定义为一个全局的编译时常量,而不是每次都必须构造的一个函数,这样可以节省时间。
  • 对于我们的合约,请创建三个item:
  • BALANCES: Map<&Addr, Uint128>:存储每个持有CW-20代币的地址的余额。
  • ALLOWANCES: Map<(&Addr, &Addr),AllowanceResponse>:使用所有者地址和花费者地址作为复合键和在值。用它来定义发送者被允许向其他地址发送多少金额。
  • STAKE_HOLDERS: Map<&Addr,StakeHolderInfo>:使用一个简单的地址作为键,也就是质押者的地址,也作为一个值,存储质押者质押的代币数量和当前的奖励。

这些都在state.rs文件里面。

Msg.rs

在这里,你将定义区块链的只读和只写功能的结构。

  • 读取功能被称为QueryMsg
  • 写入功能被称为ExecuteMsg
  • 第三种类型的Msg是用于实例化的特殊类型的msg,称为InstantiateMsg

在这里,你可以按照你的意愿来命名msg。下面你可以看到开发者用来创建CosmWasm合约的惯例。

usecosmwasm_std::{StdError, StdResult, Uint128};

use cw0::Expiration;

usecw20::{Cw20Coin};

useschemars::JsonSchema;

useserde::{Deserialize, Serialize};

#[derive(Serialize,Deserialize, JsonSchema, Debug, Clone, PartialEq)]

pub struct InstantiateMsg{

pub name: String,

pub symbol: String,

pub decimals: u8,

pubinitial_balances: Vec<Cw20Coin>,

}

impl InstantiateMsg{

pub fnvalidate(&self) -> StdResult<()> {

// Check name,symbol, decimals

if!is_valid_name(&self.name) {

returnErr(StdError::generic_err(

“Name is notin the expected format (3–50 UTF-8 bytes)”,

));

}

if!is_valid_symbol(&self.symbol) {

returnErr(StdError::generic_err(

“Ticker symbolis not in expected format [a-zA-Z\-]{3,12}”,

));

}

if self.decimals> 18 {

returnErr(StdError::generic_err(“Decimals must not exceed 18”));

}

Ok(())

}

}

fnis_valid_name(name: &str) -> bool {

let bytes =name.as_bytes();

if bytes.len() <3 || bytes.len() > 50 {

return false;

}

true

}

fnis_valid_symbol(symbol: &str) -> bool {

let bytes =symbol.as_bytes();

if bytes.len() <3 || bytes.len() > 12 {

return false;

}

for byte inbytes.iter() {

if (*byte != 45)&& (*byte < 65 || *byte > 90) && (*byte < 97 || *byte> 122) {

return false;

}

}

true

}

#[derive(Serialize,Deserialize, Clone, Debug, PartialEq, JsonSchema)]

#[serde(rename_all =“snake_case”)]

pub enum QueryMsg {

/// Returns thecurrent balance of the given address, 0 if unset.

/// Return type:BalanceResponse.

Balance {

address: String,

},

Minter {},

/// Returns metadataon the contract — name, decimals, supply, etc.

/// Return type:TokenInfoResponse.

TokenInfo {},

/// Only with“allowance” extension.

/// Returns how muchspender can use from owner account, 0 if unset.

/// Return type:AllowanceResponse.

Allowance {

owner: String,

spender: String,

},

/// Only with“enumerable” extension (and “allowances”)

/// Returns allallowances this owner has approved. Supports pagination.

/// Return type:AllAllowancesResponse.

AllAllowances {

owner: String,

start_after:Option<String>,

limit:Option<u32>,

},

/// Only with“enumerable” extension

/// Returns allaccounts that have balances. Supports pagination.

/// Return type:AllAccountsResponse.

AllAccounts {

start_after:Option<String>,

limit:Option<u32>,

},

AllStakeHolders {

start_after:Option<String>,

limit:Option<u32>,

},

TotalStake {},

StakeAndRewardOf {

account: String,

},

}

#[derive(Serialize,Deserialize, Clone, PartialEq, JsonSchema, Debug)]

#[serde(rename_all =“snake_case”)]

pub enum ExecuteMsg{

/// Transfer is abase message to move tokens to another account without triggering actions

Transfer {

recipient: String,

amount: Uint128,

},

/// Burn is a basemessage to destroy tokens forever

Burn {

amount: Uint128,

},

/// Only with“approval” extension. Allows spender to access an additional amount tokens

/// from the owner’s(env.sender) account. If expires is Some(), overwrites current allowance

/// expiration withthis one.

IncreaseAllowance {

spender: String,

amount: Uint128,

expires:Option<Expiration>,

},

/// Only with“approval” extension. Lowers the spender’s access of tokens

/// from the owner’s(env.sender) account by amount. If expires is Some(), overwrites current

/// allowanceexpiration with this one.

DecreaseAllowance {

spender: String,

amount: Uint128,

expires:Option<Expiration>,

},

/// Only with“approval” extension. Transfers amount tokens from owner -> recipient

/// if `env.sender`has sufficient pre-approval.

TransferFrom {

owner: String,

recipient: String,

amount: Uint128,

},

/// Only with“approval” extension. Destroys tokens forever

BurnFrom {

owner: String,

amount: Uint128,

},

/// Only with the“mintable” extension. If authorized, create amount new tokens

/// and adds to therecipient balance.

Mint {

recipient: String,

amount: Uint128,

},

CreateStake {

amount: Uint128,

},

RemoveStake {

amount: Uint128,

},

DistributRewards {},

WithdrawRewards {},

}

#[derive(Serialize,Deserialize, Clone, PartialEq, JsonSchema, Debug)]

pub structTotalStakeResponse {

pub total_stake:Uint128,

}

#[derive(Serialize,Deserialize, Clone, PartialEq, JsonSchema, Debug)]

pub structAllStakeHolderResponse {

pub accounts:Vec<String>,

}

InstantiateMsg:

  • 它类似于构造函数,即用于在初始化合约时来初始化合约的状态(state)。
  • 在你的合约中,它包含五个变量和在这个msg上实现的特质。
  • 首先,让我们来了解一下特质(Traits)。Traits类似于接口,比接口多一些额外的功能。
  • 如果细看这个合约,你会发现traits里面有一个函数:
  • Validate:这是一个公共函数,用于验证InstantiateMsg结构中存在的名称、符号和小数。

五个变量:

  • Name:CW20代币的名称。
  • Symbol:CW20代币的符号,类似于美元的¥。
  • Decimals:一种货币的面额小数位数。
  • 例如,美元的面额较低的是美分,这意味着小数位数是2。
  • 一般来说,货币有18位小数。
  • Initial_balances:它是一个载体,是一个带有CW20Coin的一维数组。如果你研究一下CW20Coin的定义,你会发现它包含两个字段:一个是地址,另一个是用于给用户提供一些初始余额的金额。

ExecuteMsg:

  • 这些是只写的函数,需要燃料来执行计算区块链。
  • 你将定义一个枚举,其中包含所有的消息名称与他们运行该消息所需的参数。

例如

  • 要执行一个转账( Transfer)
  • 定义枚举Transfer {recipient: String,amount: Uint128,},你需要两个参数。一个是要接收转账金额的接收人的地址,另一个是你希望接收人接收的金额。
  • 因此,如果你需要将金额x转给某人B,你需要使用{recipient: B, amount: x}来调用Transfer消息。

QueryMsg:

  • 这些是只读函数,不需要燃料来执行计算区块链。
  • 与Execute Msg类似,你将在这里定义所有的查询msg。

Lib.rs

  • Lib.rs是在你创建一个rust库时使用的。它类似于js中的index.js文件,你在那里输入文件。
  • Pub mod是告知rust编译器这些文件包含在这个rust里面的。
  • 如果你删除了任何mod,那么你将不能在你的lib中使用那个.rs文件。

pub mod allowances;

pub mod contract;

pub mod enumerable;

mod error;

pub mod msg;

pub mod state;

pub usecrate::error::ContractError;

另外,你会发现两个新的mod allowance和enumerable(可枚举项)。

  • Allowance.rs:包含所有你需要的逻辑,用于批准给予支出方的代币。
  • Enumerable.rs:包含可以让你找到所有持有CW20的账户的分页数组的逻辑。

你可以直接从这里复制这些文件,而不用自己创建这两个文件:

在本系列的第三部分,我们将介绍包含主要业务逻辑的Contract.rs,讨论让你能够在状态(state)内进行一些计算和存储值操作的函数。

您现在能做什么?

通过以下链接,您可以立即加入我们的激励测试网Project Artemis

加入CUDOS的Discord服务器

加入CUDOS的Telegram社区

买入CUDOS

成为CUDOS的大使

值得注意的是,您将根据您在测试网中完成的任务获得奖励

此外,如果您已经购买了CUDOS代币,可以把它们质押在我们的平台上,以确保网络安全,作为回报,您可以获得奖励。

关于CUDOS

CUDOS网络是一个第一层区块链和第二层计算及预言机网络,旨在大规模提供去中心化、无许可的高性能计算,可将计算资源扩展至数十万节点。在与以/太坊、Algorand、波卡和Cosmos桥接后,CUDOS将在所有桥接的区块链上实现可扩展的计算和第二层预言机。

官网:https://www.cudos.org/

Twitter:https://twitter.com/CUDOS_

微博:https://weibo.com/cudos

中文电报:https://t.me/Cudos2021

作者郑重申明:截至发文时,作者与文中提及项目存在利益关系,特此告知。利益关系包括但不限于下述情况:本人为项目团队成员、本人是项目团队成员的直系亲属或配偶、参与投资该项目、持有该项目发行的股份或通证、参与做空或做多该项目、收取回报进行有偿撰文等。
相关文章