目录

  • 1 区块链行业与职业
    • 1.1 区块链行业
      • 1.1.1 2020全球区块链产业应用与人才培养报告
      • 1.1.2 区块链行业人才需求解读(高职版)
      • 1.1.3 区块链应用操作员国家职业技术技能标准
      • 1.1.4 区块链职业技能标准及1+X 证书
      • 1.1.5 20220830 区块链2班授课视频1
      • 1.1.6 20220830 区块链2班腾讯会议
      • 1.1.7 20220830 区块链1班腾讯会议视频
    • 1.2 区块链应用软件开发与运维职业技能等级
      • 1.2.1 职业技能等级标准
      • 1.2.2 腾讯教材(初级)
      • 1.2.3 腾讯教材(中级)
      • 1.2.4 腾讯教材(高级)
      • 1.2.5 20220906 区块链1班腾讯会议视频
    • 1.3 区块链专业简介
      • 1.3.1 高职专科
      • 1.3.2 高职本科
  • 2 区块链基础
    • 2.1 区块链概述
      • 2.1.1 区块链实用型技能树
      • 2.1.2 什么是区块链
      • 2.1.3 区块链起源和发展
      • 2.1.4 分布式系统概论
      • 2.1.5 20220906 区块链1班腾讯会议视频
    • 2.2 remix 开发环境搭建
    • 2.3 访问智谷星图的remix环境
    • 2.4 FISCO BCOS入门
      • 2.4.1 FISCO BCOS环境搭建
      • 2.4.2 WeBase环境搭建
      • 2.4.3 Webase智能合约管理
    • 2.5 毕业啦项目介绍
    • 2.6 智能合约初探
    • 2.7 深入浅出solidity
  • 3 区块链咨询
    • 3.1 区块链组成原理
      • 3.1.1 20220906 区块链2班手机录像
      • 3.1.2 20220906 区块链2班腾讯会议视频
    • 3.2 智能合约
      • 3.2.1 20220906 区块链2班腾讯会议视频
      • 3.2.2 20220906 区块链2班手机录像
    • 3.3 区块链电子发票
      • 3.3.1 20220907 区块链1班腾讯视频
      • 3.3.2 20220907 区块链2班腾讯会议视频
    • 3.4 区块链跨境支付
      • 3.4.1 20220907 区块链1班腾讯视频
      • 3.4.2 20220907 区块链2班腾讯会议视频
      • 3.4.3 20220912 区块链2班腾讯会议视频
    • 3.5 区块链供应链金融
      • 3.5.1 20220907 区块链1班腾讯视频
    • 3.6 区块链国际贸易
      • 3.6.1 20220907 区块链1班腾讯视频
  • 4 solidity基础
    • 4.1 第一个solidity程序
      • 4.1.1 20220912 21级2班腾讯会议视频
      • 4.1.2 20220912 21级2班手机视频
      • 4.1.3 20220914 21级1班手机录像
      • 4.1.4 20220914 21级1班腾讯会议视频
    • 4.2 数据类型
      • 4.2.1 20220912 21级2班腾讯会议视频
      • 4.2.2 20220912 21级2班手机视频
      • 4.2.3 20220914 21级1班腾讯会议视频
    • 4.3 变量和运算符
      • 4.3.1 20220920 21级2班腾讯会议视频
      • 4.3.2 20220920 21级2班腾讯会议视频
      • 4.3.3 20220920 21级1班腾讯会议视频
      • 4.3.4 20220920 21级1班手机录像
      • 4.3.5 20220920 21级1班手机录像
    • 4.4 循环语句
    • 4.5 条件语句
    • 4.6 数据存储类型
      • 4.6.1 20220920 21级2班腾讯会议视频
      • 4.6.2 20220920 21级1班腾讯会议
      • 4.6.3 20220920 21级1班手机录像
    • 4.7 结构体
      • 4.7.1 20220920 21级1班腾讯会视频
      • 4.7.2 20220920 21级1班手机录像
      • 4.7.3 20220921 21级2班手机录像
      • 4.7.4 20220921 21级2班腾讯会议视频
    • 4.8 数组
      • 4.8.1 20220921 21级1班手机录像
      • 4.8.2 20220921 21级1班腾讯会议视频
      • 4.8.3 20220927 21级2班腾讯会议视频
      • 4.8.4 20220927 21级2班手机录像
    • 4.9 枚举
    • 4.10 20220921 21级1班手机录像
    • 4.11 20220921 21级1班腾讯会议视频
    • 4.12 映射
    • 4.13 Solidity中的单位
    • 4.14 20220927 21级2班腾讯会议视频
    • 4.15 20220927 21级2班手机录像
    • 4.16 20220927 21级1班腾讯会议视频
    • 4.17 20220927 21级1班手机录像
    • 4.18 函数
      • 4.18.1 20220927 21级1班腾讯会议视频
      • 4.18.2 20220927 21级1班手机录像
    • 4.19 函数修饰符
    • 4.20 变量作用域和函数可见性
    • 4.21 状态可变性
  • 5 solidity进阶
    • 5.1 构造函数
    • 5.2 函数重载
    • 5.3 抽象合约
    • 5.4 库
    • 5.5 接口
    • 5.6 加密函数和数学函数
    • 5.7 合约继承
    • 5.8 错误处理
    • 5.9 事件
      • 5.9.1 2022.11.02 区块链1班腾讯会议视频
    • 5.10 类型转换
      • 5.10.1 2022.11.02 区块链1班腾讯会议视频
      • 5.10.2 2022.11.02 区块链2班腾讯会议视频
    • 5.11 回退函数
      • 5.11.1 2022.11.08 区块链2班腾讯会议视频
    • 5.12 转账方式
      • 5.12.1 2022.11.08 区块链2班腾讯会议视频
      • 5.12.2 2022.11.08 区块链1班 腾讯会议视频
    • 5.13 实践代码
  • 6 智能合约游戏案例初阶
    • 6.1 合约
    • 6.2 整形和状态变量
    • 6.3 算数运算符
    • 6.4 结构体和字符串
    • 6.5 数组
    • 6.6 函数1
    • 6.7 函数2
    • 6.8 函数3
    • 6.9 类型转换
    • 6.10 事件
    • 6.11 地址类型、映射
    • 6.12 全局变量
    • 6.13 异常处理
    • 6.14 引入与继承
    • 6.15 数据位置
    • 6.16 函数可见性1
    • 6.17 函数可见性2
    • 6.18 接口1
    • 6.19 接口2
    • 6.20 接口3
    • 6.21 条件语句
  • 7 智能合约游戏案例进阶
    • 7.1 智能合约的不可更改性
    • 7.2 合约的“所有权”和权限控制
    • 7.3 函数修饰符 onlyOwner
    • 7.4 时间单位
    • 7.5 区块宠物间隔周期
    • 7.6 函数修饰符-公有函数和安全性
    • 7.7 函数修饰符-带参数的函数修饰符
    • 7.8 函数修饰符-自定义函数修饰符
    • 7.9 燃料gas
    • 7.10 gas-使用view节约gas
    • 7.11 gas-存储非常昂贵
    • 7.12 gas-for循环减少写入
    • 7.13 可支付
    • 7.14 体现和转账
    • 7.15 宠物大乐斗
    • 7.16 生成随机数
    • 7.17 宠物大乐斗流程
    • 7.18 重构通用逻辑
    • 7.19 更多重构
    • 7.20 排行榜-斗舞逻辑
    • 7.21 宠物舞技排行榜
    • 7.22 宠物胜利判断
    • 7.23 宠物失败判断
  • 8 区块链企业项目
    • 8.1 项目背景
    • 8.2 企业智能合约应用
    • 8.3 功能实现上
    • 8.4 功能实现下
    • 8.5 功能实现下代码续
    • 8.6 毕业证系统的solidity代码
  • 9 solidity语法详解
    • 9.1 源文件映射
    • 9.2 特殊特性(Esoteric Features)
    • 9.3 新建目录
    • 9.4 内部机制
    • 9.5 调用数据的布局(Layout of CallData)
    • 9.6 内存变量的布局(Layout in Memory)
    • 9.7 状态变量的存储模型(Layout of State Variables in Storage)
    • 9.8 独立的汇编语言
    • 9.9 Solidity Assembly
    • 9.10 库(Libraries)
    • 9.11 接口
    • 9.12 抽象合约(Abstract Contracts)
    • 9.13 继承(Inheritance)
    • 9.14 事件(Events)
    • 9.15 回退函数(fallback function)
    • 9.16 常量(constant state variables)
    • 9.17 新建目录
    • 9.18 函数修改器(Function Modifiers)
    • 9.19 访问函数(Getter Functions)
    • 9.20 可见性或权限控制(Visibility And Accessors)
    • 9.21 合约
    • 9.22 内联汇编(Inline Assembly)
    • 9.23 异常(Excepions)
    • 9.24 作用范围和声明(Scoping And Decarations)
    • 9.25 赋值(Assignment)
    • 9.26 表达式的执行顺序(Order of Evaluation of Expressions)
    • 9.27 创建合约实例(Creating Contracts via `new`)
    • 9.28 函数调用(Function Calls)
    • 9.29 新建目录
    • 9.30 控制结构
    • 9.31 入参和出参(Input Parameters and Output Parameters)
    • 9.32 地址相关(Address Related)
    • 9.33 数学和加密函数(Mathematical and Cryptographic Functions)
    • 9.34 特殊变量及函数(Special Variables and Functions)
    • 9.35 时间单位(Time Units)
    • 9.36 货币单位(Ether Units)
    • 9.37 类型推断(Type Deduction)
    • 9.38 基本类型间的转换
    • 9.39 左值的相关运算符
    • 9.40 映射/字典(mappings)
    • 9.41 结构体(struct)
    • 9.42 数组
    • 9.43 数据位置(Data location)
    • 9.44 引用类型(Reference Types)
    • 9.45 函数(Function Types)
    • 9.46 枚举
    • 9.47 六进制字面量
    • 9.48 字符串(String literal)
    • 9.49 小数
    • 9.50 字节数组(byte arrays)
    • 9.51 地址(Address)
    • 9.52 整型(Integer)
    • 9.53 布尔(Booleans)
    • 9.54 值类型与引用类型
    • 9.55 智能合约源文件的基本要素概览(Structure of a Contract)
    • 9.56 Solidity智能合约文件结构
    • 9.57 solidity中的特殊函数
    • 9.58 新建目录
    • 9.59 新建目录
  • 10 springboot vue前端后端分离项目
    • 10.1 创建springboot 动态页面 和api
    • 10.2 创建vue项目
    • 10.3 编写vue前端页面访问api
