{"content":{"title":"Aptos合约开发之HelloWorld详解篇","body":"## 前言\r\n在上篇文章里，我们一起搭建了Aptos开发环境，并且成功运行了Hello World程序。接下来，我将以Hello World程序为例。一起来熟悉Aptos工程中的基本概念。\r\n\r\n## 关键词\r\n\r\nAptos工程项目拆解、Move语法、Cargo包管理工具、测试用例\r\n\r\n## Aptos工程项目拆解\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/D1unoNGy6320352be81f1.png)\r\n\r\n接下来，我会使用上图中的Move.toml和HelloAptos.move文件来进行讲解。\r\n\r\n### Move.toml\r\n\r\nAptos项目的包管理工具使用的是Cargo。Cargo的作用和Python的pip，Nodejs的npm类似。\r\n\r\n在Move.toml文件中，依赖包的来源有很多种，例如crates.io、git仓库、本地库文件等等。自然而然，不同的来源应当有不同的书写方式。\r\n\r\n通常情况下，我们使用以下两种的方式来设置依赖包，基本上就能满足绝大多数的场景需求。\r\n\r\n\r\n- 依赖包来自于crates.io时候，需要列出包名称和版本。\r\n- 依赖包来自于git仓库时，需要指定git地址、相关分支名等。\r\n\r\n```\r\n[dependencies]\r\ntime = \"0.1.12\"\r\n只需要名称和版本字符串。默认情况下，Cargo是准备在crates.io上查找依赖项。\r\n\r\nrand = { git = \"https:\/\/github.com\/rust-lang-nursery\/rand.git\" }\r\n由于我们尚未指定任何其他信息，因此 Cargo 假定我们打算使用最新的提交master分支来构建。\r\n\r\nrand = { git = \"https:\/\/github.com\/rust-lang-nursery\/rand\", branch = \"next\" }\r\n我们指定了next分支，那么Cargo就会拉取next分支的包。除此之外，你还可以将git字段和rev、tag还有branch这些用于指定其他内容的字段搭配起来使用。\r\n\r\nAptosFramework = { git = \"https:\/\/github.com\/aptos-labs\/aptos-core.git\", subdir = \"aptos-move\/framework\/aptos-framework\/\", rev = \"devnet\" }\r\n我们指定了subdir文件夹和devnet分支，然后Cargo将拉取git路径的subdir文件夹下的devnet分支来构建我们的程序。\r\n```\r\n至此，除了这两种常用的引包方式外，更多关于Cargo设置依赖的方法，这里就不再罗列赘述了，需要查看的请移步查看。\r\n\r\n>http:\/\/llever.com\/cargo-book-zh\/reference\/manifest.zh.html#dependency-sections\r\n\r\n## HelloAptos.move\r\n\r\nMove语法这里不做罗列赘述。只把我认为使用频率较高的先列出来，其余语法遇到再学不迟。\r\n\r\n### 数据类型：\r\n- 基本数据类型：整型 (u8, u64, u128)、bool、address\r\n- 自定义数据类型：Struct\r\n- Resource 类型\r\n- 内置数据类型：Vector\r\n- 签名者数据类型：signer\r\n\r\n基本的数据类型里没有浮点型，那么小数计算如何处理呢？\r\n>Solidity中通过单位的不同处理小数，10^18 wei=1 ether，并且Solidity有SafeMath这样的lib可供加减乘除使用。\r\n\r\n>那Aptos呢？在Aptos里，你可以把Solidity的Safemath用Move重写一遍，或者使用Sui已封装好的lib。这个在后续的文章中再进行研究。\r\n\r\n传统资产合约编程，最大也是最多的安全问题就是：类型溢出。Move的一个大特色，就是用Resource类型来解决这一程序员编程遇到的bug问题。\r\n\r\nResource数据类型，是一种特殊的结构体类型。Resource只能用struct进行定义，同时必须使用key这个abilitities。换言之，只有key，store能力，没有drop，copy的就是resource。\r\n\r\n对resource的增删改查需要通过特殊的函数进行包裹后进行调用，特殊的函数是指的borrow_global和borrow_global_mut。\r\n\r\n#### Resource的定义\r\n```\r\n\/\/Resouce是一种特殊的结构体类型。Resource只能用struct关键字定义，同时必须拥有key、store这两种abilitities。\r\n\/\/例如HelloAptos.move中的Message\r\nstruct Message has key{\r\n    msg:string::String,\r\n    message_change_events: event::EventHandle<MessageEvent>,\r\n}\r\n```\r\n这里的Message就是合约的resource。简单点理解，resource就像一个保险箱，你只能用你自己的账号打开并且操作保险箱里面的内容。你能做的操作只是对属于你的资源(resource)进行增删改查。\r\n\r\n#### Resource的增删改查\r\n- 添加元素\r\n```\r\n\/\/移动resource到account需要使用内置函数move_to()。第一个参数\r\nnative fun move_to<T: key>(account: &signer, value: T);\r\n\r\nmove_to(account, Message {\r\n   msg:message,\r\n   message_change_events: event::new_event_handle<MessageEvent>(account),\r\n});\r\n```\r\n- 删除元素\r\n```\r\nmove_to(account, Message {\r\n   msg:message,\r\n   message_change_events: event::new_event_handle<MessageEvent>(account),\r\n});\r\n```\r\n- 修改元素\r\n```\r\n\/\/可变引用（＆mut）: 可变引用赋予我们读取和更改值的能力。\r\n\/\/native fun borrow_global_mut<T: key>(addr: address): &mut T;\r\nlet old_message = borrow_global_mut<Message>(account_addr);\r\n\/\/修改resource中的内容\r\nold_message.msg = message;\r\n\r\n```\r\n- 查询元素\r\n```\r\n\/\/全局函数 borrow_global 返回了对 Resource T 的不可变引用。不可变的引用（＆）允许我们在不更改值的情况下读取值。其签名如下：\r\n\/\/native fun borrow_global<T: key>(addr: address): &T;\r\n\r\n*&borrow_global<Message>(addr).msg\r\n\r\n\/\/全局函数 borrow_global_mut 返回了对 Resource T 的可变引用可变引用（＆mut）赋予我们读取和更改值的能力。\r\n\/\/native fun borrow_global_mut<T: key>(addr: address): &mut T;\r\n```\r\n\r\n- 检查资源\r\n```\r\nexists<Message>(account_addr)\r\n```\r\n\r\n### 自定义数据类型\r\n- struct\r\nstruct的定义方式和C语言的的很类似。\r\n\r\n### 打印日志\r\n- debug::print()\r\n```\r\ndebug::print(&account_addr);\/\/打印账户地址\r\ndebug::print(&1234); \/\/打印1234\r\n```\r\n需要注意的是，目前move的debug和assert功能都不完善，传参都是用引用。\r\n\r\n## 测试用例\r\n\r\n```\r\n#[test(account = @0x1)]\r\npublic entry fun sender_can_set_message(account: signer) acquires Message {\r\n    let addr = signer::address_of(&account);\r\n    say_message(&account,  b\"Hello, Aptos\");\r\n    debug::print(&account);\r\n    debug::print(&3456);\r\n    \/\/无法打印字符串，因为move没有string类型\r\n    \/\/let log : string::String = b\"hello world!\";\r\n    \/\/debug::print(&log);\r\n    assert!(\r\n      get_message(addr) == string::utf8(b\"Hello, Aptos\"),\r\n      ENO_MESSAGE\r\n    );\r\n}\r\n```\r\n在HelloAptos.move中编写好测试用例后，控制台输入`sudo aptos move test`。在控制台可以看到debug的日志。\r\n```\r\nRunning Move unit tests\r\n[debug] 0000000000000000000000000000000000000000000000000000000000000001\r\n[debug] (&) { 0000000000000000000000000000000000000000000000000000000000000001 }\r\n[debug] 3456\r\n[ PASS    ] 0xfbd4140a63e8fe7d6e9e25aff78e11876b1b6c21bc8d34b51cbfff40ca03164b::Message::sender_can_set_message\r\nTest result: OK. Total tests: 1; passed: 1; failed: 0\r\n{\r\n  \"Result\": \"Success\"\r\n}\r\n```\r\n\r\n今天就先讲解到这儿，因为Resource是Move里很重要的数据类型，是它让Move变得独一无二，安全且强大。所以在接下来的文章里，咱们一起动手操作Resource，加深我们对Resource的认识。\r\n\r\n### 参考文档\r\n\r\n- https:\/\/github.com\/MystenLabs\/sui\/blob\/main\/crates\/sui-framework\/deps\/move-stdlib\/sources\/fixed_point32.move\r\n\r\n- http:\/\/llever.com\/cargo-book-zh\/reference\/manifest.zh.html#dependency-sections"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/6721","address":null},"history":null,"timestamp":1663055237,"version":1}