Solidity智能合约的库驱动开发

in #ethereum7 years ago

原文: https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736

本文关于如何通过使用库来开发更多模块化,可重复使用和优雅的智能合约系统的综合评估.

solidity是一个受限的语言

  • 字节码在每个节点上运行, 需要验证执行的有效性;
  • 因此, 尽管EVM是图灵完备, 但计算比较昂贵,因为过多的计算会降低以太坊网络速度;
  • 没有标准的库被开发出来. 比如数组和字符串操作;
  • 不能从外界获取数据,除非通过一个交易(oracle);
  • 合约部署后无法升级;

什么是库

在solidity中,库是一种不同类型的合约,没有存储,不拥有以太币. 代码可以被其他合约调用,而不需要重新部署.

这样可以节省大量的gas(因此不会用重复的代码污染区块链),因为同样的代码不需要一遍又多的部署,而不同的合同只能依赖于相同的已经部署的库。

事实上,多个合同依赖于确切的代码段,可以为更安全的环境创造条件。

库不允许可支付(payable)的函数, 没有fallback函数,
库的定义: library C {}
库的调用通过DELEGATECALL, 即不切换上下文;
调用合约A的a()函数, 返回的是合约地址,而不是库的地址; 包括msg的属性:

library C {
    function a() returns (address) {
        return address(this);
    }
}

contract A {
    function a() constant returns (address) {
        return C.a();
    }
}

库的连接

在合约A部署的时候,会保留一个库地址:0073__C_____________________________________630dbe671f, 0dbe671f表示a()函数.
链接的时候,会替换合约A中该地址为真实的库C的地址.

库升级

因为在执行链接时 地址已经写死, 所以无法升级, 但是通过另一种方式可以达到升级的目的,见这里

使用结构体和方法

库可以修改链接合约的状态, 通过传递一个"stroage"的引用. 有个语法糖using.
声明库内函数的调用的第一个参数, 在真正调用时, 可以省略该参数.

library CounterLib {
    struct Counter { uint i; }

    function incremented(Counter storage self) returns (uint) {
        return ++self.i;
    }
}

contract CounterContract {
    using CounterLib for CounterLib.Counter;

    CounterLib.Counter counter;

    function increment() returns (uint) {
        return counter.incremented();
    }
}

事件和库

库没有事件和log, 在库中的事件触发后, 在外部需要监听合约的事件,才能捕获.

library EventEmitterLib {
    function emit(string s) {
        Emit(s);
    }
    
    event Emit(string s);
}

contract EventEmitterContract {
    using EventEmitterLib for string;
    
    function emit(string s) {
        s.emit();
    }
    
    event Emit(string s);
}

实现一个ERC20的lib

library SafeMathLib {
  function times(uint a, uint b) returns (uint) {
    uint c = a * b;
    assert(a == 0 || c / a == b);
    return c;
  }

  function minus(uint a, uint b) returns (uint) {
    assert(b <= a);
    return a - b;
  }

  function plus(uint a, uint b) returns (uint) {
    uint c = a + b;
    assert(c>=a && c>=b);
    return c;
  }

  function assert(bool assertion) private {
    if (!assertion) throw;
  }
}
import "../SafeMathLib.sol";

library ERC20Lib {
  using SafeMathLib for uint;

  struct TokenStorage {
    mapping (address => uint) balances;
    mapping (address => mapping (address => uint)) allowed;
    uint totalSupply;
  }

  event Transfer(address indexed from, address indexed to, uint value);
  event Approval(address indexed owner, address indexed spender, uint value);
  
  function init(TokenStorage storage self, uint _initial_supply) {
    self.totalSupply = _initial_supply;
    self.balances[msg.sender] = _initial_supply;
  }
  
  function transfer(TokenStorage storage self, address _to, uint _value) returns (bool success) {
    self.balances[msg.sender] = self.balances[msg.sender].minus(_value);
    self.balances[_to] = self.balances[_to].plus(_value);
    Transfer(msg.sender, _to, _value);
    return true;
  }

  function transferFrom(TokenStorage storage self, address _from, address _to, uint _value) returns (bool success) {
    var _allowance = self.allowed[_from][msg.sender];

    self.balances[_to] = self.balances[_to].plus(_value);
    self.balances[_from] = self.balances[_from].minus(_value);
    self.allowed[_from][msg.sender] = _allowance.minus(_value);
    Transfer(_from, _to, _value);
    return true;
  }

  function balanceOf(TokenStorage storage self, address _owner) constant returns (uint balance) {
    return self.balances[_owner];
  }

  function approve(TokenStorage storage self, address _spender, uint _value) returns (bool success) {
    self.allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
  }

  function allowance(TokenStorage storage self, address _owner, address _spender) constant returns (uint remaining) {
    return self.allowed[_owner][_spender];
  }
}
import './ERC20Lib.sol';

contract StandardToken {
   using ERC20Lib for ERC20Lib.TokenStorage;

   ERC20Lib.TokenStorage token;

   string public name = "SimpleToken";
   string public symbol = "SIM";
   uint public decimals = 18;
   uint public INITIAL_SUPPLY = 10000;

   function StandardToken() {
      token.init(INITIAL_SUPPLY);
   }

   function totalSupply() constant returns (uint) {
     return token.totalSupply;
   }

   function balanceOf(address who) constant returns (uint) {
     return token.balanceOf(who);
   }

   function allowance(address owner, address spender) constant returns (uint) {
     return token.allowance(owner, spender);
   }

   function transfer(address to, uint value) returns (bool ok) {
     return token.transfer(to, value);
   }

   function transferFrom(address from, address to, uint value) returns (bool ok) {
     return token.transferFrom(from, to, value);
   }

   function approve(address spender, uint value) returns (bool ok) {
     return token.approve(spender, value);
   }

   event Transfer(address indexed from, address indexed to, uint value);
   event Approval(address indexed owner, address indexed spender, uint value);
 }

这样,ERC20LibSafeMathLib 只要部署一次, 其他合约都可以链接并使用.

Sort:  

Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:
https://pastebin.com/BhqRb7zN