* 开发工具
-
Truffle for VS Code 在vscode上安装插件
https://trufflesuite.com/docs/vscode-ext/quickstart/ -
ganache 本地私链,免费快速的以太坊模拟器
https://trufflesuite.com/ganache/ -
remix 官方在线开发平台
https://remix.ethereum.org/ -
infura 在线的以太坊节点和测试节点,省去本地安装和同步时间
https://infura.io/ -
sepolia 测试网络 免注册直接使用
RPC接口地址:https://rpc.sepolia.org
获取测试币:https://faucet.sepolia.dev/
交易查询:https://sepolia.otterscan.io/
* 在线文档
以太坊官网
https://ethereum.org/zh/solidity官方文档
https://docs.soliditylang.org/en/v0.8.17/solidity中文文档
https://learnblockchain.cn/docs/solidity/ 由官方文档直接翻译
https://www.w3cschool.cn/solidity/web3.js、ethers.js及其他文档
https://learnblockchain.cn/manuals
* 智能合约
位于以太坊区块链上特定地址的代码(其功能)和数据(其状态)的集合。
0.4.22版本之前的版本,构造函数是与合约名相同的函数,后来改为constructor
;在0.7.0版本后不需要internal
或public
修饰符;
一个合约最多有一个 receive
函数, 声明函数为:receive() external payable { ... }
,用于接受以太币;
一个合约最多有一个fallback 回退函数,在找不到指定的函数名时,就会调用fallback函数。
声明为: fallback () external [payable]
或 fallback (bytes calldata input) external [payable] returns (bytes memory output)
如果没有receive
函数时,那么fallback
函数会被执,但必须标记为 payable
才能接受以太币。
一个没有定义receive
函数或fallback
函数的合约,直接接收以太币(使用send
或 transfer
方法)会抛出一个异常, 并返还以太币。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
// 关键字“public”让这些变量可以从外部读取
address public minter;
mapping (address => uint) public balances;
// 轻客户端可以通过事件针对变化作出高效的反应
event Sent(address from, address to, uint amount);
// 这是构造函数,只有当合约创建时运行
constructor() {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
balances[receiver] += amount;
}
// Errors allow you to provide information about
// why an operation failed. They are returned
// to the caller of the function.
error InsufficientBalance(uint requested, uint available);
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender])
revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
* 合约可以显示地转换为address类型
address(this).balance
合约的构造函数constructor() public
,可以有参数但不能重载;
合约的继承用关键词 is
,可以继承多个父合约,super
关键词访问父合约方法;
可以定义接口interface
;
* 固定大小的字节数组
值类型
bytes1
,bytes2
, bytes3
, ...bytes32
包含从 1 到最多 32 的字节序列。
一旦被声明后,就不能修改它的长度的值,只能读取。
在确定字节大小时,使用固定大小的字节数组,gas成本更低。否则就使用bytes
或string
这两个引用类型
。
bytes3 numA=0x112233;
//大的字节数组向小的字节数组转换时,低位截断
return bytes2(numa); //0x1122
bytes2 numB=0x1122;
//小的字节数组向大的字节数组转换时,低位补0
return bytes3(numB); //0x112200
* 动态大小的字节数组
bytes public num= new bytes(3);
return num.length;
num.push(bytes1 b1);
num[4]=bytes1 b2;
固定大小字节数组(bytes1
-bytes32
)不能强制转换为动态大小的字节数组(bytes
)或字符串(string
),反之亦然。但动态大小的字节数组(bytes
)和字符串(string
)可以显式互相转换。
bytes memory b=new bytes(9) 内存动态字节数组不能调整大小
* 数组
固定长度数组:int8[5] arr = [1,2,3,4,5];
固定长度数组定义之后不能再修改长度,暨不能使用push和pop方法。(ps: 0.6版本以后数组的length为只读,之前是可以修改的)
动态长度数组:int8[] arr = [1,2,3,4,5];
uint[][5]
表示5个动态数组,与C或java定义的前后顺序相反,但访问时一致。
* mapping 类似java中的hashmap
mapping (KeyType => valueType) addrs;
KeyType 可以是基本值类型、字符串,但不能为数值、字节数组或自定义类型
注意mapping
目前不可迭代,即你不能枚举它们的键。如果需要迭代,则需要自己实现或引用第三方类库。
* struct 结构体
类似C语言中的结构类型,但它不能作为参数使用;只能在internal
修饰的函数中作为返回值。
* delete 运算符
其实不是删除变量,而是将变量重置为默认值。数值型重置为0,枚举型也是重置为0,字符串重置为“”空,布尔型重置为false
delete 固定大小的数组,是将数组的每个元素值重置;delete动态大小数组,将数组的长度置为0;
不能直接delete mapping
,但可以delete mapping
中的元素。
* 状态变量(成员变量)修饰符 默认为internal
public
状态变量,编译器会自动为它们生成 getter 函数,这允许其他合约读取它们的值。当在同一个合约中使用时,外部访问(例如this.x)调用 getter,而内部访问(例如x)直接从存储中获取变量值。如需要外部修改,还需要加上setter函数。修饰符的位置与java 不同的
string public name="tim";
uint8 internal id=1;
function setId(uint8 newId) public{
id=newId;
}
private
私有修饰符,只能被当前合约访问;而internal
变量可以被子合约访问,类似java中的protected;
constant
修饰符表示为常量,不能在别处修改值。
* 函数修饰符 默认为public
external
外部函数是合约接口的一部分,这意味着它们可以从其他合约和交易中调用。f不能在内部调用外部函数(即f()不工作,但this.f()工作)。
public
公共函数是合约接口的一部分,可以在内部调用,也可以通过消息调用。
internal
内部函数只能从当前合约或从它派生的合约中访问。它们不能被外部访问。由于它们没有通过合约的 ABI 暴露给外部,它们可以采用内部类型的参数,如映射或存储引用。
private
私有函数类似于内部函数,但它们在派生合约中不可见。
view
类似变量中的constant
,表示函数中不会修改状态变量的值。
pure
表示在函数中,既不允许修改状态变量值,也不允许读取状态变量值。
*数据位置(变量存储)
以太坊虚拟机有 3 个区域用来存储数据: 存储(storage), 内存(memory) 和 调用数据(calldata)
storage
:存储在区块链上持久化的mapping,读写成本最高,所以尽量减少使用,状态变量必须为storage类型。
memory
:存储在内存中变量,每次函数调用结束后释放,一般定义在函数内部,可以用storage注解来改写。
calldata
: 读的函数参数存储区,主要用于函数的参数,使用成本最低。
0.5.0版本之后,所有复杂类型现在必须声明明确的数据位置
。
引用类型相同数据位置之间的赋值是传引用,而且不同数据位置的赋值是都会创建一份独立的拷贝(给状态变量的赋值总是拷贝,可能需要持久化的原因吧)。例如:
在 存储storage 和 内存memory 之间两两赋值,会创建一份独立的拷贝。
从 内存memory 到 内存memory 的赋值只创建引用, 这意味着更改内存变量,其他引用相同数据的所有其他内存变量的值也会跟着改变。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract Tiny {
uint[] x; // x 的数据存储位置是 storage, 位置可以忽略
// memoryArray 的数据存储位置是 memory
function f(uint[] memory memoryArray) public {
x = memoryArray; // 将整个数组拷贝到 storage 中,可行
uint[] storage y = x; // 分配一个指针(其中 y 的数据存储位置是 storage),可行
y[7]; // 返回第 8 个元素,可行
y.pop(); // 通过 y 修改 x,可行
delete x; // 清除数组,同时修改 y,可行
// 下面的就不可行了;需要在 storage 中创建新的未命名的临时数组,
// 但 storage 是“静态”分配的:
// y = memoryArray;
// 下面这一行也不可行,因为这会“重置”指针,
// 但并没有可以让它指向的合适的存储位置。
// delete y;
g(x); // 调用 g 函数,同时移交对 x 的引用
h(x); // 调用 h 函数,同时在 memory 中创建一个独立的临时拷贝
}
function g(uint[] storage ) internal pure {}
function h(uint[] memory) public pure {}
}
每次存储读取256位,即32字节的数据;所以变量定义的时候顺序可能会影响gas成本。
uint128 a;
uint256 b;
uint128 c;
改为以下顺序来声明会更好:
uint256 b;
uint128 a;
uint128 c;
*区块时间
通过block.timestamp
获取,now
在0.7版本中被删除。