美文网首页
Solidity智能合约总结

Solidity智能合约总结

作者: AI科技智库 | 来源:发表于2020-09-03 16:10 被阅读0次

    1 智能合约的概念与演变

    1.1 智能合约的概念

    智能合约,所谓“合约”就是条文、合同一类的东西,里面记录了发生的条件与对应执行的条款,以支持确权等操作;所谓"智能",就意味着自动化、可编程。
    智能合约就是可编程的合同,也可以理解为一段自动执行的条文合同,在计算机中,就是一段自动执行的程序片段。

    1.2 智能合约与区块链

    智能合约上世纪就被提出,为什么智能合约与区块链却产生了如此紧密的关联?因为区块链可以保证智能合约的不可篡改。智能合约的本体是一份代码,非常容易被篡改,如何为其提供强力的存储介质就成了问题。这正好是区块链擅长解决的。同时智能合约也在反哺着区块链,它极大地扩展了区块链的业务场景。

    1.3 以太坊的诞生

    比特币:用户通过脚本代码来定制一些内容,例如如何解锁一笔资金。这些脚本代码会随着交易一起保存,从而享有不可篡改的特质。所以从某种角度来说,这些脚本也可看作智能合约。可是脚本语言并不是图灵完备,难以实现复杂的业务逻辑。
    以太坊:以太坊中智能合约的语言是Solidity,它是图灵完备且较为上层的语言,极大地扩展了智能合约的能力范畴,降低了智能合约编写难度。

    1.4 智能合约的现状

    智能合约就是一段代码。相比常规代码,智能合约具有许多差别与限制,例如:

    • 单线程执行
    • 代码执行会消耗资源,不能超出资源限制
    • 目前难以获取链外数据
    • 其他限制,如TPS

    1.5 联盟链的智能合约

    Hyperledger Fabric:基于容器运行链码,可以支持多语言的链码运行。
    FISCO-BCOS:基于EVM虚拟机运行链码,采用Solidity作为智能合约语言。

    2 Solidity的基础特性

    通过一个简单的合约示例,来了解智能合约的代码结构。

    pragma solidity ^0.4.25;
    contract Sample{
        address private _admin;
        uint private _state;
    
        modifier onlyAdmin(){
            require(msg.sender == _admin, "you are not admin");
            _;
        }
    
        event setState(uint value);
    
        constructor() public{
            _admin = msg.sender;
        }
    
        function setstate(uint value) public onlyAdmin{
            _state = value;
            emit setState(value);
        }
    
        function getstate() public view returns (uint){
            return _state;
        }
    }
    
    • 状态变量 - _admin, _state,这些变量会被永久保存,也可以被函数修改
    • 构造函数 - 用于部署并初始化合约
    • 事件 - SetState, 功能类似日志,记录了一个事件的发生
    • 修饰符 - onlyAdmin, 用于给函数加一层"外衣"
    • 函数 - setState, getState,用于读写状态变量

    状态变量
    状态变量是合约的骨髓,它记录了合约的业务信息。用户可以通过函数来修改这些状态变量,这些修改也会被包含到交易中;交易经过区块链网络确认后,修改即为生效。
    构造函数
    构造函数用于初始化合约,它允许用户传入一些基本的数据,写入到状态变量中。构造函数不支持重载
    函数
    函数被用来读写状态变量。对变量的修改将会被包含在交易中,经区块链网络确认后才生效。生效后,修改会被永久的保存在区块链账本中。
    view修饰符,表示了该函数不会修改任何状态变量。
    pure修饰符,表示了该函数是纯函数,连状态变量都不用读,函数的运行仅仅依赖于参数。
    事件
    事件类似于日志,会被记录到区块链中,客户端可以通过web3订阅这些事件。
    修饰符
    修饰符是合约中非常重要的一环。它挂在函数声明上,为函数提供一些额外的功能,例如检查、清理等工作。修饰符onlyAdmin要求函数调用前,需要先检测函数的调用者是否为函数部署时设定的那个管理员

    3 Solidity的高级特性

    函数的可见类型
    external:推荐只向外部暴露的函数使用。
    public:权限最大,可供外部、子合约、合约内部访问。
    internal:只能是内部访问(即从当前合约内部或从它派生的合约访问)
    private :仅在当前定义它们的合约中使用,并且不能被派生合约使用。
    状态常量
    状态常量是指被声明为constant的状态变量,那么该变量值只能为编译时确定的值,无法被修改,不会给变量预留储存空间。
    面向对象之重载
    重载是指合约具有多个不同参数的同名函数。对于调用者来说,可使用相同函数名来调用功能相同,但参数不同的多个函数。
    面向对象之继承
    Solidity使用"is"作为继承关键字。

    contract A {
    }
    contract B is A {
    }
    

    继承的合约B可以访问被继承合约A的所有非private函数和状态变量。
    继承的底层实现原理为:当一个合约从多个合约继承时,在区块链上只有一个合约被创建,所有基类合约的代码被复制到创建的合约中。
    面向对象之抽象类和接口
    接口使用关键字interface

    interface Vehicle {
        //抽象方法
        function brand() public returns (bytes32);
    }
    

    4 Solidity的设计模式

    4.1 安全性(Security)

    智能合约编写,首要考虑的就是安全性问题。比如,外部调用可通过恶意回调,使代码被反复执行,从而破坏合约状态,这种攻击手法就是著名的Reentrance Attack(重放攻击)。下面将介绍两个可有效解除此类攻击的设计模式。
    Checks-Effects-Interaction - 保证状态完整,再做外部调用
    这个模式要求合约按照Checks-Effects-Interaction的顺序来组织代码。它的好处在于进行外部调用之前,Checks-Effects已完成合约自身状态所有相关工作,使得状态完整、逻辑自洽,这样外部调用就无法利用不完整的状态进行攻击了。
    Mutex - 禁止递归
    Mutex模式也是解决重放攻击的有效方式。它通过提供一个简单的修饰符来防止函数被递归调用.

    contract Mutex {
        bool locked;
        modifier noReentrancy() {
            //防止递归
            require(!locked, "Reentrancy detected");
            locked = true;
            _;
            locked = false;
        }
        //调用该函数将会抛出Reentrancy detected错误
        function some() public noReentrancy{
            some();
        }
    }
    

    4.2 可维护性(Maintaince)

    区块链中合约一旦部署,就无法更改。当需要更改合约逻辑时将面临很多问题,例如合约上的数据怎么处理,依赖的其他合约怎么办?需要将变化的事物和不变的事物相分离,以阻隔变化在系统中的传播。所以,设计良好的代码通常都组织得高度模块化、高内聚低耦合。利用这个经典的思想可解决上面的问题。
    Data segregation - 数据与逻辑相分离
    该模式要求一个业务合约和一个数据合约:数据合约只管数据存取,这部分是稳定的;而业务合约则通过数据合约来完成逻辑操作。
    Satellite - 分解合约功能
    Satellite模式运用单一职责原则解决上述问题,提倡将合约子功能放到子合约里,每个子合约(也称为卫星合约)只对应一个功能。当某个子功能需要修改,只要创建新的子合约,并将其地址更新到主合约里即可,其余功能不受影响。
    Contract Registry - 跟踪最新合约
    该设计模式下,会有一个专门的合约Registry跟踪子合约的每次升级情况,主合约可通过查询此Registyr合约取得最新的子合约地址。卫星合约重新部署后,新地址通过Registry.update函数来更新。
    Contract Relay - 代理调用最新合约
    该设计模式所解决问题与Contract Registry一样,即主合约无需暴露维护性接口就可调用最新子合约。该模式下,存在一个代理合约,和子合约享有相同接口,负责将主合约的调用请求传递给真正的子合约。卫星合约重新部署后,新地址通过update函数来更新。

    4.3 生命周期(Lifecycle)

    默认情况下,一个合约的生命周期近乎无限——除非赖以生存的区块链被消灭。但很多时候,用户希望缩短合约的生命周期。
    Mortal - 允许合约自毁
    字节码中有一个selfdestruct指令,用于销毁合约。所以只需要暴露出自毁接口即可

    contract Mortal{
        //自毁
        function destroy() public{
            selfdestruct(msg.sender);
        } 
    }
    

    Automatic Deprecation - 允许合约自动停止服务
    如果你希望一个合约在指定期限后停止服务,而不需要人工介入,可以使用Automatic Deprecation模式。

    
    contract AutoDeprecated{
     
        uint private _deadline;
     
        function setDeadline(uint time) public {
            _deadline = time;
        }
     
        modifier notExpired(){
            require(now <= _deadline);
            _;
        }
     
        function service() public notExpired{ 
            //some code    
        } 
    }
    

    4.4 权限(Authorization)

    许多管理性接口,如果任何人都可调用会造成严重后果,所以,一套保证只有特定账户能够访问的权限控制设计模式显得尤为重要。
    Ownership
    对于权限的管控,可以采用Ownership模式。该模式保证了只有合约的拥有者才能调用某些函数。首先需要有一个Owned合约:

    contract Owned{
        address public _owner;
        constructor() {
            _owner = msg.sender;
        }    
        modifier onlyOwner(){
            require(_owner == msg.sender);
            _;
        }
    }
    

    4.5 行为控制(Action And Control)

    Oracle - 读取链外数据
    链上的智能合约生态相对封闭,无法获取链外数据,影响了智能合约的应用范围。
    获取外部数据会通过名为Oracle的链外数据层来执行。当业务方的合约尝试获取外部数据时,会先将查询请求存入到某个Oracle专用合约内;Oracle会监听该合约,读取到这个查询请求后,执行查询,并调用业务合约响应接口使合约获取结果。
    下面定义了一个Oracle合约:

    contract Oracle {
        address oracleSource = 0x123; // known source
     
        struct Request {
            bytes data;
            function(bytes memory) external callback;
    }
     
        Request[] requests;
        event NewRequest(uint);
        modifier onlyByOracle() {
            require(msg.sender == oracleSource); _;
        }
     
        function query(bytes data, function(bytes memory) external callback) public {
            requests.push(Request(data, callback));
            emit NewRequest(requests.length - 1);
        }
     
        //回调函数,由Oracle调用
        function reply(uint requestID, bytes response) public onlyByOracle() {
            requests[requestID].callback(response);
        }
    }
    

    业务方合约与Oracle合约进行交互:

    contract BizContract {
        Oracle _oracle;
     
        constructor(address oracle){
            _oracle = Oracle(oracle);
        }
     
        modifier onlyByOracle() {
            require(msg.sender == address(_oracle)); 
            _;
        }
     
        function updateExchangeRate() {
            _oracle.query("USD", this.oracleResponse);
        }
     
        //回调函数,用于读取响应
        function oracleResponse(bytes response) onlyByOracle {
        // use the data
        }
    }
    

    参考摘抄

    https://blog.csdn.net/FISCO_BCOS/article/details/105619266

    相关文章

      网友评论

          本文标题:Solidity智能合约总结

          本文链接:https://www.haomeiwen.com/subject/girnsktx.html