Solidity Proxy 技术介绍

简介

Solidity Proxy是为了解决目前存在于Solidity开发实践中的问题而提出的技术设计 它由Zeppelin提出,主要是为了解决以下问题:

  • 链上代码重复率高和部署大量重复代码带来的额外开销

  • 链上代码的不可修改带来的巨大代码更新与安全修复成本

对于基础库来说,Solidity Proxy对于提升代码安全性和可维护性具有重要意义 在不使用代理技术的情况下,一旦基础库代码发生变成,业务方必须重新部署库合约 以 DAO 事件为例,以太坊被迫采用硬分叉(Hard Fork)方案以回滚 DAO 导致的损失,DAO 也因此瓦解

设计与实现

项目示例地址:https://github.com/maraoz/solidity-proxy/

在本项目中,存在以下内容:

  • TheContract.sol:一个业务合约的示例

  • Dispatcher.sol:分发器,核心组件,用于完成库合约的代理

  • DispathcerStorage.sol:库合约地址的修改保存器

  • LibInterface.sol:库函数接口示例

  • Example.sol:库合约(在 tests 中)

本项目的目标是在TheContract中可以通过Solidity Proxy调用Example代码, 除此之外,Example代码可以自由更新

实现原理

Solidity Proxy的核心思想是使用普通的合约,但把它当做库来调用(用delegatecall来取代call

Solidity Proxy 技术介绍_第1张图片

其流程大致为:

库地址存入Dispatcher Storage -> 将Dispatcher Storage传给Dispatcher ->

Main Contract链接LibInterfaceDispatcher -> Main Contract调用Dispatcher ->

Dispatcher调用库合约

在这种流程下,如果需要更新库合约,只需要修改Dispatcher Storage中的库合约地址即可,一般会在DispatcherStorage中设置修改地址的特权方法,以Ownable进行修饰。

巧妙的设计

LibInterface示例代码中可以看到,LibInterface定义了各种接口

pragma solidity ^0.4.8;

library LibInterface {
  struct S { uint i; }

  function getUint(S storage s) public constant returns (uint);
  function setUint(S storage s, uint i) public;
}

TheContract示例代码中,业务合约使用了相应的接口

pragma solidity ^0.4.8;
​
import "./LibInterface.sol";
​
contract TheContract {
  LibInterface.S s;
​
  using LibInterface for LibInterface.S;
​
  function get() public constant returns (uint) {
    return s.getUint();
  }
​
  function set(uint i) public {
    return s.setUint(i);
  }
}

由于TheContract链接LibInterfaceDispatcher上,所以实际上是由Dispatcher完成的工作, 在Dispatcher示例代码中可以看到,Dispatcher并未定义除function ()以外的任何函数, 这意味着所有传向Dispatcher的调用,都会因函数未定义而调用function (),而function ()实际上就是代理

pragma solidity ^0.4.8;
​
import "./DispatcherStorage.sol";
​
contract Dispatcher {
  function() public {
    // some code here
    assembly {
      // assembly code here
    }
  }
}

安全解决方案

这种技术使得链上的代码也能进行动态升级和更新,增加了灵活性和安全性。

如果将本技术作为安全模块使用,那么应该完成以下步骤:

  • 修改需要使用本技术的库合约,并开发相应接口

  • 在开发业务合约时,使用Dispatcher替代直接调用

在实现上,重点实现DispatcherDispatcherStorage相关功能。

架构设计

本技术最适宜的环境应该是提供公共基础类库或者由经常修改的业务逻辑代码组成的库, 因此,可以设计一个统一类库平台,所有合约的开发必须使用本平台中的类,必须通过Dispatcher调用 平台可以包括多个不同的Dispatcher,所有Dispatcher均使用同一个DispatcherStorage, 所有类的代码改动由DispatcherStorage统一管理,这要求DispatcherStorage必须存在对应的多个库合约地址:

Solidity Proxy 技术介绍_第2张图片

Dispatcher

修改自示例代码中,其Dispatcher代码可如下:

pragma solidity ^0.4.8;
​
import "./DispatcherStorage.sol";
​
contract Dispatcher {
  function() public {
    DispatcherStorage dispatcherStorage = DispatcherStorage(0x1111222233334444555566667777888899990000);
    // xxx 为库名称
    address target = dispatcherStorage.libs("xxx");
​
    assembly {
      calldatacopy(0x0, 0x0, calldatasize)
      // 发起调用
      let success := delegatecall(sub(gas, 10000), target, 0x0, calldatasize, 0, 0)
      // 设置返回数据大小
      let retSz := returndatasize
      returndatacopy(0, 0, retSz)
      switch success
      case 0 {
        revert(0, retSz)
      }
      default {
        return(0, retSz)
      }
    }
  }
}

其中,0x1111222233334444555566667777888899990000应当被替换为DispatcherStorage的地址

DispatcherStorage

修改自示例代码,DispatcherStorage代码结构可如下:

pragma solidity ^0.4.8;
​
import "zeppelin-solidity/contracts/ownership/Ownable.sol";
contract DispatcherStorage is Ownable {
​
  mapping(string => address) public libs;
​
  function DispatcherStorage(string name, address newLib) public {
    replace(name, newLib);
  }
​
  function replace(string name, address newLib) public onlyOwner /* onlyDAO */ {
    libs[name] = newLib;
  }
}

DispatcherStorage的所有者可以用于更新库合约的地址,从而实现对链上代码的升级

参考资料

http://www.coindesk.com/ethereum-executes-blockchain-hard-fork-return-dao-investor-funds

https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd

你可能感兴趣的:(区块链安全)