目录
欢迎关注笔者 twitter: @wallywxy沟通交流
Move 语言伴随着新公链 Aptos 和 Sui 迅速崛起,其设计理念“资源是一等公民”,非常适合进行资产的数字化编程。Move 的白皮书中指出数字化资源应该不可以被复制,不能被隐式丢弃,只能在程序的存储地址之间进行移动,依据该理念 Move 为资产的稀缺性表达 和 访问控制提供了高于 Solidity 的表达能力。本文主要探求一下 Move 的基本概念,体验基本语法,感受在 Aptos 上如何部署和调用 Move 的hello world。
Move 是基于 Rust 演化出来的合约编程语言,基本的语法结构和 Rust 的范式类似,但是更加简单,也做了一些支持资源特征表达方面的专门设计。
如下所示是 Move 工程的典型结构,其中 Move.toml 和 sources 是必备的,其他部分按需安排,其中 Move.toml 是工程的配置管理文件,包括版本号、依赖、包名什么的。sources 里面就是源码,源码以 .move 作为后缀名。
a_move_package
├── Move.toml (required)
├── sources (required)
├── examples (optional, test & dev mode)
├── scripts (optional)
├── doc_templates (optional)
└── tests (optional, test mode)
如下所示为一个简单的 Move 的源码实例, 在 Move 中代码组织形式包括两种,一种是 module 一种叫 script 。
// module example
module 0xCAFE::BasicCoin {
struct Coin has key {
value: u64,
}
public fun mint(account: signer, value: u64) {
move_to(&account, Coin { value })
}
}
// script example
script {
// Import the Debug module published at the named account address std.
use std::debug;
const ONE: u64 = 1;
fun main(x: u64) {
let sum = x + ONE;
debug::print(&sum)
}
}
其中 module 的完整语法定义为:
module <address>::<identifier> {
(<use> | <friend> | <type> | <function> | <constant>)*
}
module : 关键词address: 该 module 发布到链上的用户的地址,在写合约代码时,可以用别名标识identifier: 模块名称,一般大写驼峰结构表示
从区块链或者智能合约虚拟机的视角而言,Move 在链上存储的数据大体上分为 module 和 state 两块,也就是合约的字节码和合约运行过程中产生数据,它们的存储示意图如下, 可以看到 Move 的数据都存储在账户地址下,这为其权限控制提供了很好的帮助。

相对而言,solidity 大部分 ERC20 合约内部用户的数据是以状态变量的形式存储的,这样一来用户的核心资产数据存储的是在合约内部,这和 move 存储在账户下面是天然的不同,这样 Solidity 中的资产除了要控制好用户的访问安全之外,也要控制好合约自身的安全性,甚至可以说用户数字资产的安全性完全取决于合约的控制逻辑。

由于 Move 的合约要上链的话其可能会涉及到具体链的 framework, 为了操作方便,这里笔者以 Aptos 的例子来进行阐述,示例代码。
运行 aptos move 即可查看我们 aptos move 的可用功能:

aptos move 命令行工具
Aptos 区块链的交互以及 module 的存储都和个人的账户息息相关,因此这里我们得准备一下个人的账户:
aptos init ,过程中有一些交互,可以直接默认值往下走,会得到一个.aptos文件夹 ,存储了生成的账户信息和交互的链节点的配置信息: profiles:
default:
private_key: "your private_key"
public_key: "0x4acd9175462e7382307af2d43a50e540042ca6cdcab3329618c939a9264c0096"
account: 49cf0e5ae7e483f82119eb697dd8da93af1a1703a5cd152cbb99001b3d1eb3ba
rest_url: "https://fullnode.devnet.aptoslabs.com/v1"
faucet_url: "https://faucet.devnet.aptoslabs.com/" 给本地生成账户接水(接受测试 Token),运行 aptos account fund-with-faucet --account default :
{
"Result": "Added 50000 coins to account 49cf0e5ae7e483f82119eb697dd8da93af1a1703a5cd152cbb99001b3d1eb3ba"
} 好了现在身份和钱(可能不一定够,如果不够多接几次水,或者用钱包去接,钱包会弄得多一点)都有了,我们进入下一个环节。
进入 示例代码moveasy/src/contract/aptos 这里保存了一些 aptos 上的示例,我们从 hello-world 开始。合约的功能很简单,核心方法就是 get_message 和 set_message ,联盟链的同学可能对这个比较熟悉,就是存证和取证 😃。
前文提到 move 中的 module 是存储在具体的账户之下,在本地编译调试时为了方便,可以采用 named-addresses 也就是别名来替代 0xbb 类型的真实地址,因此在编译、测试等命令运行时也要加上相应的默认 named-addresses
编译合约
aptos move compile --named-addresses hello_blockchain=default
返回值如下,这里就验证了module 存储在账户的概念了,Result 中的 49cf… 就是个人的公钥信息。
{
"Result": [
"49cf0e5ae7e483f82119eb697dd8da93af1a1703a5cd152cbb99001b3d1eb3ba::message"
]
}
如图 build 文件夹是编译出来的内容,我们可以看到 message.mv 这个就是本例子中的字节码,其依赖的部分除了 move 语言自身的依赖库 MoveStdlib 还包含了 AptosFramewrok 和 AptosStdlib

本地测试合约,测试代码如下
#[test(account = @0x1)]
public entry fun sender_can_set_message(account: signer) acquires MessageHolder {
let addr = signer::address_of(&account);
aptos_framework::account::create_account_for_test(addr);
set_message(account, string::utf8(b"Hello, Blockchain"));
assert!(
get_message(addr) == string::utf8(b"Hello, Blockchain"),
ENO_MESSAGE
);
} 运行指令:aptos move test --named-addresses hello_blockchain=default
正确执行输出:

1. 发布合约,运行指令 aptos move publish --named-addresses hello_blockchain=default

至此这个hello-world的 module 就被部署到你的个人链上账户下面了,下面针对发布合约进行调用。
使用命令行提交交易
aptos move run \\
--function-id 'default::message::set_message' \\
--args 'string:hello, blockchain'aptos move run \\
--function-id 'default::message::set_message' \\
--args 'string:hello, blockchain'
使用 RestAPI 读取数据
https://fullnode.devnet.aptoslabs.com/v1/accounts/49cf0e5ae7e483f82119eb697dd8da93af1a1703a5cd152cbb99001b3d1eb3ba/resource/0x49cf0e5ae7e483f82119eb697dd8da93af1a1703a5cd152cbb99001b3d1eb3ba::message::MessageHolder
返回值:
{
"type": "0x49cf0e5ae7e483f82119eb697dd8da93af1a1703a5cd152cbb99001b3d1eb3ba::message::MessageHolder",
"data": {
"message": "hello, blockchain", // 返回的消息体
"message_change_events": {
"counter": "0",
"guid": {
"id": {
"addr": "0x49cf0e5ae7e483f82119eb697dd8da93af1a1703a5cd152cbb99001b3d1eb3ba",
"creation_num": "4"
}
}
}
}
}
至此,我们完整地体验了 Move 合约的完整调用流程。
欢迎关注笔者 twitter: @wallywxy沟通交流
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
我有一大串格式化数据(例如JSON),我想使用Psychinruby同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解