什么是智能合约

什么是智能合约

一个智能合约是一套以数字形式定义的承诺(promises) ,包括合约参与方可以在上面执行这些承诺的协议。一个合约由一组代码(合约的函数)和数据(合约的状态)组成,并且运行在以太坊虚拟机上.

以太坊虚拟机(EVM)使用了256比特长度的机器码,是一种基于堆栈的虚拟机,用于执行以太坊智能合约 。由于EVM是针对以太坊体系设计的,因此使用了以太坊账户模型(Account Model)进行价值传输。

合约的代码具有什么能力:
读取交易数据。
读取或写入合约自己的存储空间。
读取环境变量(块高,哈希值,gas)
向另一个合约发送一个“内部交易”。

1. 什么是solidity

Solidity是一种智能合约高级语言,运行在Ethereum虚拟机(EVM)之上。

solidity 语言特点

它的语法接近于Javascript,是一种面向对象的语言。但作为一种真正意义上运行在网络上的去中心合约,它有很多的不同点:

  • 异常机制,类似于事务的原子性。一旦出现异常,所有的执行都将会被回撤,这主要是为了保证合约执行的原子性,以避免中间状态出现的数据不一致。
  • 运行环境是在去中心化的网络上,会比较强调合约或函数执行的调用的方式。因为原来一个简单的函数调用变为了一个网络上的节点中的代码执行
  • 存储是使用网络上的区块链,数据的每一个状态都可以永久存储。

2. 开发的工具

3 快速入门

准备工作
  1. 搭建FISCO-BCOS块链,详细安装见文档
  2. 安装WeBASE-Front,详细见文档 , 修改相关配置,启动服务。

    以下例子演示,依赖于上面的两个步骤。

3.1 开发合约

合约开发步骤:

1. 写合约
2. 编译合约
3. 部署合约
4. 测试合约
5. 生成java文件

说明:WeBase 帮助用户开发、测试和生成对应的Java类,用户获取java类,可以直接进行业务开发,加快开发进度和效率。

3.1.1. 获取合约例子

pragma solidity ^0.4.24;

contract HelloWorld {
    string name;

    function HelloWorld() {
        name = "Hello, World!";
    }

    function get()constant returns(string) {
        return name;
    }

    function set(string n) {
        name = n;
    }
}

3.1.2. 部署合约到区块链上

  • 编译合约

  • 编译合约

  • 调用get方法

  • 调用set方法

  • 检查调用情况

3.1.4. 生成Java

3.1.5 应用开发

  • 下载应用脚手架

    $ git clone https://github.com/FISCO-BCOS/spring-boot-starter.git
  • 导入文件进行配置开始开发

    ?

    [更多工具和例子]()

3.2.1 引入概念:

address:以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用。合约的地址是基于账号随机数和交易数据的哈希计算出来的

ABI:是以太坊的一种合约间调用时或消息发送时的一个消息格式。就是定义操作函数签名,参数编码,返回结果编码等。

交易:以太坊中“交易”是指存储从外部账户发出的消息的签名数据包。

简单理解是:只要对区块链进行写操作,一定会发生交易。

交易回执:发生交易后的返回值

3.2.2 扩展阅读:

3.3 合约文件结构简介

版本声明

pragma solidity ^0.4.24;

状态变量(State Variables)

string name;

详细说明见下文

函数(Functions)

    function get()constant returns(string) {
        return name;
    }

    function set(string n) {
        name = n;
    }

事件(Events)

   //事件的声明
   event AddMsg(address indexed sender, bytes32 msg);
   //事件的使用
   function setData(int256 x) public {
         storedData = x;
         AddMsg(msg.sender, "in the set() method");
     }

结构类型(Structs Types)

   contract Contract {
     struct Data {
       uint deadline;
       uint amount;
     }
     Data data;
     function set(uint id, uint deadline, uint amount) {
       data.deadline = deadline;
       data.amount = amount;
     }
   }

函数修饰符(Function Modifiers)

类似于hook

   modifier only_with_at_least(int x) {
          if (x >= 5) {
            x = x+10;
             _;
          }
       } 

4. 合约编程模式COP

面向条件的编程(COP)是面向合约编程的一个子域,作为一种面向函数和命令式编程的混合模式。COP解决了这个问题,通过需要程序员显示地枚举所有的条件。逻辑变得扁平,没有条件的状态变化。条件片段可以被正确的文档化,复用,可以根据需求和实现来推断。重要的是,COP在编程中把预先条件当作为一等公民。这样的模式规范能保证合约的安全。

4.1 FEATURES

  • 函数主体没有条件判断

例子:

