# Solidity 基础

Solidity 是一门类似 JavaScript 的编程语言，用于编写智能合约，与其他智能合约语言相比，它使用最为广泛，相关编程的技巧和误区的研究也较成熟，是入门智能合约编程的首选。

## 一个简单的合约

```javascript
pragma solidity >=0.5.9 <0.6.0;

// The Store Contract
contract Store {
    uint256 internal value;

    function setValue(uint256 v) external {
        value = v;
    }

    function getValue() external view returns (uint256) {
        return value;
    }
}
```

如上代码，便是一个最简单的 Solidity 合约。

L1: `pragma solidity >0.5.9` 表示当前合约应该使用的编译器版本，因为 Solidity 每个版本都有所更新，尤其是有一些语法不兼容的更新。所以需要在合约的开头通过 `pragma` 限制版本号。版本号限制语法同 npm 。

L3: 注释，注释语法同 JavaScript 。

L4-L14: 合约的主体，所有合约的储存变量接口函数都在此定义。

L5: 这里定义了一个 `uint256` 类型的合约状态变量 `value`, `internal` 表示它是内部的，外部无法直接通过 `value` 这个名字访问。

L7-L9: 定义了 `setValue(uint256)` 方法，`external` 表示它只能被外部调用，即通过区块链相关 API 调用合约的该方法。在方法实现中，状态变量 `value`, 被设置为函数的入参。

L11-L13: 定义了 `getValue()` 方法, `view` 表示它不可以更改合约状态变量, `returns` 表示其返回类型，合约调用的发起者可以获得该返回值。

由此，我们得到一个合约的标准结构，首先是 `pragma` 表示编译器版本，其次是定义了合约方法，合约状态变量的合约主体结构。部分较复杂的合约还会在 `pragma` 之后有 `import` 语句引入其他库函数。

## Solidity 语言基础

### 基础类型

从上一节的合约例子，我们已经见到了最基础了类型 `uint256` . Solidity 还支持如下基础类型：

* `bool` : 即 `true` 和 `false`, Big endian 扩充 256 位。
* 整数类型: `int` 和 `uint` , 支持后缀数字表示位长，最小从 `uint8` 到最长 `uint256`. Big endian.
* `address` : 地址类型，表示合约或普通账户地址，为 20 字节。内部处理时高位补齐到 32 字节。同时在 TVM 优化中，也处理了 21 字节地址，即支持前缀 0x41
* 定长字节: `bytes1`, `bytes2`, `bytes3`, …, `bytes32`. `byte` 是 `bytes1 的别名`，高位补齐到 32 字节。

### 字面常量 - literals

地址，数值。 Solidity 没有八进制数值常量。数值常量可以用科学计数法表示，例如 `2.5e1` 。

字符串字面常量。支持单引号或双引号。支持常见转义，支持 `\uNNNN` 转义。

十六进制字面常量。`hex"00112233cafe"`。

### 枚举类型 - enum

枚举类型可以通过显式类型转换语法和整数类型互相转换。不支持隐式类型转换。例如 `uint(val)`.

```c
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
```

由于 ABI 不支持自定义类型，所以出现在 ABI 中的枚举，会自动转换为合适的 `uintN` 类型。例如 `function getChoice() public view returns (ActionChoices)` 会变为 `getChoice() returns (uint8)` .

### 引用类型 - reference tyes

以上的可以放入一个 `uint256` 的类型都叫值类型 value type. 与此对应的，是引用类型。

引用类型一般被放入 `storage` 或 `memory` 。默认情况下函数参数和返回值中的引用类型是 `memory` ，合约状态变量是 `storage` 。(data location)

另外有种特殊的 data location 叫 `calldata` 隶属于外部函数调用时候传递的参数，类似 `memory` 但是不可修改。

#### 变长类型 - dynamic size type

`bytes` 和 `string` 这样的长度动态指定的类型，被叫做 dynamic size type. 虚拟机在操作时代价高于定长类型，应避免使用。两者不同的地方在于，虽然底层表示都是字节，但 `string` 不允许索引操作(utf8 string)。

#### 数组类型 - array

数组可以是编译时确定大小，或动态大小。支持高维数组。例如 `uint[][5]` 。

`bytes` 类型类似 `byte[]` 但代价更小，需要尽量避免使用 `byte[]` 。

创建 array 使用 `uint[] memory a = new uint`。

支持 .length (rw) .push() 操作。

#### 结构体 - struct

```c
struct Funder {
    address addr;
    uint amount;
}

x = MyStruct({a: 1, b: 2});
```

java-tron ABI 无法支持结构体的描述，所以出现在 ABI 中的结构体统一表述为 `tuple` 结构。实际合约中应避免在 ABI 中出现结构体。

