
The Decentralized Fourth Estate
Thanks to Eric Zhang for discussions about several topics related to media decentralization. The advance of tech has created a fifth estate. People who control tech control the most scarce resource of our time and a very long time to come. In the realm of media and journalism, consolidation of news corporations and social media created the danger of an authoritarian future, especially when major media and social media platforms share a common view. Although the shift of Twitter’s control chan...

DAOrayaki |衡量加密项目时的17个重要因素
基本面分析是区块链/加密货币领域不可或缺的投资和交易策略之一。因此,DAOrayaki社区编译《衡量加密项目时的17个重要因素》一文。以下,是17个相关因素,同时我将分享一些实际案例,让我们开始!。DAOrayaki DAO研究奖金池:资助地址: DAOrayaki.eth 投票进展:DAO Committee 2/0 通过 赏金总量:60 USDC 研究种类:DAOs, Application 原文作者: Ehsan Yazdanparast 贡献者:Hahaho@DAOrayaki.org 审核者:DAOctor@DAOrayaki.org 原文: 17 Important Factors in Fundamental Analysis DAOrayaki 是一个去中心化的研究者组织和去中心化媒体,通过 DAO的形式去中心化地资助世界各地的研究者进行研究、翻译、分析等工作。DAOrayaki 由早期的 DAO 组织 DAOONE 核心成员发起,得到了Dora Factory基础设施的支持。欢迎通过文末方式提交星际移民、量子计算、DAO等相关研究,瓜分10000USDC赏金池!...

联合曲线设计脑洞大全及参数大典
在这篇帖子中,我试图勾画出联合曲线的广阔设计空间和参数,并指出注意事项和用例。我们还探讨了如何缓冲(mitigate)一些攻击矢量(attack vectors),如“拉高出货”(pump and dumps)。此外,我还描述了一个可以应用于各种用例的简单框架。每个用例的理想谢林点参数(ideal Schelling point)可能都有所不同。本帖旨在发挥一个代币工程工具集(token engineering toolset)的作用,以激励社区中的实验和创新,并创建一个更全面的思考联合曲线设计的方式。 DAOrayaki DAO研究奖金池: 资助地址: 0xCd7da526f5C943126fa9E6f63b7774fA89E88d71 投票进展:DAO Committee 5/7 通过 赏金总量:150 USDC 研究种类:DAO, Bonding Curve, Predict Market 原文作者: Paul Kohlhaas 贡献者:Demo, DAOctor, Trinity@DAOrayaki 原文: Token Bonding Curve Design Param...
DAOrayaki is a decentralized media and research organization that is autonomous by readers, researchers, and funders. linktree: daorayaki

The Decentralized Fourth Estate
Thanks to Eric Zhang for discussions about several topics related to media decentralization. The advance of tech has created a fifth estate. People who control tech control the most scarce resource of our time and a very long time to come. In the realm of media and journalism, consolidation of news corporations and social media created the danger of an authoritarian future, especially when major media and social media platforms share a common view. Although the shift of Twitter’s control chan...

DAOrayaki |衡量加密项目时的17个重要因素
基本面分析是区块链/加密货币领域不可或缺的投资和交易策略之一。因此,DAOrayaki社区编译《衡量加密项目时的17个重要因素》一文。以下,是17个相关因素,同时我将分享一些实际案例,让我们开始!。DAOrayaki DAO研究奖金池:资助地址: DAOrayaki.eth 投票进展:DAO Committee 2/0 通过 赏金总量:60 USDC 研究种类:DAOs, Application 原文作者: Ehsan Yazdanparast 贡献者:Hahaho@DAOrayaki.org 审核者:DAOctor@DAOrayaki.org 原文: 17 Important Factors in Fundamental Analysis DAOrayaki 是一个去中心化的研究者组织和去中心化媒体,通过 DAO的形式去中心化地资助世界各地的研究者进行研究、翻译、分析等工作。DAOrayaki 由早期的 DAO 组织 DAOONE 核心成员发起,得到了Dora Factory基础设施的支持。欢迎通过文末方式提交星际移民、量子计算、DAO等相关研究,瓜分10000USDC赏金池!...

联合曲线设计脑洞大全及参数大典
在这篇帖子中,我试图勾画出联合曲线的广阔设计空间和参数,并指出注意事项和用例。我们还探讨了如何缓冲(mitigate)一些攻击矢量(attack vectors),如“拉高出货”(pump and dumps)。此外,我还描述了一个可以应用于各种用例的简单框架。每个用例的理想谢林点参数(ideal Schelling point)可能都有所不同。本帖旨在发挥一个代币工程工具集(token engineering toolset)的作用,以激励社区中的实验和创新,并创建一个更全面的思考联合曲线设计的方式。 DAOrayaki DAO研究奖金池: 资助地址: 0xCd7da526f5C943126fa9E6f63b7774fA89E88d71 投票进展:DAO Committee 5/7 通过 赏金总量:150 USDC 研究种类:DAO, Bonding Curve, Predict Market 原文作者: Paul Kohlhaas 贡献者:Demo, DAOctor, Trinity@DAOrayaki 原文: Token Bonding Curve Design Param...
DAOrayaki is a decentralized media and research organization that is autonomous by readers, researchers, and funders. linktree: daorayaki