独立的汇编语言

上面介绍的在Solidity中嵌入的内联汇编语言也可以单独使用。实际上,它是被计划用来作为编译器的一种中间语言。在这个目的下,它尝试达到下述的目标:

  1. 使用它编写的代码要可读,即使代码是从Solidity编译得到的。

  2. 从汇编语言转为字节码应该尽可能的少坑。

  3. 控制流应该容易检测来帮助进行形式验证与优化。

为了达到第一条和最后一条的目标,Solidity汇编语言提供了高层级的组件比如,for循环,switch语句和函数调用。这样的话,可以不直接使用SWAP,DUP,JUMP,JUMPI语句,因为前两个有混淆的数据流,后两个有混淆的控制流。此外,函数形式的语句如mul(add(x, y), 7)比纯的指令码的形式7 y x add num更加可读。

第二个目标是通过引入一个绝对阶段来实现,该阶段只能以非常规则的方式去除较高级别的构造,并且仍允许检查生成的低级汇编代码。Solidity汇编语言提供的非原生的操作是用户定义的标识符的命名查找(函数名,变量名等),这些都遵循简单和常规的作用域规则,会清理栈上的局部变量。

作用域:一个标识符(标签,变量,函数,汇编)在定义的地方,均只有块级作用域(作用域会延伸到,所在块所嵌套的块)。跨函数边界访问局部变量是不合法的,即使可能在作用域内(译者注:这里可能说的是,函数内定义多个函数的情况,JavaScript有这种语法)。不允许shadowing。局部变量不能在定义前被访问,但标签,函数和汇编可以。汇编是非常特殊的块结构可以用来,如,返回运行时的代码,或创建合约。外部定义的汇编变量在子汇编内不可见。

