# Move Cheatsheet-0 项目结构&账户结构 **Published by:** [0xturbofish.eth](https://paragraph.com/@0xturbofish/) **Published on:** 2022-07-25 **URL:** https://paragraph.com/@0xturbofish/move-cheatsheet-0 ## Content Overview对于Move语言本身就不过多介绍了,脱身于原Facebook Diem(Libra)项目,之后衍生出了不少基于Move开发的公链项目,感兴趣小伙伴可以去研究一下,初看到这个list也超出了我的预期。所以感兴趣尤其是有一定rust的基础的朋友不要错过! Move目前的开发doc已经很完善了,这里主要是把自己阅读move book里的一些note记录下来,当作cheetsheet方便开发过程中查找。原文档的顺序不太适合有一定经验的开发者阅读,简单调整了下顺序。这里主要以aptos move开发为例。CheatSheet原move book是从基本语法讲起,然后过度到类型介绍,流程控制,结构和多态,账户模型最后到项目模型和测试。 这里大体是反过来的顺序,从顶向下来看。项目结构Move项目结构整体类似Rust项目结构,这里是aptos framwork项目结构供参考。a_move_package ├── Move.toml (required) ├── sources (required) ├── examples (optional, test & dev mode) ├── scripts (optional) ├── doc_templates (optional) └── tests (optional, test mode) 其中sources以及Move.toml都比较好理解,值得注意的是Move.toml中会辅助定义named address,这个会在部署合约的时候用到。 重点关注一下Move.toml中,各个地址之间的关系,考虑两种情况本module作为最终部署的合约入口本module作为中间层,被其他module调用本module作为最终部署的合约入口[package] name = "ExamplePkg" ... [addresses] my_deployment_addr = "0x007" // other deployment addr for dependency std = "0x01" 同样参考aptos framework的coin例子,这里我们一个非常简单的智能合约(move中叫module)就可以这样完成。module my_deployment_addr::message { use std::string; use std::error; ... struct MessageHolder has key { message: string::String, } public entry fun set_message(account: signer, message_bytes: vector<u8>) {...} public fun get_message(addr: address): string::String acquires MessageHolder {...} } 这里为了便于理解,把本合约named address叫做my_deployment_addr。部署时候,需要以这个地址部署,部署完成后。合约位于my_deployment_addr的module空间下,这里实际部署的地址为0x007。同时该合约还调用了位于0x001地址下的另一个合约。 需要的注意的是,所有的地址在编译时候需要会被翻译为实际地址,否则在部署时无法找到合约需要部署的地址空间。同样这里会有两种方式制定 1/ 编译器CLI传入 2/ Move.toml中制定。本module作为中间层通常软件开发的习惯是config统一定义,首先module作为中间层时,我们同样可以制定地址。比如下面的std = "0x01",是非常明确地址的标准库。但是对于我们自定义的很多中间层而言,并不希望在这里指定地址。于是出现了另一种wildcard指定方式。[package] name = "ExamplePkg" ... [addresses] my_deployment_addr = "_" // other deployment addr for dependency std = "0x01" 这里用_替代,使得其他依赖此package的可以自定义相关地址。项目结构总结简单来说,move项目结构比较简单。主要容易混淆的地方在与部署地址以及module依赖关系中,地址的制定和传递。 熟悉以太部署的小伙伴可能很自然会有一个反应,部署地址有了,那么合约部署到了哪里?谁又有权限呢?简单来讲,不同于以太部署会生成对应的合约地址,对应代码bytecode位于新生成的合约地址空间。Move的合约会被保存在部署地址的空间下,这里在下个部分仔细讲。账户结构Move官方文档的对于账户空间的描述简单易懂,伪代码如下struct GlobalStorage { resources: Map<(address, ResourceType), ResourceValue> modules: Map<(address, ModuleName), ModuleBytecode> } 描述为,账户可访问空间包括且仅包括两部分,账户无法访问这两部分之外的账户资源(这里的资源更偏操作系统定义,包括文件系统,网络等) 文档中对Move Program的描述是,通过Move Program实现对于global storage的读写。 我们来看一下两部分,其中resources表示该账户的空间的数据部分,而modules表示该账户空间的代码部分。值得注意的是,这里账户的数据空间(resources)以及代码空间(modules)的Map键值结构类似,都是地址以及另一个相关Name/Type的tuple。这样就意味着:对于同一个地址,同一个资源类型只会存储一个值。对于同一个地址,同名的module,也只能存在一个。这样的描述仍然比较抽象,回到上面项目结构中的例子。项目结构中,地址0x007部署了,module名为message的代码,该代码描述了MessageHolder字符以及提供了getter&setter借口,我们来看一下部署完成之后的代码和数据空间。请注意,前面提到了,地址0x007将代码部署在其本地空间中,所以下面我们讨论的是0x007的空间而非其他地址(比如新生成的地址)。 其代码空间看起来是:(0x007, message) : { "module_description": {...} "byte_code": {...} } 一个以(0x007, message)为key,module abi(description)以及bytecode为值的k-v pair出现在代码空间中。 如果我们不调用该module的setter结构,数据空间当中当然没有数据,考虑我们调用0x007::message::set_messageset了这个值Hello Move from 0x007,此时的数据空间将会变成(0x007, 0x007::message::MessageHolder) : { "message" : "Hello Move from 0x007" } 这时如果另一个账户0x008,同样调用了0x007::message::set_message设置为Hello Move from 0x008会发生什么呢?首先0x008对0x007的账户以及数据空间并没有影响然后0x008并没有进行任何部署module的操作,所以其代码空间也没有影响最后0x008的数据空间,这个key发生了变化(0x008, 0x007::message::MessageHolder)其数据空间将会变为(0x008, 0x007::message::MessageHolder) : { "message" : "Hello Move from 0x008" } Summary简单小结一下,本来是想写一个cheetsheet,主要是总结一下各种易忘点,比如各种primitive type的特性,copy&drop的特性,reference和global storage borrow等等。慢慢抛开项目以及账户结构,这些写的很空泛。于是这一篇主要以实例的方式理解了一下move合约的大框架,希望对于不是开发者小伙伴也能有些帮助。后续主要是个人读技术文档以及编程过程中的notes,当作一个工具箱使用,大家happy coding = )也欢迎一起学习指正。 ## Publication Information - [0xturbofish.eth](https://paragraph.com/@0xturbofish/): Publication homepage - [All Posts](https://paragraph.com/@0xturbofish/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@0xturbofish): Subscribe to updates