{"content":{"title":"Aptos合约开发之部署ERC20合约","body":"## 一、前言\r\n距上篇教程发出后不久，Aptos官方对合约框架进行了版本升级，目前的版本是0.3.1 。因此在本篇文章开始之前，各位需要注意下开发环境。否则，这会对你后续的开发和部署造成很多麻烦！\r\n\r\n## 二、关键词\r\n框架升级、测试网升级、[ERC20合约](https:\/\/learnblockchain.cn\/article\/339)部署、代币合约开发、Resource资源类型\r\n\r\n## 三、注意\r\n\r\n1、本篇教程使用Aptos-Cli V0.3.1进行开发。\r\n\r\n2、在本篇以及后续教程中，Move.toml中的依赖路径将使用local path替代远程链接这种形式。这么做的目的是为了避免因为网络等原因导致的合约无法部署等问题。因此，请去官方github下载最新Aptos-core到你的WSL2中。\r\n\r\n```\r\n[dependencies]\r\n#AptosFramework = { git = \"https:\/\/github.com\/aptos-labs\/aptos-core.git\", subdir = \"aptos-move\/framework\/aptos-framework\/\", rev = \"devnet\" }\r\n#MoveStdlib = { git = \"https:\/\/github.com\/aptos-labs\/aptos-core.git\", subdir = \"aptos-move\/framework\/move-stdlib\", rev = \"devnet\" }\r\nAptosFramework = { local = \"\/home\/ouhuang\/aptos-core-main\/aptos-move\/framework\/aptos-framework\" }\r\nAptosStdlib = { local = \"\/home\/ouhuang\/aptos-core-main\/aptos-move\/framework\/aptos-stdlib\" }\r\n```\r\n\r\n3、window环境下，在文件资源管理器目录下 `\\\\wsl$` 打开虚拟机文件夹目录，因此可以直接把window下的任何文件拖到WSL2中。\r\n\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/CSj9AwrI63228a9f1a57b.png)\r\n\r\n\r\n4、由于Aptoes-Cli与Devnet版本不匹配导致的无法部署问题，官方表示在接下来版本中会修复这个问题。\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/nKjDjtJr63228aa8caf58.png)\r\n\r\n\r\n## 四、创建项目\r\n1、创建项目工程\r\n```\r\n\/\/创建项目文件夹\r\nmkdir erc20_aptos\r\n\r\n\/\/创建账号\r\naptos init \r\n\r\n\/\/创建合约目录目录\r\nmkdir sources\r\nmkdir sources\/modules\r\n\r\n\/\/创建项目配置文件\r\ntouch Move.toml\r\n```\r\n2、修改Move.toml\r\n```\r\n[package]\r\nname = \"StandardErc20\"\r\nversion = \"0.0.1\"\r\n\r\n[dependencies]\r\n#AptosFramework = { git = \"https:\/\/github.com\/aptos-labs\/aptos-core.git\", subdir = \"aptos-move\/framework\/aptos-framework\/\", rev = \"devnet\" }\r\n#MoveStdlib = { git = \"https:\/\/github.com\/aptos-labs\/aptos-core.git\", subdir = \"aptos-move\/framework\/move-stdlib\", rev = \"devnet\" }\r\nAptosFramework = { local = \"\/home\/ouhuang\/aptos-core-main\/aptos-move\/framework\/aptos-framework\" }\r\nAptosStdlib = { local = \"\/home\/ouhuang\/aptos-core-main\/aptos-move\/framework\/aptos-stdlib\" }\r\n[addresses]\r\nTestCoin = '032621a894a17d900a7f8cfd7581380f02afdedb9818a07138fde2bb1a420516'\r\n```\r\n\r\n\r\n3、检查账号是否有Gas\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/erc20_aptos$ aptos account list --query resources --account  032621a894a17d900a7f8cfd7581380f02afdedb9818a07138fde2bb1a420516\r\n```\r\n\r\n4、编写Erc20合约\r\n\r\n在modules下创建standard_erc20.move文件。\r\n```\r\nmodule TestCoin::StandardErc20{\r\n    use std::signer;\r\n    use std::string;\r\n\r\n    struct Coin has store {\r\n        value : u128,\r\n    }\r\n\r\n    struct CoinStore has key {\r\n        coin : Coin,\r\n    }\r\n\r\n    struct CoinInfo has key {\r\n        \/\/ name\r\n        name: string::String,\r\n        \/\/ symbol\r\n        symbol: string::String,\r\n        \/\/ decimals\r\n        decimals: u8,\r\n        \/\/ suppley\r\n        supply: u128,\r\n        \/\/ capcity\r\n        cap: u128,\r\n    }\r\n\r\n        \/\/\/ Address of the owner of this module\r\n    const MODULE_OWNER: address = @TestCoin;\r\n\r\n    const THE_ACCOUNT_HAS_BEEN_REGISTERED : u64 = 1;\r\n\r\n    const INVALID_TOKEN_OWNER : u64 = 2;\r\n\r\n    const THE_ACCOUNT_IS_NOT_REGISTERED : u64 = 3;\r\n\r\n    const INSUFFICIENT_BALANCE : u64 = 4;\r\n\r\n    const ECOIN_INFO_ALREADY_PUBLISHED : u64 = 5;\r\n\r\n    const EXCEEDING_THE_TOTAL_SUPPLY : u64 = 6;\r\n\r\n    public fun getBalance(owner: address) : u128 acquires CoinStore{\r\n\r\n        assert!(is_account_registered(owner), THE_ACCOUNT_IS_NOT_REGISTERED);\r\n\r\n        borrow_global<CoinStore>(owner).coin.value\r\n    }\r\n\r\n\r\n    public fun is_account_registered(account_addr : address) : bool{\r\n        exists<CoinStore>(account_addr)\r\n    }\r\n  \r\n    fun deposit(account_addr : address, coin : Coin) acquires CoinStore {\r\n\r\n        assert!(is_account_registered(account_addr), THE_ACCOUNT_IS_NOT_REGISTERED);\r\n\r\n        let balance = getBalance(account_addr);\r\n\r\n        let balance_ref = &mut borrow_global_mut<CoinStore>(account_addr).coin.value;\r\n\r\n        *balance_ref = balance + coin.value;\r\n\r\n        let Coin { value:_ } = coin;\r\n    }\r\n\r\n    fun withdraw(account_addr : address, amount : u128) : Coin acquires CoinStore {\r\n        assert!(is_account_registered(account_addr), THE_ACCOUNT_IS_NOT_REGISTERED);\r\n        let balance = getBalance(account_addr);\r\n\r\n        assert!(balance >= amount, INSUFFICIENT_BALANCE);\r\n\r\n        let balance_ref = &mut borrow_global_mut<CoinStore>(account_addr).coin.value;\r\n\r\n        *balance_ref = balance - amount;\r\n\r\n        Coin { value: amount }\r\n    }\r\n\r\n\r\n    public entry fun initialize(address : &signer, name : vector<u8>, symbol : vector<u8>, decimals : u8, supply : u128)  {\r\n\r\n        assert!(signer::address_of(address) == MODULE_OWNER, INVALID_TOKEN_OWNER);\r\n\r\n        assert!(!exists<CoinInfo>(MODULE_OWNER), ECOIN_INFO_ALREADY_PUBLISHED);\r\n\r\n        move_to(address, CoinInfo{name : string::utf8(name), symbol : string::utf8(symbol), decimals, supply, cap : 0});\r\n    }\r\n\r\n\r\n    public entry fun register(address : &signer) {\r\n        let account = signer::address_of(address);\r\n\r\n        assert!(!exists<CoinStore>(account), THE_ACCOUNT_HAS_BEEN_REGISTERED);\r\n\r\n        move_to(address, CoinStore{ coin : Coin{ value : 0 } });\r\n    }\r\n\r\n\r\n    public entry fun mint(owner : &signer,to : address,amount : u128) acquires CoinStore,CoinInfo{\r\n\r\n        assert!(signer::address_of(owner) == MODULE_OWNER, INVALID_TOKEN_OWNER);\r\n\r\n        assert!(borrow_global<CoinInfo>(MODULE_OWNER).cap + amount <= borrow_global<CoinInfo>(MODULE_OWNER).supply,EXCEEDING_THE_TOTAL_SUPPLY);\r\n\r\n        deposit(to, Coin { value : amount });\r\n\r\n        let cap = &mut borrow_global_mut<CoinInfo>(MODULE_OWNER).cap;\r\n        *cap = *cap + amount;\r\n    }\r\n\r\n\r\n\r\n    public entry fun transfer(from : &signer, to : address, amount : u128) acquires CoinStore {\r\n\r\n        let coin = withdraw(signer::address_of(from), amount);\r\n\r\n        deposit(to, coin);\r\n    }\r\n\r\n\r\n    public entry fun burn(owner : &signer, amount : u128) acquires CoinStore,CoinInfo {\r\n\r\n        assert!(signer::address_of(owner) == MODULE_OWNER, INVALID_TOKEN_OWNER);\r\n\r\n        let coin = withdraw(signer::address_of(owner), amount);\r\n        let Coin { value: amount } = coin;\r\n\r\n        let cap = &mut borrow_global_mut<CoinInfo>(MODULE_OWNER).cap;\r\n        *cap = *cap - amount;\r\n\r\n        let supply = &mut borrow_global_mut<CoinInfo>(MODULE_OWNER).supply;\r\n        *supply = *supply - amount;\r\n    }\r\n}\r\n```\r\n\r\n5、合约编译部署\r\n由于aptos链不断在更新迭代，所以请务必注意你的cli版本和devnet版本。不然你肯定是部署不成功的。遇到类似下面这种API error时，请先更新你的cli脚手架。\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/erc20_aptos$ aptos move publish --named-addresses  Erc20Token=032621a894a17d900a7f8cfd7581380f02afdedb9818a07138fde2bb1a420516\r\n{\r\n  \"Error\": \"API error: error decoding response body: missing field `state_root_hash`\"\r\n}\r\n```\r\n更新完cli后，编译部署成功。\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/erc20_aptos$ aptos move compile --package-dir .\r\n{\r\n  \"Result\": [\r\n    \"032621a894a17d900a7f8cfd7581380f02afdedb9818a07138fde2bb1a420516::Erc20\"\r\n  ]\r\n}\r\nouhuang@LAPTOP-HM465BTT:~\/erc20_aptos$ aptos move publish --package-dir \/home\/ouhuang\/erc20_aptos\/\r\npackage size 2600 bytes\r\n{\r\n  \"Result\": {\r\n    \"transaction_hash\": \"0x4ebef2c4857f75bc9ecae21e2bc9383bfaeb68cc1b0e9b924b2fc1b9a0356344\",\r\n    \"gas_used\": 852,\r\n    \"gas_unit_price\": 1,\r\n    \"sender\": \"032621a894a17d900a7f8cfd7581380f02afdedb9818a07138fde2bb1a420516\",\r\n    \"sequence_number\": 9,\r\n    \"success\": true,\r\n    \"timestamp_us\": 1661787424248832,\r\n    \"version\": 21297869,\r\n    \"vm_status\": \"Executed successfully\"\r\n  }\r\n}\r\n```\r\n6、合约调用\r\naptos合约调用和solidity调用类似，要么是在区块链浏览器中调用，或者是使用前端sdk调用。\r\n\r\n>https:\/\/explorer.devnet.aptos.dev\/account\/0xe13c36e921448a601f2de9dc5341525ca6619a44e1444f302fba37fb39c5cf93\r\n\r\n\r\n## 五、合约解析\r\nSolidity是代码和数据放一起，因此在Solidity中想表示地址和余额的关系可以用`mapping`。\r\n```\r\n\/\/定义地址==》余额 映射关系\r\nmapping (address => uint256) private _rOwned;\r\nmapping (address => uint256) private _tOwned;\r\nmapping (address => bool) private _isExcluded;\r\n\r\n\/\/查询余额\r\nreturn _rOwned[address]\r\n```\r\n\r\n而Move是代码和数据分离。每个账户地址下拥有的是资源类型。要想表达地址和余额的关系。需要先根据地址找到资源类型，然后再从类型找到余额。\r\n\r\n```\r\n\/\/ 余额\r\nstruct Coin has store {\r\n    value : u128,\r\n}\r\n\/\/定义资源类型\r\nstruct CoinStore has key {\r\n    coin : Coin,\r\n}\r\n\r\n\/\/查询余额：地址Owner==》资源类型CoinStore==>余额 地址.资源类型.coin.value\r\nborrow_global<CoinStore>(owner).coin.value\r\n```\r\n\r\n\r\n- signer\r\n\r\n引入signer类型的原因之一是要明确显示哪些函数需要发送者权限，哪些不需要。因此，函数不能欺骗用户未经授权访问其 Resource\r\n\r\n\r\n- Acquires 关键字\r\n\r\n该关键字放在函数返回值之后，用来显式定义此函数获取的所有 Resource。我们必须指定所有获取的 Resource。\r\n\r\n## 七、小结\r\n按照上述步骤，我们就能部署属于自己的ERC20合约。在接下来的文章里，我们将一起构建Dapp来实现Claim Token等常用功能。"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/6721","address":null},"history":"QmPraAfLbwHDBqRg3x5oyTmYTtbt64YcDJh7XB2mCCcnNn","timestamp":1663232454,"version":1}