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
)
其流程大致为:
将库地址
存入Dispatcher Storage
-> 将Dispatcher Storage
传给Dispatcher
->
Main Contract
链接LibInterface
到Dispatcher
-> 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
链接LibInterface
到Dispatcher
上,所以实际上是由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
替代直接调用
在实现上,重点实现Dispatcher
和DispatcherStorage
相关功能。
本技术最适宜的环境应该是提供公共基础类库或者由经常修改的业务逻辑代码组成的库, 因此,可以设计一个统一类库平台
,所有合约的开发必须使用本平台中的类,必须通过Dispatcher
调用 平台可以包括多个不同的Dispatcher
,所有Dispatcher
均使用同一个DispatcherStorage
, 所有类的代码改动由DispatcherStorage
统一管理,这要求DispatcherStorage
必须存在对应的多个库合约
地址:
修改自示例代码中,其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
代码结构可如下:
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