solidity学习(一)
1、版本标识符 pragmas 标识合约适配的编译器版本
pragmas solidity >0.5.0;
2、ABI 合约的json描述,类比API
3、导入其他的源文件 类似JS ES6导入规则,不支持default export
import "./filename";
import * as bb from "./filename";
import './filename' as bb;
import {aa as _aa, bb} from './filename';
4、路径解析
路径建议使用相对引用
·import './filename';·
5、合约结构:状态变量、函数、修改器、事件、结构体、枚举、库、接口
- 状态变量是永久地存储在合约存储中的值
- 函数调用 可发生在合约内部或外部(可见性:provide受保护、internal内部调用、public、external外部调用)
6、类型
- “undefined”或“null”值的概念在Solidity中不存在
- 值类型:bool、int、uint、fixed(未完全支持)、address、contract、bytes1-32、bytes、string、字面常量(地址、有理数整数、字符串、十六进制)、enum、function类型(默认internal)
- 引用类型:struct、数组、map(使用引用类型必须指定存储位置:memory、storage、calldata)
- 所有的引用类型,如 数组 和 结构体 类型,都有一个额外注解 数据位置
- storage 和 memory 赋值
*在 存储storage 和 内存memory 之间两两赋值(或者从 调用数据calldata 赋值 ),都会创建一份独立的拷贝
*从 内存memory 到 内存memory 的赋值只创建引用
*从 存储storage 到本地存储变量的赋值也只分配一个引用。
*其他的向 存储storage 的赋值,总是进行拷贝 - 状态变量标记 public 的数组,Solidity创建一个 getter函数
- 访问超出数组长度的元素会导致异常(assert 类型异常 )
- 动态数组可以使用 .push() 方法在末尾追加一个新元素,其中 .push() 追加一个零初始化的元素并返回对它的引用
- 数组切片 x[start:end]
- mapping(_KeyType => _ValueType),_KeyType 可以是任何基本类型,映射、结构体、即除 bytes 和 string 之外的数组类型是不可以
- 映射只能是 存储storage 的数据位置
点击查看代码
pragma solidity >=0.6.0 <0.9.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// 注意下面的代码并不是一对动态数组,
// 而是一个数组元素为一对变量的动态数组(也就是数组元素为长度为 2 的定长数组的动态数组)。
// 因为 T[] 总是 T 的动态数组, 尽管 T 是数组
// 所有的状态变量的数据位置都是 storage
bool[2][] m_pairsOfFlags;
// newPairs 存储在 memory 中 (仅当它是公有的合约函数)
function setAllFlagPairs(bool[2][] memory newPairs) public {
// 向一个 storage 的数组赋值会对 ``newPairs`` 进行拷贝,并替代整个 ``m_pairsOfFlags`` 数组
m_pairsOfFlags = newPairs;
}
struct StructType {
uint[] contents;
uint moreInfo;
}
StructType s;
function f(uint[] memory c) public {
// 保存引用
StructType storage g = s;
// 同样改变了 ``s.moreInfo``.
g.moreInfo = 2;
// 进行了拷贝,因为 ``g.contents`` 不是本地变量,而是本地变量的成员
g.contents = c;
}
function setFlagPair(uint index, bool flagA, bool flagB) public {
// 访问不存在的索引将引发异常
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) public {
// 使用 push 和 pop 是更改数组长度的唯一方法
if (newSize < m_pairsOfFlags.length) {
while (m_pairsOfFlags.length > newSize)
m_pairsOfFlags.pop();
} else if (newSize > m_pairsOfFlags.length) {
while (m_pairsOfFlags.length < newSize)
m_pairsOfFlags.push();
}
}
function clear() public {
// 这些完全清除了数组
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// 效果相同(和上面)
m_pairsOfFlags.length = new bool[2][](0);
}
bytes m_byteData;
function byteArrays(bytes memory data) public {
// 字节数组(bytes)不一样,它们在没有填充的情况下存储。
// 可以被视为与 uint8 [] 相同
m_byteData = data;
for (uint i = 0; i < 7; i++)
m_byteData.push();
m_byteData[3] = 0x08;
delete m_byteData[2];
}
function addFlag(bool[2] memory flag) public returns (uint) {
m_pairsOfFlags.push(flag);
return m_pairsOfFlags.length;
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
// 使用`new`创建动态内存数组:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// 内联(Inline)数组始终是静态大小的,如果只使用字面常量,则必须至少提供一种类型。
arrayOfPairs[0] = [uint(1), 2];
// 创建一个动态字节数组:
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(uint8(i));
return b;
}
}
7、单位 1 ether = 10**9 gwei = 10**18 wei
- 时间单位:seconds、 minutes、 hours、 days 和 weeks
8、特殊变量和函数
- blockhash(uint blockNumber) returns (bytes32):指定区块的区块哈希——仅可用于最新的 256 个区块且不包括当前区块
- block.chainid (uint): 当前链 id
- block.coinbase ( address ): 挖出当前区块的矿工地址
- block.difficulty ( uint ): 当前区块难度
- block.gaslimit ( uint ): 当前区块 gas 限额
- block.number ( uint ): 当前区块号
- block.timestamp ( uint): 自 unix epoch 起始当前区块以秒计的时间戳
- gasleft() returns (uint256) :剩余的 gas
- msg.data ( bytes ): 完整的 calldata
- msg.sender ( address ): 消息发送者(当前调用)
- msg.sig ( bytes4 ): calldata 的前 4 字节(也就是函数标识符)
- msg.value ( uint ): 随消息发送的 wei 的数量
- tx.gasprice (uint): 交易的 gas 价格
- tx.origin (address payable): 交易发起者(完全的调用链)
- ABI 编码及解码函数
![]()
- 错误处理函数 require、assert、revert
- 数学密码学函数:addmod、mulmod、kaccak256
9、地址address
- 成员 balance、code、codehash、transfer、send、call、delegatecall、staticcall
- transfer、send 发送固定2300gas
- call、delegatecall调用关系
![]()
10、合约相关
- this (当前的合约类型),可以显示转换为 地址类型 Address。
- selfdestruct(address payable recipient)销毁合约,并把余额发送到指定 地址类型 Address。
11、类型信息 type(X)
- type(Contract) : name、creationCode、runtimeCode、
- type(Interface):interfaceId 接口ID
- type(Int):min、max
12 表达式和控制结构
- 没有switch、goto
- try/catch 仅用于外部函数调用和合约创建调用
- solidity的非布尔值不能转换成布尔值 if(1) 无效
13函数调用
- 内部函数调用 只能在同一个合约实例的函数,可以进行内部调用
*避免过多的递归调用,每个内部函数调用至少使用一个堆栈槽,最多有1024个 - 外部函数调用 this.g(); 、 C.g();
*函数将会通过一个消息调用来进行外部调用
*不可以在构造函数里使用this,这时候合约实例还未创建
*对于外部函数调用,所有的函数参数都需要被复制到内存
*从一个合约到另一个合约的函数调用不会创建自己的交易,它是整个交易的一部分
*使用extcodesize 检查调用合约是否存在
*feed.info{value: 10, gas: 800}() 设置调用函数时可以使用的gas和发送的wei - 具名调用和匿名调用参数
set({value: 2, key: 3});
set(2, 3);
function set(uint key, uint value) public { data[key] = value; } - 通过 new 创建合约
D newD = (new D){value:amount}(arg); - 通过汇编指令 create2 创建合约
*通过create2指令指定salt,可以计算出合约创建时的地址
/// 这个复杂的表达式只是告诉我们,如何预先计算地址。
/// 这里仅仅用来说明。
/// 实际上,你仅仅需要 ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(
type(D).creationCode,
arg
))
)))));
address pair;
assembly {
//通过create2方法布署合约,并且加盐,返回地址到pair变量
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
D d = new D{salt: salt}(arg);
require(address(d) == predictedAddress);
- 表达式计算顺序
*语句按顺序执行,布尔表达式的短路执行 - 赋值
*解构赋值 (uint x, bool b, uint y) = f();
14作用域和声明
- 变量声明后初始值为零状态
- 对于 enum 类型, 默认值是第一个成员
- 变量将会从它们被声明之后可见,直到一对 { } 块的结束
15用assert检查异常(Panic) 和 require 检查错误(Error)



浙公网安备 33010602011771号