contract Token {
    // The balance of everyone
    mapping (address => uint) public balances;
    // Constructor - we‘re a millionaire!
    function Token() {
        balances[msg.sender] = 1000000;
    }
    // Transfer `_amount` tokens of ours to `_dest`.
    function transfer(uint _amount, address _dest) {
        balances[msg.sender] -= _amount;
        balances[_dest] += _amount;
    }
}

改进后:

function transfer(uint _amount, address _dest) {
    if (balances[msg.sender] < _amount)
        return;
    balances[msg.sender] -= _amount;
    balances[_dest] += _amount;
}

COP的风格

modifier only_with_at_least(uint x) {
    if (balances[msg.sender] >= x) _;
}

function transfer(uint _amount, address _dest)
only_with_at_least(_amount) {
    balances[msg.sender] -= _amount;
    balances[_dest] += _amount;
}

扩展阅读:

5. 语法介绍

5.1 值类型

  • 布尔(Booleans)true false支持的运算符

    !逻辑非&& 逻辑与|| 逻辑或== 等于!= 不等于

  • 整型(Integer)int/uint:变长的有符号或无符号整型。变量支持的步长以8递增,支持从uint8到uint256,以及int8到int256。需要注意的是,uint和int默认代表的是uint256和int256
  • 地址(Address):以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用
  • 定长字节数组(fixed byte arrays)
  • 有理数和整型(Rational and Integer Literals,String literals)
  • 枚举类型(Enums)
  • 函数(Function Types)

5.2 引用类型(Reference Types)

  • 不定长字节数组(bytes)
  • 字符串(string)
  • 数组(Array)
  • 结构体(Struts)

更多详情见官方API

6. 重要概念

6.1 Solidity的数据位置

数据位置的类型

变量的存储位置属性。有三种类型,memory,storage和calldata。

  • memory存储位置同我们普通程序的内存类似。即分配,即使用,越过作用域即不可被访问,等待被回收-
  • storage的变量,数据将永远存在于区块链上。
  • calldata 数据位置比较特殊,一般只有外部函数的参数(不包括返回参数)被强制指定为calldata

Storage - 状态变量的存储模型

大小固定的变量(除了映射,变长数组以外的所有类型)在存储(storage)中是依次连续从位置0开始排列的。如果多个变量占用的大小少于32字节,会尽可能的打包到单个storage槽位里,具体规则如下:

  • 在storage槽中第一项是按低位对齐存储(lower-order aligned)
  • 基本类型存储时仅占用其实际需要的字节。
  • 如果基本类型不能放入某个槽位余下的空间,它将被放入下一个槽位。
  • 结构体和数组总是使用一个全新的槽位,并占用整个槽(但在结构体内或数组内的每个项仍遵从上述规则)

优化建议:

为了方便EVM进行优化,尝试有意识排序storage的变量和结构体的成员,从而让他们能打包得更紧密。比如,按这样的顺序定义,uint128, uint128, uint256,而不是uint128, uint256, uint128。因为后一种会占用三个槽位。

Memory - 内存变量的布局(Layout in Memory)

Solidity预留了3个32字节大小的槽位:

0-64:哈希方法的暂存空间(scratch space)

64-96:当前已分配内存大小(也称空闲内存指针(free memory pointer))

暂存空间可在语句之间使用(如在内联编译时使用)

Solidity总是在空闲内存指针所在位置创建一个新对象,且对应的内存永远不会被释放(也许未来会改变这种做法)。

有一些在Solidity中的操作需要超过64字节的临时空间,这样就会超过预留的暂存空间。他们就将会分配到空闲内存指针所在的地方,但由于他们自身的特点,生命周期相对较短,且指针本身不能更新,内存也许会,也许不会被清零(zerod out)。因此,大家不应该认为空闲的内存一定已经是清零(zeroed out)的。

例子

6.2 address

以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用

6.3 event

event AddMsg(address indexed sender, bytes32 msg);

  • 这行代码声明了一个“事件”。客户端(服务端应用也适用)可以以很低的开销来监听这些由区块链触发的事件

事件是使用EVM日志内置功能的方便工具,在DAPP的接口中,它可以反过来调用Javascript的监听事件的回调。

var event = instance.AddMsg({}, function(error, result) {
        if (!error) {
            var msg = "AddMsg: " + utils.hex2a(result.args.msg) + " from "
            console.log(msg);
            return;
        } else {
            console.log(‘it error‘)
        }
    });

  • 事件在合约中可被继承。当被调用时,会触发参数存储到交易的日志中(一种区块链上的特殊数据结构)。这些日志与合约的地址关联,并合并到区块链中,只要区块可以访问就一直存在(至少Frontier,Homestead是这样,但Serenity也许也是这样)。日志和事件在合约内不可直接被访问,即使是创建日志的合约。
  • 日志位置在nodedir0/log 里面,可以打出特殊的类型进行验证