参考: [StackOverflow](https://stackoverflow.com/questions/61107744/always-returning-empty-array-of-arrays-although-its-showing-successful-transac)

#### 映射 - mapping

即 k-v 哈希字典。mapping(\_KeyType => \_ValueType) .

key 可以是除 mapping 动态数组，枚举，结构体之外的类型。value 可以是任意类型。

需要注意的是，和其他语言不同， Solidity 中的映射没有 size 的概念，一旦初始化，所有 key 对应的 value 都有默认值(全0值)。所以 mapping 是不可迭代结构。这是由于：

mapping 的内部实现是通过对 key 进行 hash 运算得到一个储存位置，返回这个储存位置对应的值。而对于合约储存而言，储存位置默认都是 0.

### 类型转换

有了类型，就有类型间的转换。值域属于子集的类型，可以隐式类型转换，否则需要显式类型转换。例如：

```
int8 a = -3;
uint x = uint(a);
```

此时， x 将等于 -3 的二进制补码表示。

### Storage

合约的 Storage 生命周期同合约本身。同 state variable.

private public external internal

自动为 public storage 变量创建 getter.

### 表达式和控制结构

程序的功能和逻辑，离不开基础表达式和控制结构。

Solidity 的表达式基本同 C/JavaScript 系语言，相关参考见: [运算符优先级](https://solidity.readthedocs.io/en/v0.5.3/miscellaneous.html?highlight=view#order-of-precedence-of-operators)

Solidity 的基本控制结构同样来自 C/JavaScript 系语言，即: `if`, `else`, `while`, `do`, `for`, `break`, `continue`, `return`.

此外，对于外部调用和合约创建调用， Solidity 还支持 `try/catch` 语句。(TRON 目前对合约创建合约的支持较弱)

{% hint style="info" %}
TRON 目前(4.0.1)只支持合约创建合约的 `CREATE` 调用， `CREATE2` 调用并没有实现。
{% endhint %}

### 函数

合约的主要逻辑在函数中定义。函数定义的语法如下：

```
function [name](<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)] {
    function_body
}
```

如果函数作为类型出现，那么只需要 `function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]` 。

这里的 internal/external 是访问控制关键字，四种访问控制关键字如下：

* `private`: only from this contract
* `internal`: this contract and derived contract
* `external`: can not be accessed internally
* `public`: may be called internally

实际使用中，应选择最小范围的访问控制，例如使用 `external` 而非 `public`.

* `constant`: 不需要网络确认的操作，例如查询 storage, 新版 Solidity 已经废弃了该关键字
* `view`: 用于替换原有的 `constant` 关键字，表示函数不会修改 storage
* `pure`: 比 `view` 更严格，不允许查询 storage

而 `payable` 是单独的概念，用于表示该合约函数可以处理代币转账。其中无函数名的特殊 `payable` 函数叫 fallback function:

```javascript
function() public payable {}
```

#### 函数相关操作

```javascript
this.f.selector: byte4

// internal form
f()

// external form
this.f()
```

#### 函数修饰符 - function modifier

函数修饰符用于改变函数的行为，例如自动权限检查。

```javascript
modifier onlyOwner {
    require(
        msg.sender == owner,
        "Only owner can call this function."
    );
    _;
}

function close() public onlyOwner {
    selfdestruct(owner);
}
```

### 事件 - Event

Event 通过 EVM/TVM 的 logging 机制，提供了高效的事件记录机制。

```javascript
contract SimpleAuction {
    // 定义事件
    event HighestBidIncreased(address bidder, uint amount);

    function bid() public payable {
        // 触发事件
        emit HighestBidIncreased(msg.sender, msg.value);
    }
}
```

### 合约的构造函数和继承

#### 继承

Solidity 支持合约的多重继承机制。父合约可以定义虚函数，由子合约实现，虚合约，抽象合约。

多重继承不会导致创建多个合约，最终只有一个合约被创建，相关父合约的代码会被 copy 到最终合约。

```javascript
contract owned {
    constructor() { owner = msg.sender; }
    address owner;
}

contract mortal is owned {
    function kill() {
        if (msg.sender == owner) selfdestruct(owner);
    }
}

/// ...
contract named is owned, mortal {
    constructor(bytes32 name) {
        Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
        NameReg(config.lookup(1)).register(name);
    }
}

// 继承并提供构造函数
contract PriceFeed is owned, mortal, named("GoldFeed") { ... }
```

#### 构造函数

默认构造函数 `contructor() public {}`.

构造函数可以被标记为 `public` 或 `internal`. `internal` 相当于抽象合约(类)。

父类构造函数也可以通过如下方式传递:

```javascript
contract Derived2 is Base {
    constructor(uint _y) Base(_y * _y) public {}
}
```

### 接口 - interface

接口相当于抽象类。

* Cannot inherit other contracts or interfaces.
* Cannot define constructor.
* Cannot define variables.
* Cannot define structs.
* Cannot define enums.

### 库 - library

库的存在，避免了自己实现轮子的麻烦。保证了多个合约共享逻辑的一致性。

需要区分库和父合约。库其实是一个单独的合约。代码独立与调用者存在，需要通过 `DELEGATECALL` 指令调用。

库和合约必须单独部署，且库不产生 ABI 文件。库先部署完毕后，得到一个合约地址， 将该合约地址转换为 20 字节地址，填入目标合约 `.bin` 文件的占位符后。再部署最终合约。

占位符格式在不同的版本的 Solidity 编译器下有所不同。

参考: [Libraries](https://solidity.readthedocs.io/en/v0.4.24/contracts.html#libraries).