Subscribe to DAOrayaki

Subscribe to DAOrayaki
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers


资助地址: DAOrayaki.eth
投票进展:DAO Reviewer 3/0 通 过
研究种类:Aptos,Layer1
创作者:FF@DoraFactory
本文主要讲解操作 aptos cli 和 aptos sdk
# Clone the Aptos repo.git clone # cd into aptos-core directory.cd aptos-core# Run the scripts/dev_setup.sh Bash script as shown below. This will prepare your developer environment../scripts/dev_setup.sh# Update your current shell environment.source ~/.cargo/env# Skip this step if you are not installing an Aptos node.git checkout --track origin/devnet
Using CLI to Run a Local Testnet | Aptos Docs[1]
启动本地链
ps: 通过这个方法启动的本地链、数据都会保存在启动这条命令的当前文件夹下,以.aptos/ 文件存在
aptos node run-local-testnet --with-faucet
启动成功:
Completed generating configuration: Log file: "/Users/greg/.aptos/testnet/validator.log" Test dir: "/Users/greg/.aptos/testnet" Aptos root key path: "/Users/greg/.aptos/testnet/mint.key" Waypoint: 0:74c9d14285ec19e6bd15fbe851007ea8b66efbd772f613c191aa78721cadac25 ChainId: TESTING REST API endpoint: 0.0.0.0:8080 FullNode network: /ip4/0.0.0.0/tcp/6181Aptos is running, press ctrl-c to exitFaucet is running. Faucet endpoint: 0.0.0.0:8081
启动成功后会提示 rest api 和 faucet api 的地址。后面需要把这两个信息配置在 aptos cli 环境内。
配置 aptos cli 环境
为了通过命令行访问和调用本地测试链,我们需要给 aptos cli 根据上面的部署信息配置 config。
PROFILE=localaptos init --profile $PROFILE --rest-url --faucet-url
执行过程中,我们会得到如下的输出。我们可以选择输入一个秘钥,也可以默认随机生成。
Configuring for profile localUsing command line argument for rest URL Using command line argument for faucet URL Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]
确认之后,会创建一个账户并使用默认数量的 token 为其注资。
No key given, generating key...Account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1 doesn't exist, creating it and funding it with 10000 coinsAptos is now set up for account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1! Run `aptos help` for more information about commands{ "Result": "Success"}
从现在开始,我们就可以通过添加--profile local命令以在本地测试网上运行它们。
--profile``kube-config
profile 的配置,会设置执行者地址、node-rest-api、faucet-api 信息。
# 列出cli控制的所有账户aptos account list# 为账户注资:aptos account fund --profile $PROFILE --account $PROFILE# 创建新的资源账户aptos account create-resource-account --profile $PROFILE --seed 1# 编译move合约aptos move compile --package-dir hello_blockchain# 部署合约aptos move publish --package-dir hello_blockchain --named-addresses basecoin= --profile local# 调用合约aptos move run --function-id :::: --profile local# 列出指定账户的modules/resources信息aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile localaptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile local# 合约升级aptos move publish --upgrade-policy `arbitrary`, `compatible`, `immutable` 对应 0,1,2 0 不做任何检查,强制替换code, 1 做兼容性检查(同样的public 函数,不能改变已有Resource的内存布局) 2 禁止升级 每次publish的时候会比较链上的policy和此次publish的policy(默认是1), 只有此次的policy小于链上的policy时才允许合约升级
module MyCounterAddr::MyCounter { use std::signer; struct Counter has key, store { value:u64, } public fun init(account: &signer){ move_to(account, Counter{value:0}); } public fun incr(account: &signer) acquires Counter { let counter = borrow_global_mut(signer::address_of(account)); counter.value = counter.value + 1; } public entry fun init_counter(account: signer){ Self::init(&account) } public entry fun incr_counter(account: signer) acquires Counter { Self::incr(&account) }}
MyCounter 源码分析
module 是发布在特定地址下的打包在一起的一组函数和结构体。使用 script 时需要与已发布的 module 或标准库一起运行,而标准库本身就是在 0x1 地址下发布的一组 module。
module MyCounterAddr::MyCounter{ } 则在该 MyCounterAddr 地址下(对应 Move.toml 下的 MyCounterAddr = "0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf")创建一个 module。
use std::signer,是使用标准库下的 signer module,Signer 是一种原生的类似 Resource 的不可复制的类型,它包含了交易发送者的地址。引入 signer 类型的原因之一是要明确显示哪些函数需要发送者权限,哪些不需要。因此,函数不能欺骗用户未经授权访问其 Resource。具体可参考源码[2]。
module std::signer { // Borrows the address of the signer // Conceptually, you can think of the `signer` as being a struct wrapper arround an // address // ``` // struct signer has drop { addr: address } // ``` // `borrow_address` borrows this inner field native public fun borrow_address(s: &signer): &address; // Copies the address of the signer public fun address_of(s: &signer): address { *borrow_address(s) } /// Return true only if `s` is a transaction signer. This is a spec function only available in spec. spec native fun is_txn_signer(s: signer): bool; /// Return true only if `a` is a transaction signer address. This is a spec function only available in spec. spec native fun is_txn_signer_addr(a: address): bool;}
Struct & Abilities
struct Counter has key, store { value:u64,}
使用 struct 定义了一个叫做 Counter 的结构体,同时被 key,store 两种限制符修饰。
Move 的类型系统灵活,每种类型都可以定义四种能力(abilities)。
它们定义了类型的值是否可以被复制、丢弃和存储。
这四种 abilities 限制符分别是: Copy, Drop, Store 和 Key。
它们的功能分别是:
Copy - 值可以被复制。
Drop - 在作用域(Scope)结束时值可以被丢弃。
Key - 值可以作为键值(Key)被「全局存储操作( global storage operations)」进行访问。
Store - 值可以被 存储 到全局状态。
这里用 key、store 修饰,则表示它不能被复制,也不能被丢弃或重新使用,但是它却可以被安全地存储和转移。
Abilities 的语法
基本类型和内建类型的 abilities 是预先定义好的并且不可改变: integers, vector, addresses 和 boolean 类型的值先天具有 copy、drop 和 store ability。
然而,结构体的 ability 可以按照下面的语法进行添加:
struct NAME has ABILITY [, ABILITY] { [FIELDS] }
一个简单的图书馆例子:
module Library { // each ability has matching keyword // multiple abilities are listed with comma struct Book has store, copy, drop { year: u64 } // single ability is also possible struct Storage has key { books: vector } // this one has no abilities struct Empty {}}
Move 白皮书中详细描述了 Resource 这个概念。最初,它是作为一种名为 resource 的结构体类型被实现,自从引入 ability 以后,它被实现成拥有 Key和 Store两种 ability 的结构体。Resource 可以安全的表示数字资产,它不能被复制,也不能被丢弃或重新使用,但是它却可以被安全地存储和转移。
Resource 是一种用 key 和 store ability 限制了的结构体:
module M { struct T has key, store { field: u8 }}
在代码中,Resource 类型有几个主要限制:
Resource 存储在帐户下。因此,只有在分配帐户后才会存在,并且只能通过该帐户访问。
一个帐户同一时刻只能容纳一个某类型的 Resource。
Resource 不能被复制;与它对应的是一种特殊的kind:resource,它与copyable不同,这一点在泛型章节中已经介绍。(这里可以抽象到 Rust 的所有权那)
Resource 必需被使用,这意味着必须将新创建的 Resource move到某个帐户下,从帐户移出的 Resource 必须被解构或存储在另一个帐户下。
刚才的案例
struct Counter has key, store { value:u64,}
所以这里就有一个和 solidity 的区别了,在 eth 上如果需要发行一个新资产,比如 usdc。那这个资产是记录在合约里的某个 map 中。而 move 就不同了,资产是作为 resource 存在用户地址下的。
定义函数
public fun init(account: &signer){ move_to(account, Counter{value:0});}public fun incr(account: &signer) acquires Counter { let counter = borrow_global_mut(signer::address_of(account)); counter.value = counter.value + 1;}public entry fun init_counter(account: signer){ Self::init(&account)}public entry fun incr_counter(account: signer) acquires Counter { Self::incr(&account)}
定义格式则是:
public fun 函数名(参数:参数类型){ }
move 函数默认是私有函数,只能在定义它们的模块中访问。关键字 public 将更改函数的默认可见性并使其公开,即可以从外部访问。
init 方法参数是一个&signer,意味着该方法必须是一个账户合法签名过后才可以调用,move_to则是 move 的一个原语,作用是发布、添加 Counter 资源到 signer 的地址下。Move 的账户模型,code 和 data 是存储在一个账户地址下的。
下面是列举的常用原语
move_to< T >(&signer, T):发布、添加类型为 T 的 Resource 到 signer 的地址下。
move_from< T >(addr: address): T - 从地址下删除类型为 T 的 Resource 并返回这个资源。
borrow_global< T >(addr: address): &T - 返回地址下类型为 T 的 Resource 的不可变引用。
borrow_global_mut< T >(addr: address): &mut T - 返回地址下类型为 T 的 Resource 的可变引用。
exists< T >(address): bool:判断地址下是否有类型为 T 的 Resource。
incr 方法参数也是一个&signer,意味着该方法必须是一个账户合法签名过后才可以调用,
关键字 acquires,放在函数返回值之后,用来显式定义此函数获取的所有 Resource。
Signer::address_of(account) 从签名者中拿到 address
borrow_global_mut 上面有介绍到,可变借用到 address 下的 resource Counter,然后将 Counter 结构体下的 value 进行+1 操作。
这下面的两个方法则是 script 方法,它与上面两个函数有什么区别呢?
public fun : 方法可以在任何模块中被调用。
public(script) fun / public entry fun:script function 是模块中的入口方法,表示该方法可以通过控制台发起一个交易来调用,就像本地执行脚本一样
下个版本的 Move 会用 public entry fun 替代 public(script) fun
Self 则是代表自身 module。
# 创建新的测试环境aptos init --profile devtest --rest-url --faucet-url # 编译move合约aptos move compile --package-dir my-counter# 部署合约# 例如:aptos move publish --package-dir my-counter --named-addresses basecoin=0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664 --profile devtestaptos move publish --package-dir my-counter --named-addresses basecoin= --profile devtest# 调用合约# 例如:# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::init_counter --profile devtest# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::incr_counter --profile devtestaptos move run --function-id :::: --profile devtest# 列出指定账户的modules/resources信息aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile devtestaptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile devtest
编译好合约之后,我们可以通过 sdk 调用我们的合约。
我们可以选择通过 sdk 部署合约,也可以通过 sdk 调用 move 合约。
1、通过 sdk 部署合约
当我们编译完成之后,会在 move 合约文件夹下生成 build/ 文件夹
我们需要把 my-counter/build/Examples/bytecode_modules/MyCounter.mv 文件 copy 到SDK脚本下。
aptos move compile --package-dir my-countercp MyCounter.mv my-counter-sdk-demo/
2、部署合约相关的 sdk 代码
/** Publish a new module to the blockchain within the specified account */export async function publishModule(accountFrom: AptosAccount, moduleHex: string): Promise<string> { const moudleBundlePayload = new TxnBuilderTypes.TransactionPayloadModuleBundle( new TxnBuilderTypes.ModuleBundle([new TxnBuilderTypes.Module(new HexString(moduleHex).toUint8Array())]), ); const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([ client.getAccount(accountFrom.address()), client.getChainId(), ]); const rawTxn = new TxnBuilderTypes.RawTransaction( TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()), BigInt(sequenceNumber), moudleBundlePayload, 1000n, 1n, BigInt(Math.floor(Date.now() / 1000) + 10), new TxnBuilderTypes.ChainId(chainId), ); const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn); const transactionRes = await client.submitSignedBCSTransaction(bcsTxn); return transactionRes.hash;}
3、通过 SDK 发送交易
这里,我们以 my-counter 合约中的init_counter 和 incr_counter 为例。
构造两个方法用于调用这两个方法,从而实现客户端调用 init 和 incr 的功能。
async function initCounter(contractAddress: string, accountFrom: AptosAccount): Promise<string> { const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction( TxnBuilderTypes.ScriptFunction.natural( `${contractAddress}::MyCounter`, // 合约地址::合约名称 "init_counter", // script 函数方法 [], [], ), ); const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([ client.getAccount(accountFrom.address()), client.getChainId(), ]); const rawTxn = new TxnBuilderTypes.RawTransaction( TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()), BigInt(sequenceNumber), scriptFunctionPayload, 1000n, 1n, BigInt(Math.floor(Date.now() / 1000) + 10), new TxnBuilderTypes.ChainId(chainId), ); const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn); const transactionRes = await client.submitSignedBCSTransaction(bcsTxn); return transactionRes.hash;}async function incrCounter(contractAddress: string, accountFrom: AptosAccount): Promise<string> { const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction( TxnBuilderTypes.ScriptFunction.natural( `${contractAddress}::MyCounter`, "incr_counter", [], [], ), ); const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([ client.getAccount(accountFrom.address()), client.getChainId(), ]); const rawTxn = new TxnBuilderTypes.RawTransaction( TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()), BigInt(sequenceNumber), scriptFunctionPayload, 1000n, 1n, BigInt(Math.floor(Date.now() / 1000) + 10), new TxnBuilderTypes.ChainId(chainId), ); const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn); const transactionRes = await client.submitSignedBCSTransaction(bcsTxn); return transactionRes.hash;}
4、通过 SDK 获取账户里的资源信息。
resource 是存放在所属的账户地址下的,我们可以根据 account 地址,查询相关的 resource 信息。
getCounter()方法其实就是获取 my-counter 下的 **Counter **资源。
async function getCounter(contractAddress: string, accountAddress: MaybeHexString): Promise<string> { try { const resource = await client.getAccountResource( accountAddress.toString(), `${contractAddress}::MyCounter::Counter`, ); return (resource as any).data["value"]; } catch (_) { return ""; }}
其实这个效果就类似 sdk 里的
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf
最终的主函数
async function main() { assert(process.argv.length == 3, "Expecting an argument that points to the helloblockchain module"); const contractAddress = "0x173d51b1d50614b03d0c18ffcd958309042a9c0579b6b21fc9efeb48cdf6e0b0"; // 指定之前部署的合约地址 const bob = new AptosAccount(); // 创建一个测试地址 Bob console.log("\n=== Addresses ==="); console.log(`Bob: ${bob.address()}`); await faucetClient.fundAccount(bob.address(), 5_000); // 给 Bob 地址空投5000个测试token console.log("\n=== Initial Balances ==="); console.log(`Bob: ${await accountBalance(bob.address())}`); await new Promise<void>((resolve) => { readline.question( "Update the module with Alice's address, build, copy to the provided path, and press enter.", () => { resolve(); readline.close(); }, ); }); const modulePath = process.argv[2]; const moduleHex = fs.readFileSync(modulePath).toString("hex"); console.log('Init Counter Moudle.'); let txHash = await initCounter(contractAddress, bob); // 在bob下init Counter资源,此时bob下的Counter的value为0. await client.waitForTransaction(txHash); console.log("\n=== Testing Bob Get Counter Value ==="); console.log(`Initial value: ${await getCounter(contractAddress, bob.address())}`); console.log('========== Incr Counter Value, 1th =========='); txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为1. console.log(txHash); await client.waitForTransaction(txHash); await Sleep(100); console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。 console.log('========== Incr Counter Value, 2th =========='); txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为2. console.log(txHash); await client.waitForTransaction(txHash); await Sleep(100); console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。 console.log('========== Incr Counter Value, 3th =========='); txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为3. console.log(txHash); await client.waitForTransaction(txHash); await Sleep(100); console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。}if (require.main === module) { main().then((resp) => console.log(resp));}
执行效果
执行成功,在这里通过 SDK,给一个随机生成的账户 init 了 Counter 资源(Counter=0),然后 incr 了三次,所以最后 Counter 的 Value 为 3。
image-20220831200516865
my-counter 相关代码:https://github.com/99Kies/Aptos-Move-Dapp
[1]
Using CLI to Run a Local Testnet | Aptos Docs: https://aptos.dev/nodes/local-testnet/using-cli-to-run-a-local-testnet
[2]
通过 DAO,研究组织和媒体可以打破地域的限制,以社区的方式资助和生产内容。DAOrayaki将会通过DAO的形式,构建一个代表社区意志并由社区控制的功能齐全的去中心化媒体。欢迎通过文末方式提交与DAO、量子计算、星际移民、DA相关的内容,瓜分10000USDC赏金池!欢迎加入DAOrayaki社区,了解去中心化自治组织(DAO),探讨最新话题!
Media:https://media.daorayaki.org
Discord server: https://discord.gg/wNUPmsGsa4
Medium: https://medium.com/@daorayaki
Email: daorayaki@dorafactory.org
Twitter: @daorayaki_
微信助手:DAOrayaki-Media
小宇宙:DAOrayaki
资助地址: DAOrayaki.eth
投票进展:DAO Reviewer 3/0 通 过
研究种类:Aptos,Layer1
创作者:FF@DoraFactory
本文主要讲解操作 aptos cli 和 aptos sdk
# Clone the Aptos repo.git clone # cd into aptos-core directory.cd aptos-core# Run the scripts/dev_setup.sh Bash script as shown below. This will prepare your developer environment../scripts/dev_setup.sh# Update your current shell environment.source ~/.cargo/env# Skip this step if you are not installing an Aptos node.git checkout --track origin/devnet
Using CLI to Run a Local Testnet | Aptos Docs[1]
启动本地链
ps: 通过这个方法启动的本地链、数据都会保存在启动这条命令的当前文件夹下,以.aptos/ 文件存在
aptos node run-local-testnet --with-faucet
启动成功:
Completed generating configuration: Log file: "/Users/greg/.aptos/testnet/validator.log" Test dir: "/Users/greg/.aptos/testnet" Aptos root key path: "/Users/greg/.aptos/testnet/mint.key" Waypoint: 0:74c9d14285ec19e6bd15fbe851007ea8b66efbd772f613c191aa78721cadac25 ChainId: TESTING REST API endpoint: 0.0.0.0:8080 FullNode network: /ip4/0.0.0.0/tcp/6181Aptos is running, press ctrl-c to exitFaucet is running. Faucet endpoint: 0.0.0.0:8081
启动成功后会提示 rest api 和 faucet api 的地址。后面需要把这两个信息配置在 aptos cli 环境内。
配置 aptos cli 环境
为了通过命令行访问和调用本地测试链,我们需要给 aptos cli 根据上面的部署信息配置 config。
PROFILE=localaptos init --profile $PROFILE --rest-url --faucet-url
执行过程中,我们会得到如下的输出。我们可以选择输入一个秘钥,也可以默认随机生成。
Configuring for profile localUsing command line argument for rest URL Using command line argument for faucet URL Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]
确认之后,会创建一个账户并使用默认数量的 token 为其注资。
No key given, generating key...Account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1 doesn't exist, creating it and funding it with 10000 coinsAptos is now set up for account 7100C5295ED4F9F39DCC28D309654E291845984518307D3E2FE00AEA5F8CACC1! Run `aptos help` for more information about commands{ "Result": "Success"}
从现在开始,我们就可以通过添加--profile local命令以在本地测试网上运行它们。
--profile``kube-config
profile 的配置,会设置执行者地址、node-rest-api、faucet-api 信息。
# 列出cli控制的所有账户aptos account list# 为账户注资:aptos account fund --profile $PROFILE --account $PROFILE# 创建新的资源账户aptos account create-resource-account --profile $PROFILE --seed 1# 编译move合约aptos move compile --package-dir hello_blockchain# 部署合约aptos move publish --package-dir hello_blockchain --named-addresses basecoin= --profile local# 调用合约aptos move run --function-id :::: --profile local# 列出指定账户的modules/resources信息aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile localaptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile local# 合约升级aptos move publish --upgrade-policy `arbitrary`, `compatible`, `immutable` 对应 0,1,2 0 不做任何检查,强制替换code, 1 做兼容性检查(同样的public 函数,不能改变已有Resource的内存布局) 2 禁止升级 每次publish的时候会比较链上的policy和此次publish的policy(默认是1), 只有此次的policy小于链上的policy时才允许合约升级
module MyCounterAddr::MyCounter { use std::signer; struct Counter has key, store { value:u64, } public fun init(account: &signer){ move_to(account, Counter{value:0}); } public fun incr(account: &signer) acquires Counter { let counter = borrow_global_mut(signer::address_of(account)); counter.value = counter.value + 1; } public entry fun init_counter(account: signer){ Self::init(&account) } public entry fun incr_counter(account: signer) acquires Counter { Self::incr(&account) }}
MyCounter 源码分析
module 是发布在特定地址下的打包在一起的一组函数和结构体。使用 script 时需要与已发布的 module 或标准库一起运行,而标准库本身就是在 0x1 地址下发布的一组 module。
module MyCounterAddr::MyCounter{ } 则在该 MyCounterAddr 地址下(对应 Move.toml 下的 MyCounterAddr = "0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf")创建一个 module。
use std::signer,是使用标准库下的 signer module,Signer 是一种原生的类似 Resource 的不可复制的类型,它包含了交易发送者的地址。引入 signer 类型的原因之一是要明确显示哪些函数需要发送者权限,哪些不需要。因此,函数不能欺骗用户未经授权访问其 Resource。具体可参考源码[2]。
module std::signer { // Borrows the address of the signer // Conceptually, you can think of the `signer` as being a struct wrapper arround an // address // ``` // struct signer has drop { addr: address } // ``` // `borrow_address` borrows this inner field native public fun borrow_address(s: &signer): &address; // Copies the address of the signer public fun address_of(s: &signer): address { *borrow_address(s) } /// Return true only if `s` is a transaction signer. This is a spec function only available in spec. spec native fun is_txn_signer(s: signer): bool; /// Return true only if `a` is a transaction signer address. This is a spec function only available in spec. spec native fun is_txn_signer_addr(a: address): bool;}
Struct & Abilities
struct Counter has key, store { value:u64,}
使用 struct 定义了一个叫做 Counter 的结构体,同时被 key,store 两种限制符修饰。
Move 的类型系统灵活,每种类型都可以定义四种能力(abilities)。
它们定义了类型的值是否可以被复制、丢弃和存储。
这四种 abilities 限制符分别是: Copy, Drop, Store 和 Key。
它们的功能分别是:
Copy - 值可以被复制。
Drop - 在作用域(Scope)结束时值可以被丢弃。
Key - 值可以作为键值(Key)被「全局存储操作( global storage operations)」进行访问。
Store - 值可以被 存储 到全局状态。
这里用 key、store 修饰,则表示它不能被复制,也不能被丢弃或重新使用,但是它却可以被安全地存储和转移。
Abilities 的语法
基本类型和内建类型的 abilities 是预先定义好的并且不可改变: integers, vector, addresses 和 boolean 类型的值先天具有 copy、drop 和 store ability。
然而,结构体的 ability 可以按照下面的语法进行添加:
struct NAME has ABILITY [, ABILITY] { [FIELDS] }
一个简单的图书馆例子:
module Library { // each ability has matching keyword // multiple abilities are listed with comma struct Book has store, copy, drop { year: u64 } // single ability is also possible struct Storage has key { books: vector } // this one has no abilities struct Empty {}}
Move 白皮书中详细描述了 Resource 这个概念。最初,它是作为一种名为 resource 的结构体类型被实现,自从引入 ability 以后,它被实现成拥有 Key和 Store两种 ability 的结构体。Resource 可以安全的表示数字资产,它不能被复制,也不能被丢弃或重新使用,但是它却可以被安全地存储和转移。
Resource 是一种用 key 和 store ability 限制了的结构体:
module M { struct T has key, store { field: u8 }}
在代码中,Resource 类型有几个主要限制:
Resource 存储在帐户下。因此,只有在分配帐户后才会存在,并且只能通过该帐户访问。
一个帐户同一时刻只能容纳一个某类型的 Resource。
Resource 不能被复制;与它对应的是一种特殊的kind:resource,它与copyable不同,这一点在泛型章节中已经介绍。(这里可以抽象到 Rust 的所有权那)
Resource 必需被使用,这意味着必须将新创建的 Resource move到某个帐户下,从帐户移出的 Resource 必须被解构或存储在另一个帐户下。
刚才的案例
struct Counter has key, store { value:u64,}
所以这里就有一个和 solidity 的区别了,在 eth 上如果需要发行一个新资产,比如 usdc。那这个资产是记录在合约里的某个 map 中。而 move 就不同了,资产是作为 resource 存在用户地址下的。
定义函数
public fun init(account: &signer){ move_to(account, Counter{value:0});}public fun incr(account: &signer) acquires Counter { let counter = borrow_global_mut(signer::address_of(account)); counter.value = counter.value + 1;}public entry fun init_counter(account: signer){ Self::init(&account)}public entry fun incr_counter(account: signer) acquires Counter { Self::incr(&account)}
定义格式则是:
public fun 函数名(参数:参数类型){ }
move 函数默认是私有函数,只能在定义它们的模块中访问。关键字 public 将更改函数的默认可见性并使其公开,即可以从外部访问。
init 方法参数是一个&signer,意味着该方法必须是一个账户合法签名过后才可以调用,move_to则是 move 的一个原语,作用是发布、添加 Counter 资源到 signer 的地址下。Move 的账户模型,code 和 data 是存储在一个账户地址下的。
下面是列举的常用原语
move_to< T >(&signer, T):发布、添加类型为 T 的 Resource 到 signer 的地址下。
move_from< T >(addr: address): T - 从地址下删除类型为 T 的 Resource 并返回这个资源。
borrow_global< T >(addr: address): &T - 返回地址下类型为 T 的 Resource 的不可变引用。
borrow_global_mut< T >(addr: address): &mut T - 返回地址下类型为 T 的 Resource 的可变引用。
exists< T >(address): bool:判断地址下是否有类型为 T 的 Resource。
incr 方法参数也是一个&signer,意味着该方法必须是一个账户合法签名过后才可以调用,
关键字 acquires,放在函数返回值之后,用来显式定义此函数获取的所有 Resource。
Signer::address_of(account) 从签名者中拿到 address
borrow_global_mut 上面有介绍到,可变借用到 address 下的 resource Counter,然后将 Counter 结构体下的 value 进行+1 操作。
这下面的两个方法则是 script 方法,它与上面两个函数有什么区别呢?
public fun : 方法可以在任何模块中被调用。
public(script) fun / public entry fun:script function 是模块中的入口方法,表示该方法可以通过控制台发起一个交易来调用,就像本地执行脚本一样
下个版本的 Move 会用 public entry fun 替代 public(script) fun
Self 则是代表自身 module。
# 创建新的测试环境aptos init --profile devtest --rest-url --faucet-url # 编译move合约aptos move compile --package-dir my-counter# 部署合约# 例如:aptos move publish --package-dir my-counter --named-addresses basecoin=0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664 --profile devtestaptos move publish --package-dir my-counter --named-addresses basecoin= --profile devtest# 调用合约# 例如:# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::init_counter --profile devtest# aptos move run --function-id 0x8e00bd9827faf171996ef37f006dd622bb5c3e43ec52298a8f37fd38cd59664::MyCounter::incr_counter --profile devtestaptos move run --function-id :::: --profile devtest# 列出指定账户的modules/resources信息aptos account list --query modules --account 0xa1285adb4b8abedf5faf7a46d260c5844f1f64d59dd9b8869db1543cf5bbadf4 --profile devtestaptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf --profile devtest
编译好合约之后,我们可以通过 sdk 调用我们的合约。
我们可以选择通过 sdk 部署合约,也可以通过 sdk 调用 move 合约。
1、通过 sdk 部署合约
当我们编译完成之后,会在 move 合约文件夹下生成 build/ 文件夹
我们需要把 my-counter/build/Examples/bytecode_modules/MyCounter.mv 文件 copy 到SDK脚本下。
aptos move compile --package-dir my-countercp MyCounter.mv my-counter-sdk-demo/
2、部署合约相关的 sdk 代码
/** Publish a new module to the blockchain within the specified account */export async function publishModule(accountFrom: AptosAccount, moduleHex: string): Promise<string> { const moudleBundlePayload = new TxnBuilderTypes.TransactionPayloadModuleBundle( new TxnBuilderTypes.ModuleBundle([new TxnBuilderTypes.Module(new HexString(moduleHex).toUint8Array())]), ); const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([ client.getAccount(accountFrom.address()), client.getChainId(), ]); const rawTxn = new TxnBuilderTypes.RawTransaction( TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()), BigInt(sequenceNumber), moudleBundlePayload, 1000n, 1n, BigInt(Math.floor(Date.now() / 1000) + 10), new TxnBuilderTypes.ChainId(chainId), ); const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn); const transactionRes = await client.submitSignedBCSTransaction(bcsTxn); return transactionRes.hash;}
3、通过 SDK 发送交易
这里,我们以 my-counter 合约中的init_counter 和 incr_counter 为例。
构造两个方法用于调用这两个方法,从而实现客户端调用 init 和 incr 的功能。
async function initCounter(contractAddress: string, accountFrom: AptosAccount): Promise<string> { const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction( TxnBuilderTypes.ScriptFunction.natural( `${contractAddress}::MyCounter`, // 合约地址::合约名称 "init_counter", // script 函数方法 [], [], ), ); const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([ client.getAccount(accountFrom.address()), client.getChainId(), ]); const rawTxn = new TxnBuilderTypes.RawTransaction( TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()), BigInt(sequenceNumber), scriptFunctionPayload, 1000n, 1n, BigInt(Math.floor(Date.now() / 1000) + 10), new TxnBuilderTypes.ChainId(chainId), ); const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn); const transactionRes = await client.submitSignedBCSTransaction(bcsTxn); return transactionRes.hash;}async function incrCounter(contractAddress: string, accountFrom: AptosAccount): Promise<string> { const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction( TxnBuilderTypes.ScriptFunction.natural( `${contractAddress}::MyCounter`, "incr_counter", [], [], ), ); const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([ client.getAccount(accountFrom.address()), client.getChainId(), ]); const rawTxn = new TxnBuilderTypes.RawTransaction( TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()), BigInt(sequenceNumber), scriptFunctionPayload, 1000n, 1n, BigInt(Math.floor(Date.now() / 1000) + 10), new TxnBuilderTypes.ChainId(chainId), ); const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn); const transactionRes = await client.submitSignedBCSTransaction(bcsTxn); return transactionRes.hash;}
4、通过 SDK 获取账户里的资源信息。
resource 是存放在所属的账户地址下的,我们可以根据 account 地址,查询相关的 resource 信息。
getCounter()方法其实就是获取 my-counter 下的 **Counter **资源。
async function getCounter(contractAddress: string, accountAddress: MaybeHexString): Promise<string> { try { const resource = await client.getAccountResource( accountAddress.toString(), `${contractAddress}::MyCounter::Counter`, ); return (resource as any).data["value"]; } catch (_) { return ""; }}
其实这个效果就类似 sdk 里的
aptos account list --query resources --account 0x4200c2b801870f20a709abba80b6edb90a45ecd9b8acce9842b93d597602edcf
最终的主函数
async function main() { assert(process.argv.length == 3, "Expecting an argument that points to the helloblockchain module"); const contractAddress = "0x173d51b1d50614b03d0c18ffcd958309042a9c0579b6b21fc9efeb48cdf6e0b0"; // 指定之前部署的合约地址 const bob = new AptosAccount(); // 创建一个测试地址 Bob console.log("\n=== Addresses ==="); console.log(`Bob: ${bob.address()}`); await faucetClient.fundAccount(bob.address(), 5_000); // 给 Bob 地址空投5000个测试token console.log("\n=== Initial Balances ==="); console.log(`Bob: ${await accountBalance(bob.address())}`); await new Promise<void>((resolve) => { readline.question( "Update the module with Alice's address, build, copy to the provided path, and press enter.", () => { resolve(); readline.close(); }, ); }); const modulePath = process.argv[2]; const moduleHex = fs.readFileSync(modulePath).toString("hex"); console.log('Init Counter Moudle.'); let txHash = await initCounter(contractAddress, bob); // 在bob下init Counter资源,此时bob下的Counter的value为0. await client.waitForTransaction(txHash); console.log("\n=== Testing Bob Get Counter Value ==="); console.log(`Initial value: ${await getCounter(contractAddress, bob.address())}`); console.log('========== Incr Counter Value, 1th =========='); txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为1. console.log(txHash); await client.waitForTransaction(txHash); await Sleep(100); console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。 console.log('========== Incr Counter Value, 2th =========='); txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为2. console.log(txHash); await client.waitForTransaction(txHash); await Sleep(100); console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。 console.log('========== Incr Counter Value, 3th =========='); txHash = await incrCounter(contractAddress, bob); // bob调用一次incrCounter方法,此时Counter为3. console.log(txHash); await client.waitForTransaction(txHash); await Sleep(100); console.log(`New value: ${await getCounter(contractAddress, bob.address())}`); // 获取bob地址下的Counter值,并输出。}if (require.main === module) { main().then((resp) => console.log(resp));}
执行效果
执行成功,在这里通过 SDK,给一个随机生成的账户 init 了 Counter 资源(Counter=0),然后 incr 了三次,所以最后 Counter 的 Value 为 3。
image-20220831200516865
my-counter 相关代码:https://github.com/99Kies/Aptos-Move-Dapp
[1]
Using CLI to Run a Local Testnet | Aptos Docs: https://aptos.dev/nodes/local-testnet/using-cli-to-run-a-local-testnet
[2]
通过 DAO,研究组织和媒体可以打破地域的限制,以社区的方式资助和生产内容。DAOrayaki将会通过DAO的形式,构建一个代表社区意志并由社区控制的功能齐全的去中心化媒体。欢迎通过文末方式提交与DAO、量子计算、星际移民、DA相关的内容,瓜分10000USDC赏金池!欢迎加入DAOrayaki社区,了解去中心化自治组织(DAO),探讨最新话题!
Media:https://media.daorayaki.org
Discord server: https://discord.gg/wNUPmsGsa4
Medium: https://medium.com/@daorayaki
Email: daorayaki@dorafactory.org
Twitter: @daorayaki_
微信助手:DAOrayaki-Media
小宇宙:DAOrayaki
No activity yet