如果控制流来到了块的结束,局部变量数匹配的pop指令会插入到栈底(译者注:移除局部变量,因为局部变量失效了)。无论何时引用局部变量,代码生成器需要知道其当前在堆栈中的相对位置,因此需要跟踪当前所谓的堆栈高度。由于所有的局部变量在块结束时会被移除,因此在进入块之前和之后的栈高应该是不变的,如果不是这样的,将会抛出一个警告。

我们为什么要使用高层级的构造器,比如switchfor和函数。

使用switchfor和函数,可以在不用jumpjumpi的情况下写出来复杂的代码。这会让分析控制流更加容易,也可以进行更多的形式验证及优化。

此外,如果手动使用jumps,计算栈高是非常复杂的。栈内所有的局部变量的位置必须是已知的,否则指向本地变量的引用,或者在块结束时自动删除局部变量都不会正常工作。脱机处理机制正确的在块内不可达的地方插入合适的操作以修正栈高来避免出现jump时非连续的控制流带来的栈高计算不准确的问题。

示例:

我们从一个例子来看一下Solidity到这种中间的脱机汇编结果。我们可以一起来考虑下下述Soldity程序的字节码:

contract C {
  function f(uint x) returns (uint y) {
    y = 1;
    for (uint i = 0; i < x; i++)
      y = 2 * y;
  }
}