6.4 数组

数组是定长或者是变长数组。有length属性,表示当前的数组长度。

  1. bytes:类似于byte[], 动态长度的字节数组
  2. string:类似于bytes,动态长度的UTF-8编码的字符类型
  3. bytes1~bytes32

    一般使用定长的 bytes1~bytes32。在知道字符串长度的情况下,指定长度时,更加节省空间。

6.4.1 创建数组

  1. 字面量 uint[] memory a = []
  2. new uint[] memory a = new uint[](7);
    例子
     pragma solidity ^0.4.0;
    
     contract SimpleStartDemo{
       uint[] stateVar;
    
       function f(){
        //定义一个变长数组
         uint[] memory memVar;
    
         //不能在使用new初始化以前使用
         //VM Exception: invalid opcode
         //memVar [0] = 100;
    
         //通过new初始化一个memory的变长数组
         memVar = new uint[](2);
    
         //不能在使用new初始化以前使用
         //VM Exception: invalid opcode
         //stateVar[0] = 1;
    
         //通过new初始化一个storage的变长数组
         stateVar = new uint[](2);
         stateVar[0] = 1;
       }
     }
    

6.4.2 数组的属性和方法

length属性

storage变长数组是可以修改length

memory变长数组是不可以修改length

push方法

storage变长数组可以使用push方法

bytes可以使用push方法

例子

pragma solidity ^0.4.2;

contract SimpleStartDemo {
  uint[] stateVar;

  function f() returns (uint){
    //在元素初始化前使用
    stateVar.push(1);

    stateVar = new uint[](1);
    stateVar[0] = 0;
    //自动扩充长度
     uint pusharr = stateVar.push(1);
     uint len = stateVar.length;
    //不支持memory
    //Member "push" is not available in uint256[] memory outside of storage.
    //uint[] memory memVar = new uint[](1);
    //memVar.push(1);

    return len;
  }
}

下标:和其他语言类似

6.4.3 Memory数组

  1. 如果Memory数组作为函数的参数传递,只能支持ABI能支持的类型类型。
  2. Memory数组是不能修改修改数组大小的属性
    例子

    pragma solidity ^0.4.2;

    contract SimpleStartDemo {

     function f() {
         //创建一个memory的数组
         uint[] memory a = new uint[](7);
    
         //不能修改长度
         //Error: Expression has to be an lvalue.
         //a.length = 100;
     }
    
     //storage
     uint[] b;
    
     function g(){
         b = new uint[](7);
         //可以修改storage的数组
         b.length = 10;
         b[9] = 100;
     }

    }

EVM的限制

由于EVM的限制,不能通过外部函数直接返回动态数组和多维数组

  1. 将stroage数组不能直接返回,需要转换成memory类型的返回
      //Data层数据
      struct Rate {
              int key1;
            int unit;
            uint[3] exDataArr;
            bytes32[3] exDataStr;
        }

        mapping(int =>Rate) Rates;
     function getRate(int key1) public constant returns(int,uint[3],bytes32[3]) {
            uint[3] memory exDataInt = Rates[key1].exDataArr;
            bytes32[3] memory exDataStr = Rates[key1].exDataStr;
            return (Rates[key1].unit,exDataInt,exDataStr);
        }

业务场景

6.5 函数

function (<parameter types>) {internal(默认)|external} constant [returns (<return types>)]

6.5.1 函数的internal与external

例子

pragma solidity ^0.4.5;

contract FuntionTest{
    function internalFunc() internal{}

    function externalFunc() external{}

    function callFunc(){
        //直接使用内部的方式调用
        internalFunc();

        //不能在内部调用一个外部函数,会报编译错误。
        //Error: Undeclared identifier.
        //externalFunc();

        //不能通过`external`的方式调用一个`internal`
        //Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //this.internalFunc();

        //使用`this`以`external`的方式调用一个外部函数
        this.externalFunc();
    }
}
contract FunctionTest1{
    function externalCall(FuntionTest ft){
        //调用另一个合约的外部函数
        ft.externalFunc();

        //不能调用另一个合约的内部函数
        //Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //ft.internalFunc();
    }
}

访问函数有外部(external)可见性。如果通过内部(internal)的方式访问,比如直接访问,你可以直接把它当一个变量进行使用,但如果使用外部(external)的方式来访问,如通过this.,那么它必须通过函数的方式来调用。

