Solidity语法

形而上者谓之道;形而下者谓之器。(《周易·系辞上》)

疑问:读书时不太懂的。

一、编程基础

1 基本

// 单行注释
// 版本控制
pragma solidity >= ^0.4.0 < 0.8;
/** 多行注释 -- 类似ts **/
import * as aliceName from "filename"

2 库

  1. 能有:fallback函数paybale关键字、storage变量、日志;
  2. 库可以:修改和他们相联的合约的storage变量;分发事件

类比:函数传入C指针 – 库处理合约storage

web3库开发dapp监听eventemittercontract.emit 收到库函数触发事件,但是监听eventemitterLib.emit事件,什么也收不到。

3 合约文件结构

  1. 状态变量:state variables
  2. 结构定义:structure definitions
  3. 修饰符定义:modifier definitions
  4. 事件声明:event definitions
  5. 枚举定义:enumeration definitions
  6. 函数定义:function definitions

4. 变量类型

  1. 基础类型(int、uint、struct)、数组类型、映射
  2. 固定变量static、动态变量dynamic
4.1 值类型
  1. 布尔类型:true\false; 不能和整数隐式转换
  2. 整型:int\uint;默认256;包含uint8\16\24\32..;默认为十进制
  3. 地址:长度20B、160bit,可以用uint160编码
  4. 定长字节数组:?? 疑问;
  5. 有理数和整型字面量:literal 本身是 疑问;
  6. 枚举类型:enums 不能和整数隐式转换
  7. 函数:可以将函数赋值给变量;函数作为参数;返回函数;
4.2 引用类型

为了避免空间占用太大,采用引用类型:

  1. 不定长字节数组:bytes;
  2. 字符串:string; 没有\0结尾,”test”就是4个字节
  3. 数组:array: memory不可以更改数组长度,storage可以根据.lengthg更改
  4. 结构体:struct: 自定义数据结构
4.3 字典、映射

key的类型允许除【字典】以外的所有类型,数组、合约、枚举、结构体

mapping(keyType ->valueType)

4.4 特殊情况

变量放在上,只能容纳16层

5 操作符

用到再说吧

6 语句

不支持switch goto
单行语句中的大括号可以删除

if (condition) {

} else {

}
while (condition) {

}
break: 跳出循环
continue: 继续下次
return: 返回
?:::三元

7 修饰符

  1. 函数修饰符:external、internal<默认>、public<默认>、private
  2. 针对状态变量:constant、view、pure、paybale
  • internal
    1. internal 不能从外部访问,不是接口的一部分
    2. 只能内部调用、或继承的合约中调用;
    3. 不能加this, this代表以外部形式调用
  • external
    1. 外部函数是合约的一部分,可以从其他合约或通过交易发起调用
    2. this.f()
  • public
    1. 公开函数为合约接口的一部分
    2. public 的状态变量,会自动创建一个访问器
  • private
    1. 函数和变量仅仅在当前合约可以访问
    2. 继承中不能访问
    3. 是接口的一部分
  • constant
    1. 不能改变状态,只能获取状态返回
    2. 似乎没啥用
  • view
    1. 不能修改状态变量,等同 constant
  • pure
    1. 更多的限制;
    2. 不能读写状态量,函数不能读写当前的状态和交易变量
  • paybale
    1. 声明的函数可以从调用者那里接受ether;
    2. 一个函数如果声明了payable,说明只能接受ether;
    3. 如果调用者没有eth就报错

external & public 🚩🚩

  1. 可见度:都可以被其他合约通过调用、交易的方式调用
  2. 不同
    1. internal 消耗最少,用jump命令,参数是以内存指针传递
    2. public因为不知道调用者是 external | internal; public就会像处理internal一样将参数复制到memory,这样操作很贵
    3. 如果可以确信函数只能外部调用,则用external
    4. 大多数情况,this.f() 调用方式没有意义,他会调用call()

internal & external

pragma solidity >=0.8.0 <0.9.0;
contract TestFun {
function internalFunc() internal {}
function externalFunc() external {}
function callFunc() public {
// 直接使用内部的方式调用
internalFunc();
// ❌ 不能内部调用一个外部函数
// DeclarationError: Undeclared identifier. Did you mean "internalFunc" or "externalFunc"?
// externalFunc();
// ❌ 不能通过external的方式调用internal
// TypeError: Member "internalFunc" not found or not visible after argument-dependent lookup in contract TestFun.
// this.internalFunc();
this.externalFunc();
}
}

contract TestFun1 {
function externalCall(TestFun tf) public {
// 调用另一个函数的外部函数
tf.externalFunc();
// ❌ 不能调用另外一个合约的内部函数
// TypeError: Member "internalFunc" not found or not visible after argument-dependent lookup in contract TestFun.
// tf.internalFunc();
}
}

8 自定义修饰符

修饰符可以改变函数的行为,类似装饰器。

  1. 执行函数前检查条件
  2. 修饰符可以继承
  3. 可以被派生合约重载

contract owned {
constructor() public {
owner = msg.sender;
}

address payable owner;
// 合约定义了个 【修饰符】在派生合约里使用
// 函数体会加入到修饰符定义里的特殊符号 _ 之后
modifier onlyOwner() {
require(msg.sender == owner, 'Only owner can call this function');
_;
}
}

contract mortal is owned {
function close() public onlyOwner {
self.destruct(owner);
}
}

contract priced {
modifier consts(uint price) {
if(msg.value >= price) {
// 表示函数体
_;
}
}
}

contract Register is priced, owned {
mapping (address=>bool) registeredAddresses;

uint price;
constructor (uint initialPrice) public {
price = initialPrice;
}

function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}

contract Mutex {
bool locked;
modifier noReentrancy (){
require(!locked, 'Reentrant call');
locked = true;
_;
locked = false;
}

function f() public noReentrancy returns(uint) {
(bool, success,) = msg.sender.call('');
require(succeess);
return 7;
}
}

9 数据位置

evm提供四种数据结构来存储变量;

  • storage
    1. 链上永久存储;
    2. 可以被所有函数访问的全局变量
  • memory
    1. 合约本地内存变量
    2. 生命周期很短,函数执行结束就销毁
  • calldata
    1. 所有函数调用你的数据,包含函数参数的保存位置
    2. 不可修改的内存位置
  • stack
    1. EVM 维护了个1024级的栈,用来导入变量和以太坊的机器、汇编指令代码交互;

数据结构-总结

  1. memory:默认的函数参数、返回的参数
  2. storage: 默认的局部变量、默认的状态变量<合约声明的公有变量>
  3. 外部函数的参数<不包含返回参数> 被强制指定为calldata