它将生成下述的汇编内容:

{
  mstore(0x40, 0x60) // store the "free memory pointer"
  // function dispatcher
  switch div(calldataload(0), exp(2, 226))
  case 0xb3de648b {
    let (r) = f(calldataload(4))
    let ret := $allocate(0x20)
    mstore(ret, r)
    return(ret, 0x20)
  }
  default { revert(0, 0) }
  // memory allocator
  function $allocate(size) -> pos {
    pos := mload(0x40)
    mstore(0x40, add(pos, size))
  }
  // the contract function
  function f(x) -> y {
    y := 1
    for { let i := 0 } lt(i, x) { i := add(i, 1) } {
      y := mul(2, y)
    }
  }
}

在经过脱机汇编阶段,它会编译成下述的内容:

{
  mstore(0x40, 0x60)
  {
    let $0 := div(calldataload(0), exp(2, 226))
    jumpi($case1, eq($0, 0xb3de648b))
    jump($caseDefault)
    $case1:
    {
      // the function call - we put return label and arguments on the stack
      $ret1 calldataload(4) jump(f)
      // This is unreachable code. Opcodes are added that mirror the
      // effect of the function on the stack height: Arguments are
      // removed and return values are introduced.
      pop pop
      let r := 0
      $ret1: // the actual return point
      $ret2 0x20 jump($allocate)
      pop pop let ret := 0
      $ret2:
      mstore(ret, r)
      return(ret, 0x20)
      // although it is useless, the jump is automatically inserted,
      // since the desugaring process is a purely syntactic operation that
      // does not analyze control-flow
      jump($endswitch)
    }
    $caseDefault:
    {
      revert(0, 0)
      jump($endswitch)
    }
    $endswitch:
  }
  jump($afterFunction)
  allocate:
  {
    // we jump over the unreachable code that introduces the function arguments
    jump($start)
    let $retpos := 0 let size := 0
    $start:
    // output variables live in the same scope as the arguments and is
    // actually allocated.
    let pos := 0
    {
      pos := mload(0x40)
      mstore(0x40, add(pos, size))
    }
    // This code replaces the arguments by the return values and jumps back.
    swap1 pop swap1 jump
    // Again unreachable code that corrects stack height.
    0 0
  }
  f:
  {
    jump($start)
    let $retpos := 0 let x := 0
    $start:
    let y := 0
    {
      let i := 0
      $for_begin:
      jumpi($for_end, iszero(lt(i, x)))
      {
        y := mul(2, y)
      }
      $for_continue:
      { i := add(i, 1) }
      jump($for_begin)
      $for_end:
    } // Here, a pop instruction will be inserted for i
    swap1 pop swap1 jump
    0 0
  }
  $afterFunction:
  stop
}