例子

    pragma solidity ^0.4.2;

    contract SimpleStartDemo {
        uint public c = 10;

        function accessInternal() returns (uint){
            return c;
        }

        function accessExternal() returns (uint){
            return this.c();
        }
    }

6.5.2 函数调用

  • 内部调用,不会创建一个EVM调用,也叫消息调用
  • 外部调用,创建EVM调用,会发起消息调用

6.5.3 函数修改器(Function Modifiers)

修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)

例子

    pragma solidity ^0.4.2;

    contract SimpleStartDemo {
        int256 storedData;
        event AddMsg(address indexed sender, bytes32 msg);

        modifier only_with_at_least(int x) {
           if (x >= 5) {
             x = x+10;
              _;
           }
        }
        function setData(int256 x) public only_with_at_least(x){
            storedData = x;
            AddMsg(msg.sender, "[in the set() method]");
        }
    }

6.5.4合约构造函数 同名函数

  • 可选
  • 仅能有一个构造器
  • 不支持重载

6.6 Constant

函数也可被声明为常量,这类函数将承诺自己不修改区块链上任何状态。

一般从链上获取数据时,get函数都会加上constant

6.7 继承(Inheritance)

Solidity通过复制包括多态的代码来支持多重继承。

父类

    pragma solidity ^0.4.4;

    contract Meta {
        string  public name;
        string  public abi;
        address metaAddress;

        function Meta(string n,string a){
            name=n;
            abi=a;
        }

        function getMeta()public constant returns(string,string,address){
            return (name,abi,metaAddress);
        }

        function setMetaAddress(address meta) public {
            metaAddress=meta;
        }
    }

子类

    pragma solidity ^0.4.4;

    import "Meta.sol";
    contract Demo is Meta{
        bytes32 public orgID; 

        function Demo (string n,string abi,bytes32 id) Meta(n,abi)
        {
            orgID = id;
        }
    }
最简单的合约架构

1:1合约架构图

7. 限制

基于EVM的限制,不能通过外部函数返回动态的内容

please keep in mind

  • Fail as early and loudly as possible
  • Favor pull over push payments
  • Order your function code: conditions, actions, interactions
  • Be aware of platform limits
  • Write tests
  • Fault tolerance and Automatic bug bounties
  • Limit the amount of funds deposited
  • Write simple and modular code
  • Don’t write all your code from scratch
  • Timestamp dependency: Do not use timestamps in critical parts of the code, because miners can manipulate them
  • Call stack depth limit: Don’t use recursion, and be aware that any call can fail if stack depth limit is reached
  • Reentrancy: Do not perform external calls in contracts. If you do, ensure that they are the very last thing you do

8. 语言本身存在的痛点

  1. ABI支持的类型有限,难以返回复杂的结构体类型。
  2. Deep Stack的问题
  3. 难以调试,只能靠event log ,进行合约的调试
  4. 合约调用合约只能使用定长数组

9. 合约架构

9.1. 合约架构分层

实用架构示意图

合约的架构分两层数据合约和逻辑合约,方便后期合约的升级。更多详情,请参见浅谈以太坊智能合约的设计模式与升级方法 。

9.2. 更多合约更新架构和方法

10. 参考资料

原文地址:https://www.cnblogs.com/Cristic-MeiFen/p/12109812.html

时间: 2024-10-09 21:23:21

什么是智能合约的相关文章

使用Go语言与Ethereum智能合约进行交互

尽管最近出现了麻烦,但Ethereum仍然是区块链空间中实现智能合约最重要的系统,而且这种情况似乎不太可能很快改变. 在我看来,技术本身具有很大的潜力,从学术的角度来看是非常有趣的,但正如前面提到的问题,之前展示出来了很多问题,区块链技术,智能合约,特别是带有Solidity的Ethereum生态系统是非常不成熟的,而且也没有准备好的黄金时段/生产用例. 然而,这是学习和了解这种技术的一个很好的机会,能够在应用程序奔溃前提前做准备. 在我之前的一篇文章中,我创建了一个小型应用程序,其中包含一个简

智能合约如何可信的与外部世界交互

区块链应用中,外部世界如何与智能合约交互往往是一个容易被忽视的问题,很多的智能合约应用场景是根据一些外部事件,输出相应的结果,而传统的IT数据交互方式实际上并不能投入真正的工作.例如,按照农产品价格情况来支付投保人赔款的农产品价格险保单.传统IT人员一般认为是如下的流程:智能合约会在预定的时间,从期货交易场所获取农产品价格,然后按照获取的数据采取预设的行动.听起来很简单,但却不可能实现.为什么呢?因为这里存在两个问题,一是共识问题,二是受信任方问题. 一.共识问题 区块链是基于共识的系统,只有在

