# 初探Solidity

By [MarkTang's Blog](https://paragraph.com/@marktang-s-blog) · 2022-08-26

---

初探Solidity
==========

基础概念
----

### 没有浮点数运算

为了弥补这个缺陷，如何表示带小数的数字呢？以太坊将自身的数切分为最小18位，我们称为wei，任何数字都是wei的整数倍

44A06DF3-DABB-4BD0-86EE-86A0726C014C

### 变量类型

智能合约的变量分为两种，存在区块链上的和不存在区块链上的。存在区块链上的我们称为状态变量（state variable）。这类变量将永久记录在区块链上，写入读取它们就仿佛操作一个数据库一样，修改和赋值都会造成巨额的开销。不存在于区块链上的变量则是程序中的内存变量（memory variable），程序运行完毕就从内存中释放，相对开销较小

#### internal/external修饰符

*   internal修饰符可以让合约继承后子合约访问该函数
    
*   external 修饰符让该函数只能被外部调用者调用
    

**Solidity的函数也有修饰符，称为modifiers，这标明了函数可能对区块链状态有无修改/读取的标记。一般都会标记该值让编译器帮我们执行代码的优化**

672641FC-3F0D-45C1-921E-05293A6D99FD

#### 内置函数

*   keccak256 散列函数算法，可以根据任意长度的明文产生固定长度256位的哈希值
    

#### 数据结构：Map

映射数据结构：mapping

> 例如我们可以存储账号地址以及它对应的合约内的token数量的关系(假设是一个代币合约)

1CC08B0E-986A-4DDA-9732-C7A7DAB23454

#### 环境变量：msg.sender

> 当前合约调用者的地址

FD0D5090-B4DB-40B3-AE34-36C17DB7A106

#### require / assert

> 在合约中进行权限检查或者条件满足检查

*   require 条件检查语句如果不通过，则扣除运行到当前语句时，程序执行所话费的gas，终止程序执行，并返回
    
*   assert 条件检查语句如果不通过，则视为严重错误，扣除所有的gas，终止程序执行，并返回
    

6CFC6B7B-5EAF-4AE4-8D89-4514B8C65B9E

#### 继承和引入

> 智能合约的代码可以来源于自身项目内，也可以来源于外部早已部署完毕的链上合约

CDE78801-35DE-4599-BBA1-7F536A4F5C6A

C77EB3A9-9CD3-463A-9A1A-787056410F6F

#### 内存变量

> 不同的存储类型，花费的gas数额不同。它们的最终存储地方也不同。有时候为了省钱，我们会把临时变量留在内存里，而不是保存在区块链上。随着程序执行，内存里的变量会消亡，而区块链上的会永存。由于没有改变区块链状态，内存变量(memory)的花费会比状态变量(storage)的花费少很多

9047DF90-02CA-48D2-B48D-CE2B597A5BD9

#### 接口与合约调用

> 合约的接口就是合约的抽象。我们可以通过定义合约接口，并指定合约地址，来调用另外一个在以太坊上早已经部署好的合约

ECC942AC-4156-445B-9542-F6379B490E37

8CDE7C2D-CB8D-41E3-833F-1446E07D5FDE

#### 多返回值

09363FC1-2333-472A-8116-1ABDB5BF929F

#### Ownable控制

753431E0-28EA-4C7B-9C26-DDE534A6B4AE

#### Pausable控制

367FDA31-4F30-4E7F-8E41-BF0F9190CF36

#### 时间单位表示

86C34667-D059-4B79-8AB4-6F6ABA3AAD58

这里now和5 minutes都属于时间表示，我们甚至可以直接将时间单位赋值给uint类型的变量。以下的语句是完全合法的。

    uint lastUpdated = now;
    

      uint one_day = 1 days;
      uint five_minutes = 5 minutes;
      uint now_time = now;
      uint two_hours = 2 hours;
    

#### 带参数的函数修饰符

    // ID 和年龄的映射
    mapping (uint => uint) public age;
    
    // 一个函数修饰
    modifier olderThan(uint _age, uint _userId) {
      require(age[_userId] >= _age);
      _;
    }
    
    // 限制年龄大于16岁才能开车
    function driveCar(uint _userId) public olderThan(16, _userId) {
    
    }
    

#### for循环

环在智能合约中使用范围不是很明显。因为循环消耗大量的计算步骤，智能合约编纂者都尽量减少循环次数，甚至从根本上避免循环的产生

    function getOdds() pure external returns(uint[]) {
      uint[] memory odds = new uintUnsupported embed;
    
      uint counter = 0;
    
      for (uint i = 1; i <= 10; i++) {
        if (i % 2 != 0) {
          odds[counter] = i;
          counter++;
        }
      }
      return odds;
    }
    

#### 合约收款：payable修饰符

    contract OnlineStore {
      function buySomething() external payable {
        // 查看付款金额
        require(msg.value == 0.001 ether);
        // 将对应金额的物品转移给调用方
        transferSomething(msg.sender);
      }
    }
    

上面多了一个关键字payable和两个环境变量：msg.value和msg.sender。payable表示调用者可以在调用该方法时附上想发送的以太币数量；msg.value则表示实际使用中调用者支付的以太币，这里要和gas费用相区别，gas费用是付给矿工来执行合约的，而 msg.value则是直接付给智能合约的费用。在智能合约收到以太币后，可以直接调用其他函数进行等价交换。这种函数经常在ICO的代币发行中使用，让广大用户可以直接通过合约交换以太坊为代币，无需人工干预，公平、公正、公开

#### 支付费用：transfer方法

智能合约的作者往往能获得报酬。怎么获得呢？就是通过用户不断向智能合约打入以太币，并积累在智能合约的balance余额中。当智能合约所有者想提取的时候，调用一个函数就可以执行

A984B0D7-FBB5-4CA0-9FC7-DB47FB2BC256

#### Truffle

Truffle内置了一个默认的测试底层网络实现 Ganache。Ganache 原名 Test-RPC，是以太坊开源项目中采用JavaScript编写的模拟区块链运行的节点。它本身并不联网产生区块，而是一个测试环境中的高度仿真模拟器。它的Web3命令支持丰富，紧跟社区的最新提案和开发进度，因为没有实际挖矿（仅仅模拟挖矿行为），所以它的运行速度是惊人的，非常利于测试环境下的高速使用，不需要等实际区块链的出块时间间隔

![EFD042A4-D6FD-4DD6-A83C-15F52C7E3FF8.png](https://storage.googleapis.com/papyrus_images/95abda582280d0a341ba7ae798ab4ea29e9a7a7a4f296f6e8006049c34eae620.png)

EFD042A4-D6FD-4DD6-A83C-15F52C7E3FF8.png

#### 命令

91F22119-838F-4434-9836-83983AAB103D

    $ mkdir truffle-project
    $ cd truffle-project
    $ truffle unbox metacoin
    

2E0AF910-243F-4C8F-8013-A6376F777513

453E6230-9C76-4E60-9078-95117039817D

每个目录具体解释如下：

*   contracts目录包含了主要的合约代码，示例中包含了MetaCoin.sol（主要合约），ConvertLib.sol （库文件）和部署时记录地址的合约 Migration.sol
    
*   migrations目录包含了两个部署文件，先会部署Migration.sol, 再会链接ConvertLib.sol与MetaCoin.sol部署到链上，两者顺序不可颠倒。代码中aritfacts.require()函数相当于node.js中的require()函数，指明了我们希望引用哪个合约的抽象。这个引用名必须与合约中的类名相同
    
*   test目录包含了两种不同的测试文件，智能合约可以通过sol结尾的合约进行测试写作，也可以通过js文件写作，两者功能几乎一致，笔者在开发组多时，考虑到语言的通用性与灵活程度，平时一直使用js的格式书写测试。Truffle的测试都是Mocha测试框架格式的语法
    
*   truffle-config.js和truffle.js都保存了控制truffle行为定义的全局变量，当想调换测试网络时，可以编辑truffle.js指定网络和端口
    

#### ERC20合约

3C20E549-C0C0-4A14-9142-6E565F46A871

---

*Originally published on [MarkTang's Blog](https://paragraph.com/@marktang-s-blog/solidity)*