汇编有下面四个阶段:

  1. 解析

  2. 脱汇编(移除switch,for和函数)

  3. 生成指令流

  4. 生成字节码

我们将简单的以步骤1到3指定步骤。更加详细的步骤将在后面说明。

解析、语法

解析的任务如下:

  • 将字节流转为符号流,去掉其中的C++风格的注释(一种特殊的源代码引用的注释,这里不打算深入讨论)。

  • 将符号流转为下述定义的语法结构的AST。

  • 注册块中定义的标识符,标注从哪里开始(根据AST节点的注解),变量可以被访问。

组合词典遵循由Solidity本身定义的词组。

空格用于分隔标记,它由空格,制表符和换行符组成。 注释是常规的JavaScript / C ++注释,并以与Whitespace相同的方式进行解释。

语法:

AssemblyBlock = '{' AssemblyItem* '}'
AssemblyItem =
    Identifier |
    AssemblyBlock |
    FunctionalAssemblyExpression |
    AssemblyLocalDefinition |
    FunctionalAssemblyAssignment |
    AssemblyAssignment |
    LabelDefinition |
    AssemblySwitch |
    AssemblyFunctionDefinition |
    AssemblyFor |
    'break' | 'continue' |
    SubAssembly | 'dataSize' '(' Identifier ')' |
    LinkerSymbol |
    'errorLabel' | 'bytecodeSize' |
    NumberLiteral | StringLiteral | HexLiteral
Identifier = [a-zA-Z_$] [a-zA-Z_0-9]*
FunctionalAssemblyExpression = Identifier '(' ( AssemblyItem ( ',' AssemblyItem )* )? ')'
AssemblyLocalDefinition = 'let' IdentifierOrList ':=' FunctionalAssemblyExpression
FunctionalAssemblyAssignment = IdentifierOrList ':=' FunctionalAssemblyExpression
IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)*
AssemblyAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
    ( 'default' AssemblyBlock )?
AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock
AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
    ( '->' '(' IdentifierList ')' )? AssemblyBlock
AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression)
    FunctionalAssemblyExpression ( AssemblyBlock | FunctionalAssemblyExpression) AssemblyBlock
SubAssembly = 'assembly' Identifier AssemblyBlock
LinkerSymbol = 'linkerSymbol' '(' StringLiteral ')'
NumberLiteral = HexNumber | DecimalNumber
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+

脱汇编

一个AST转换,移除其中的forswitch和函数构建。结果仍由同一个解析器,但它不确定使用什么构造。如果添加仅跳转到并且不继续的jumpdests,则添加有关堆栈内容的信息,除非没有局部变量访问到外部作用域或栈高度与上一条指令相同。伪代码如下:

desugar item: AST -> AST =
match item {
AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) ->
  <name>:
  {
    jump($<name>_start)
    let $retPC := 0 let argn := 0 ... let arg1 := 0
    $<name>_start:
    let ret1 := 0 ... let retm := 0
    { desugar(body) }
    swap and pop items so that only ret1, ... retm, $retPC are left on the stack
    jump
    0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC
  }
AssemblyFor('for' { init } condition post body) ->
  {
    init // cannot be its own block because we want variable scope to extend into the body
    // find I such that there are no labels $forI_*
    $forI_begin:
    jumpi($forI_end, iszero(condition))
    { body }
    $forI_continue:
    { post }
    jump($forI_begin)
    $forI_end:
  }
'break' ->
  {
    // find nearest enclosing scope with label $forI_end
    pop all local variables that are defined at the current point
    but not at $forI_end
    jump($forI_end)
    0 (as many as variables were removed above)
  }