Solidity编程 四 之 智能合约的结构

Solidity的智能合约和面向对象语言中的类很相似.每个智能合约可以包含的元素有:state变量的定义,方法,函数修改器,事件,结构类型以及枚举类型.同时合约可以继承于另外一个合约 state变量 Solidity里的state变量表示的是永久的存储在合约的存储中. pragma solidity ^0.4.0; contract SimpleStorage { uint storedData; // State variable // ... } 方法 方法是合约里的可执行单元.方法可以被合

区块链入门(5)Truffle 项目实战,Solidity IDE, 智能合约部署

在上一张我们学习了Truffle项目的创建,部署等相关内容,今天我们就来实战一下. 今天我们要做3件事: 1) 学习搭建一个Solidity IDE(Remix). 2) 使用这个Solidity Ide编写一份智能合约. 3) 在我们前面第1,2,3章中部署的私有网络节点集群中部署这个合约,并能够在不同的节点中调用这个合约. Remix,是一个Solidity开发语言的Ide, 它是一款运行在浏览器端的solidity ide,也是官方推荐使用的ide. 另外还有其它的solidiy ide,

以太坊智能合约编程之菜鸟教程

基本概念 了解这些名词是一个不错的开始: 公钥加密系统. Alice有一把公钥和一把私钥.她可以用她的私钥创建数字签名,而Bob可以用她的公钥来验证这个签名确实是用Alice的私钥创建的,也就是说,确实是Alice的签名.当你创建一个以太坊或者比特币钱包的时候,那长长的0xdf...5f地址实质上是个公钥,对应的私钥保存某处.类似于Coinbase的在线钱包可以帮你保管私钥,你也可以自己保管.如果你弄丢了存有资金的钱包的私钥,你就等于永远失去了那笔资金,因此你最好对私钥做好备份.过来人表示:通过

solidity编写智能合约(入门)

一个简单的智能合约 先从一个非常基础的例子开始,不用担心你现在还一点都不了解,我们将逐步了解到更多的细节. 存储 contract SimpleStorage { uint storedData; function set(uint x) { storedData = x; } function get() constant returns (uint retVal) { return storedData; } } 在Solidity中,一个合约由一组代码(合约的函数)和数据(合约的状态)组成

以太坊私链与智能合约部署学习(博主修正篇)—— 第一篇

以太坊(Ethereum)自2017年5月诞生以来,发展迅速.作为了一个小白,博主初步打算上个车,过个瘾.那什么是以太坊?请参看度娘的解释(https://baike.baidu.com/item/%E4%BB%A5%E5%A4%AA%E5%9D%8A/20865117?fr=aladdin). 事物是个新事特,也是个好东西,但资料不多,所以小白博主也只能慢慢找寻资料,慢慢记录自己填坑的过程.  第一篇的主题是:以太坊私链的创建:)    一.配置环境与软件安装 1.安装geth 以下的资料来自

智能合约语言 Solidity 教程系列5 - 数组介绍

写在前面 Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊.智能合约有所了解, 如果你还不了解,建议你先看以太坊是什么 本文前半部分是参考Solidity官方文档(当前最新版本:0.4.20)进行翻译,后半部分对官方文档中没有提供代码的知识点补充代码说明(订阅专栏阅读). 数组(Arrays) 数组可以声明时指定长度,也可以是动态变长.对storage存储的数组来说,元素类型可以是任意的,类型可以是数组,映射类型,结构体等.但对于memory的数组来说.如果作为public

以太坊智能合约开发工具 Truffle 最佳入门指导1

Truffle是以太坊(Ethereum)智能合约开发的瑞士军刀,小巧好用,上手简单. 本篇文章主要展示如何用Truffle 开发第一个Ethereum智能合约. 1.准备工作:(本人针对window环境,如果是mac 或linux可以自行搜索其他教程) a.安装git bash :http://gitforwindows.org/ b.安装npm:https://jingyan.baidu.com/article/a17d528506d7f58098c8f2b0.html 2.安装Truffl

以太坊Crypto Countries加密国家火爆,区块链游戏成智能合约应用探索突破口

继加密猫(CryptoKitties)之后,以太坊上又出现了几款火爆的区块链游戏,它们是CryptoCountries,Crypto-All Stars,CryptoCelebrities和EtherBots. 这几款游戏目前的知名度还不如CryptoKitties,不过相信用不了多久,它们中的某一款肯定会比CryptoKitties更有名. 为什么这么说,因为它们是比CryptoKitties更好玩的区块链游戏,或者说是CryptoKitties的进化版.加密猫(CryptoKitties)是