timezone |
---|
Asia/Shanghai |
- 一名程序猿 + 一名自洽的产品。技术栈:web3:solidity相关。web2:vue3、fastapi。
- 可以完成。
1、了解了aptos链的历史 2、对比了共识机制 3、下载IDE,安装环境
1、ERC20存储在合约中,是一个总账形式。mapping(address => uint256)
2、Aptos的token balance是分账,存储在各自用户的账户中。
3、这么做的原因是因为Aptos中将token视为资源,利用区块交易的确定性,将所有权和资源绑定。
4、当move的合约需要token的总数,这个话题需要之后回答,我觉得可能的形式是单独维护一个总账,或进行链上计算所有的分账?
1、EVM上转账不需要接收人确认。
2、EVM这种机制的问题是可能恶意转账:多次小额转、转没用的资产、转违法收入比如洗钱。
3、Move转账需要接收人确认。
起步,明天开始吧,今天把layer2和aptos的所有区别捋了一遍,从理论上解决了非以太坊公链和以太坊二链的区别。包括生态、技术、共识、交易对手、品牌等。
安装aptos,开始move编程。
进入指定的目录
aptos init
在项目目录下
aptos move init
结果:
创建了move.toml等文件
写代码
在项目目录下
aptos move compile
aptos move build
主要用于编译 Move 源代码,并生成字节码文件(.mv
文件)。
只关注 Move 代码的语法和逻辑正确性,不会将模块或脚本发布到链上。
开发阶段使用:常用于开发过程中测试代码是否能通过编译,以及生成字节码供其他操作使用。
输出的是编译后的 Move 模块(例如 .mv
文件),这些文件不会包含包管理相关的信息。
语法格式:aptos move compile --package-dir <package_path>
这是一个更高层次的命令,它除了编译 Move 代码外,还会对整个 Move 包进行构建。
它会处理 Move 包的依赖关系、生成构建工件(artifact),并打包输出编译好的模块或脚本。
在构建过程中,它会生成一些额外的元数据和包管理信息,方便后续的模块发布或部署。
生产阶段使用:适用于打包、准备发布 Move 项目,构建结果可以直接用于链上发布。
语法格式:aptos move build --package-dir <package_path>
生成特定前缀的账户(部署人用于部署合约的账户,因为aptos里没有contract address的概念)
--vanity-prefix参数是自定义的账户名的前缀,在这里值是--vanity-prefix
--output-file是自定义的生成的保存账户地址的文件,在这里值是ace.key
aptos key generate --vanity-prefix 0xace --output-file ace.key
结果
{
"Result": {
"Account Address:": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"PublicKey Path": "ace.key.pub",
"PrivateKey Path": "ace.key"
}
}
ace_addr=0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46
aptos account fund-with-faucet --account $ace_addr
结果
{
"Result": "Added 100000000 Octas to account acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46"
}
1、使用aptos move publish部署
2、参数--named-addresses:部署人地址
3、--private-key-file:私钥
4、--assume:不清楚
aptos move publish \
--named-addresses test_account=$ace_addr \
--private-key-file ace.key \
--assume-yes
结果
{
"Result": {
"transaction_hash": "0x1d7b074dd95724c5459a1c30fe4cb3875e7b0478cc90c87c8e3f21381625bec1",
"gas_used": 1294,
"gas_unit_price": 100,
"sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"sequence_number": 0,
"success": true,
"timestamp_us": 1685077849297587,
"version": 528422121,
"vm_status": "Executed successfully"
}
}
1、是move语言中的数据结构!
2、唯一所有性:只能被一个账户所拥有。不可复制,所以没有双花。
3、资源生命周期管理:只有合约才能CRUD资源。
4、资源包含token、认证秘钥、序列号等。
这是move语言特定的。
是资源所有的权层面,而不是类似于erc20这种同质化的经济层面。
所以只要是资源,无论他是什么,他都具备唯一所有权性。
1、智能合约的逻辑单位
2、其实就是各种函数、规则等代码。
3、可以CRUD资源。
4、提供接口对外操作。
5、有权限控制
6、可以复用。
地址的定义:32位字节标识符,用于定位区块链上账户或资源。
账户的定义:与某些地址相关联的完整的状态和资源的集合。
以太坊上,只要有一个私钥就能创建一个新的地址字符串(公钥),而且这个新的地址字符串在交易之前是无需“上链”这个操作的。链上交易可以直接和这个地址进行交易,此时该地址显性出现在链上,而在交易之前,此地址对于链上是隐性的。
aptos中,也是通过私钥创建地址,但是地址在交易前必须“上链”(aptos account create),这时候地址就是对于链上显性的,只有上链的地址才能进行交易。
aptos account开头
命令 | 说明 |
---|---|
create |
在链上创建一个新账户 |
create-resource-account |
在链上创建一个资源账户 |
derive-resource-account-address |
推导资源账户的地址 |
fund-with-faucet |
使用水龙头为账户提供代币 |
balance |
显示账户持有的不同代币的余额,默认是默认账户 |
list |
列出一个地址所拥有的资源、模块或余额 |
lookup-address |
通过链上查找表查找账户地址 |
rotate-key |
旋转账户的认证密钥 |
transfer |
在账户之间转移 APT 代币 |
help |
打印此消息或指定子命令的帮助信息 |
数据结构struct type,和操作数据结构的函数function。
模块只能发布published
定义了全局存储的策略。
定义了更新存储的规则(其实就是操作数据结构)
地址层面的树状结构存储
每个地址存储资源数据值resource data values和模块代码值module code values。
每种资源只能保存唯一一份,不能重复。
操作这个资源的模块可以多个,但是不能重名。
类似于别的语言的main文件,是入口文件。
通常是调用已经发布(published或理解为部署deployed)的模块
对全局存储进行了更新
脚本只能执行execute。
注意这不是像ethers.js一样在链下调用,脚本是在链上执行的,像是一种链上调用,每次执行脚本只运行一次。
使用场景:无需部署全部合约,去测试合约中部分功能。
script {
<use>*
<constants>*
fun <identifier><[type parameters: constraint]*>([identifier: type]*) <function_body>
}
1、声明:以use开头
2、声明常量:
3、声明函数:自定义函数名称,每个脚本只能有一个函数。可以有参数,不能有返回值。
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)
}
}
1、不能直接读写全局存储,而是通过调用已经发布的module的function来间接实现操作全局存储的。
2、不能定义新的好友(模块之间的关系)friends、数据结构类型struct type。
1、智能合约的逻辑单位
2、其实就是各种函数、规则等代码。
3、可以CRUD资源resources。
4、提供接口对外操作。
5、有权限控制
6、可以复用。
7、总结的说:定义数据结构struct type,和操作数据结构的函数function。
8、模块只能发布published
9、绑定一个地址
module <address>::<identifier> {
(<use> | <friend> | <type> | <function> | <constant>)*
}
1、整数都是无符号整数unsigned integer,其实就是非负整数。
2、u开头,8,16,32,一直到256.
3、如果不注明类型,那么就从上下文推断类型,如果推断不出来就默认u64。
Type | Value Range | 十进制范围 |
---|---|---|
u8 |
0 to 2^8- 1 | 255 |
u16 |
0 to 2^16 - 1 | 65535 |
u32 |
0 to 2^32 - 1 | 42亿 |
u64 |
0 to 2^64 - 1 | / |
u128 |
0 to 2^128 - 1 | / |
u256 |
0 to 2^256 - 1 | / |
1、函数名中函数类型
let a_u16 = 299;
语法:整数表达式 as 整数类型
x as u8;
25u64 as u16;
4/2 +12345 as y256;
1、数字序列(推荐)
let a: u64 = 2;
2、数字后带类型(声明变量时候不注明类型)
let b = 1u256;
3、数字的值除了是十进制的,也可以是十六进制的
let c: u8 = 0x1;
let hex_u256: u256 = 0x1123_456A_BCDE_F;
let simple_u64: u64 = 1_234_5678;
前提:类型必须一致,比如都是u256
+ - * / %
运算会自动换成二进制后进行位运算
语法 | 解释 | 详述 |
---|---|---|
& |
与 | 对每个位进行布尔与运算 |
` | ` | 或 |
^ |
异或 | 对每个位逐对执行布尔异或运算 |
module BitwiseExample {
public fun bitwise_operations() {
let a: u8 = 0b1100; // 二进制表示的 12
let b: u8 = 0b1010; // 二进制表示的 10
let and_result = a & b; // 0b1000 = 8
let or_result = a | b; // 0b1110 = 14
let xor_result = a ^ b; // 0b0110 = 6
assert(and_result == 8, 1);
assert(or_result == 14, 2);
assert(xor_result == 6, 3);
}
}
左边是变量,右边是移的位数
语法 | 解释 | 限制条件 |
---|---|---|
<< |
左移 | 要移位的位数大于整数类型的大小,否则终止 |
>> |
右移 | 要移位的位数大于整数类型的大小,否则终止 |
module ShiftExample {
public fun shift_operations() {
let a: u8 = 0b0001_0000; // 二进制表示的 16
let left_shift_result = a << 2; // 左移 2 位:0b0100_0000 = 64
let right_shift_result = a >> 2; // 右移 2 位:0b0000_0100 = 4
assert(left_shift_result == 64, 1);
assert(right_shift_result == 4, 2);
}
}
> < <= >= == !=
true false
值为布尔值的表达式
用于if、while、assert这三个场景。
ports three logical operations:
语法 | 描述 | 相等操作 |
---|---|---|
&& |
布尔与 | if (p) q else false |
` | ` | |
! |
布尔逆 | if (p) false else true |
1、是全局存储global storage中用来表达账户所有权的类型。
2、格式:256-bit,也叫做256比特,256位。同时也是32byte,也叫做32字节。地址的值是16进制,因此是64个十六进制字符。因为比特是二进制的,而字节是八进制的。
分为数值地址numerical address和命名地址named address
其实就是地址以字面量形式来展示,比如
0xCAFE
1、用变量名来代表一个地址
2、真实的赋值过程来自于Move.toml这个配置文件的addresses字段或者dev-addresses字段。注意这个赋值不来自于代码中。
配置文件Move.toml进行赋值
[addresses]
MyAddress = "0x78ab28452bb4ea270fdaaf0459c9cca388011f9982496c441d3cad65f131e731"
代码中使用
module MyModule {
public fun use_named_address() {
let named_addr = @MyAddress; // 使用命名地址
}
}
1、在非表达式的地址上,直接使用地址
2、无论地址是数值地址还是命名地址
3、非表达式的地址场景包括:模块声明、资源声明、引用命名地址,即use后、module后和resource后。
// 模块声明
address 0xCAFE { // 这里的地址是模块所属
module MyModule {
}
}
// 模块声明
module OtherModule {
use MyAddress::MyModule; // 这里 MyAddress 是命名地址
}
// 资源声明(这个代码可能错误)
resource MyResource { ... }
表达式中使用地址,前面加@
这和solidity不同,solidity直接使用字面量地址或者变量地址,无论在哪都不加符号区别。
move中无论地址是数值地址还是命名地址,只要在表达式中都加@
表达式的地址场景包括:地址赋值给变量、地址作为参数调用函数、地址作为判断条件、地址参与其它表达式计算
// 地址赋值
module MyModule {
public fun use_address() {
let addr = @0xCAFE; // 这是一个数值地址表达式,@0xCAFE
let named_addr = @MyAddress; // 这是一个命名地址表达式,@MyAddress
}
}
my_function(@0x123);
if (@0x789 == some_var) {...}
中秋节快乐
1、创建一个指定的新地址
生成特定前缀的账户(部署人用于部署合约的账户,因为aptos里没有contract address的概念)
--vanity-prefix参数是自定义的账户名的前缀,在这里值是--vanity-prefix
--output-file是自定义的生成的保存账户地址的文件,在这里值是ace.key
aptos key generate --vanity-prefix 0xace --output-file ace.key
结果
{
"Result": {
"Account Address:": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46",
"PublicKey Path": "ace.key.pub",
"PrivateKey Path": "ace.key"
}
}
命令:
aptos account create --account 地址
{
"Result": {
"transaction_hash": "0x4321d286cfc12ee405dfa09ed50375403bf1d180c2d564af3ae0fcbdaf0d9e90",
"gas_used": 1069,
"gas_unit_price": 100,
"sender": "2f49d65a6520b3d13e989fc028d8395eadb9281d8053c1ed6007f3a47b4d4189",
"sequence_number": 1,
"success": true,
"timestamp_us": 1726214222997777,
"version": 64214470,
"vm_status": "Executed successfully"
}
}
vector的语法深入学习
对比了signer和msg.sender的不同点,回忆OpenZepplin的owner相关内容和move的owner。
继续move的学习,应该只能完成move语法,不能进入aptos的完整学习。
听课,了解NFT,aptos DA的代码结构。
代码理解上遇到问题,rust和solidity语法区别还是很大的,而且aptos本身机制也比evm封装更好。 重学了vector的crud:
类似于其他语言的列表或数组
操作代码 | 描述 | 是否会中止操作 |
---|---|---|
let v = vector::empty(); | 创建一个可以存储 u64 类型值的空向量。 |
永不中止 |
let v = vector::singleton(5); | 创建一个包含单个元素 5 的向量。 | 永不中止 |
vector::destroy_empty(v); | 删除向量 v ,仅当其为空时成功。 |
如果向量不为空会中止 |
let len = vector::length(&v); | 返回向量 v 的长度。 |
永不中止 |
let is_empty = vector::is_empty(&v); | 判断向量 v 是否为空。 |
永不中止 |
语法:vector[ ]: vector
script {
fun example() {
(vector[]: vector<bool>); // 布尔空向量
(vector[0u8, 1u8, 2u8]: vector<u8>); // 8位整数3元素向量
(vector<u128>[]: vector<u128>); //128位整数空向量
(vector<address>[@0x42, @0x100]: vector<address>); // 地址2元素向量,注意使用@
}
}
操作代码 | 描述 | 是否会中止操作 |
---|---|---|
vector::push_back(&mut v, 5); | 在向量 v 的末尾添加元素 5。 |
永不中止 |
vector::insert(&mut v, 0, 5); | 在索引 0 处插入元素 5,后续元素右移。 | 如果索引超出范围会中止 |
操作代码 | 描述 | 是否会中止操作 |
---|---|---|
let last_element = vector::pop_back(&mut v); | 删除并返回向量 v 中的最后一个元素。 |
如果向量为空会中止 |
let removed_element = vector::remove(&mut v, 0); | 删除索引 0 处的元素,后续元素左移,保持顺序。 | 如果索引超出范围会中止 |
let removed_element = vector::swap_remove(&mut v, 0); | 交换索引 0 处元素和最后一个元素,然后删除最后一个元素。 | 如果索引超出范围会中止 |
操作代码 | 描述 | 是否会中止操作 |
---|---|---|
let first_element = vector::borrow(&v, 0); | 获取索引 0 处元素的不可变引用。 | 如果索引超出范围会中止 |
let mut_ref = vector::borrow_mut(&mut v, 0); | 获取索引 0 处元素的可变引用。 | 如果索引超出范围会中止 |
let contains = vector::contains(&v, &5); | 判断向量 v 是否包含元素 5。 |
永不中止 |
let (found, index) = vector::index_of(&v, &5); | 返回元素 5 的索引,如果不存在,返回 (false, 0)。 | 永不中止 |
索引语法 | 对应的向量操作 |
---|---|
&v[i] |
vector::borrow(&v, i) |
&mut v[i] |
vector::borrow_mut(&mut v, i) |
v[i] |
*vector::borrow(&v, i) |
v[i] = x |
*vector::borrow_mut(&mut v, i) = x |
&v[i].field |
&vector::borrow(&v, i).field |
&mut v[i].field |
&mut vector::borrow_mut(&mut v, i).field |
v[i].field |
vector::borrow(&v, i).field |
v[i].field = x |
vector::borrow_mut(&mut v, i).field = x |
操作代码 | 描述 | 是否会中止操作 |
---|---|---|
vector::swap(&mut v, 0, 1); | 交换向量 v 中索引 0 和索引 1 处的元素。 |
如果索引超出范围会中止 |
vector::reverse(&mut v); | 反转向量 v 中所有元素的顺序。 |
永不中止 |
vector::reverse_slice(&mut v, 0, 2); | 反转向量 v 中从索引 0 到索引 2 的元素。 |
如果索引超出范围会中止 |
继续move和aptos学习。
function和struct语法