'continue' ->
  {
    // find nearest enclosing scope with label $forI_continue
    pop all local variables that are defined at the current point
    but not at $forI_continue
    jump($forI_continue)
    0 (as many as variables were removed above)
  }
AssemblySwitch(switch condition cases ( default: defaultBlock )? ) ->
  {
    // find I such that there is no $switchI* label or variable
    let $switchI_value := condition
    for each of cases match {
      case val: -> jumpi($switchI_caseJ, eq($switchI_value, val))
    }
    if default block present: ->
      { defaultBlock jump($switchI_end) }
    for each of cases match {
      case val: { body } -> $switchI_caseJ: { body jump($switchI_end) }
    }
    $switchI_end:
  }
FunctionalAssemblyExpression( identifier(arg1, arg2, ..., argn) ) ->
  {
    if identifier is function <name> with n args and m ret values ->
      {
        // find I such that $funcallI_* does not exist
        $funcallI_return argn  ... arg2 arg1 jump(<name>)
        pop (n + 1 times)
        if the current context is `let (id1, ..., idm) := f(...)` ->
          let id1 := 0 ... let idm := 0
          $funcallI_return:
        else ->
          0 (m times)
          $funcallI_return:
          turn the functional expression that leads to the function call
          into a statement stream
      }
    else -> desugar(children of node)
  }
default node ->
  desugar(children of node)
}

生成操作码流

在操作码流生成期间,我们在一个计数器中跟踪当前的栈高,所以通过名称访问栈的变量是可能的。栈高在会修改栈的操作码后或每一个标签后进行栈调整。当每一个新局部变量被引入时,它都会用当前的栈高进行注册。如果要访问一个变量(或者拷贝其值,或者对其赋值),会根据当前栈高与变量引入时的当时栈高的不同来选择合适的DUPSWAP指令。

伪代码:

codegen item: AST -> opcode_stream =
match item {
AssemblyBlock({ items }) ->
  join(codegen(item) for item in items)
  if last generated opcode has continuing control flow:
    POP for all local variables registered at the block (including variables
    introduced by labels)
    warn if the stack height at this point is not the same as at the start of the block
Identifier(id) ->
  lookup id in the syntactic stack of blocks
  match type of id
    Local Variable ->
      DUPi where i = 1 + stack_height - stack_height_of_identifier(id)
    Label ->
      // reference to be resolved during bytecode generation
      PUSH<bytecode position of label>
    SubAssembly ->
      PUSH<bytecode position of subassembly data>
FunctionalAssemblyExpression(id ( arguments ) ) ->
  join(codegen(arg) for arg in arguments.reversed())
  id (which has to be an opcode, might be a function name later)
AssemblyLocalDefinition(let (id1, ..., idn) := expr) ->
  register identifiers id1, ..., idn as locals in current block at current stack height
  codegen(expr) - assert that expr returns n items to the stack
FunctionalAssemblyAssignment((id1, ..., idn) := expr) ->
  lookup id1, ..., idn in the syntactic stack of blocks, assert that they are variables
  codegen(expr)
  for j = n, ..., i:
  SWAPi where i = 1 + stack_height - stack_height_of_identifier(idj)
  POP
AssemblyAssignment(=: id) ->
  look up id in the syntactic stack of blocks, assert that it is a variable
  SWAPi where i = 1 + stack_height - stack_height_of_identifier(id)
  POP
LabelDefinition(name:) ->
  JUMPDEST
NumberLiteral(num) ->
  PUSH<num interpreted as decimal and right-aligned>
HexLiteral(lit) ->
  PUSH32<lit interpreted as hex and left-aligned>
StringLiteral(lit) ->
  PUSH32<lit utf-8 encoded and left-aligned>
SubAssembly(assembly <name> block) ->
  append codegen(block) at the end of the code
dataSize(<name>) ->
  assert that <name> is a subassembly ->
  PUSH32<size of code generated from subassembly <name>>
linkerSymbol(<lit>) ->
  PUSH32<zeros> and append position to linker table
}