【CITA 智能合约开发系列】智能合约安全性

智能合约安全性

参考 [https://www.bilibili.com/video/av58299098]

因为智能合约是不可逆的,所以他的交易一旦形成,是无法回退的。在这种情形下,智能合约的安全性尤为重要。以下先介绍几种合约常见的合约安全性隐患,然后会给出改善他们的方法。

智能合约溢出型漏洞

16bit 整数:0x0000,0x0001,0x0002,…,0xfffd,0xffff

0x8000 + 0x8000 = 0x10000 = 0x0000 = 0

0xffff + 0x0003 = 0x10002 = 0x0002 = 2

0x0000 - 0x0001 = 0xffff = -1 = 65535

function transferMulti(address[] _to, uint256[] _value) public returns (uint256 amount) {	
    require(_to.length == _value.length);	
    for(uint8 j; j<len; j++) {		
    amount += _value[j];	
    }	
    require(balanceOf[msg.sender] >= amount);	
    for(uint8 i ; i < len; i++) {		
        address _toI = _to[i];		
        uint256 _valueI = _value[i];		
        balanceOf[_toI] += _valueI;		
        balanceOf[msg.sender] -= _valueI;		
        Transfer(msg.sender, _tiI, _valueI);	
    }
}

这个函数想要做到的是把 msg.sender 在合约中的 token 转给多个人, amount += _value[j]; 这个操作会存在溢出的风险,如果在加的时候出现状况 amount = 0x8000 + 0x8000 = 0,那么在后面一步的判断 require(balanceOf[msg.sender] >= amount);中会出现的实际判断的是 balanceOf[msg.sender] >= 0 那么可以从空的账户中把钱转出。

代码注入漏洞

function approveAndCallcode(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
    allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);	
    if(!_spender.call(_extraData)) {
        revert();
    }	
    return true;
}

可以把这个合约本身拥有的代币偷走转给别的用户,因为对于extraData 来说,自由度非常高, _spender.call(_extraData) 可以是任何一个地址调用任何一个函数。

itchyDAO in MakerDAO 投票系统

这个主要是以一个比较复杂的例子来给学员讲合约中函数调用需要知道的地方,暗示智能合约还是比较难以把控的,需要多学习

以下是一个在 MakerDAO 中的投票系统,在这个投票系统中,一个sender 需要根据自己的权重对一个提案进行投票。

function etch(address[] memory yays) public note returns (bytes32 slate){	
    require(yays.length <= MAX_YAYS);	requireByOrderSet(yays);		
    bytes32 hash = keccak256(abi.encodePacked(yays));	
    emit Etch(hash);	
    return hash;
}
function vote(address[] memory yays) public returns (bytes32){	
    bytes32 slate = etch(yays);	vote(slate);	
    return slate;
}
function vote(bytes32 slate) public note {	
    uint weight = deposit[msg.sender];	
    subWeight(weight, vote[msg.sender]);	
    votes[msg.sender] = slate;	
    addWeight(weight, vote[msg.sender]);	
}

以下是投票函数,在投票以后把票数进行 addWeight 和 subWeight 操作。

function addWeight(uint weight, bytes32 slate) internal {	
    address[] storage yays = slates[slate];	
    for(uint i = 0; i < yays.lenght; i++) {		
        approvals[yays[i]] = add(approvals[yays[i]], weight);	
    }
}
function subWeight(uint weight, bytes32 slate) internal {	
    address[] storage yays = slates[slate];	
    for(uint i = 0; i < yays.length; i++) {		
        approvals[yays[i]] = sub(approvals[yays[i]], weight);
    }
}

最后一步是在 lock 一种币,在 lock 以后可以进行投票操作,在投票完成以后,可以 free 从而退回自己的币。

function lock(uint wad) public note{	
    GOV.pull(msg.sender,wad);	
    IOU.mint(msg.sender, wad);	
    deposits[msg.sender] = add(deposits[msg.sender], wad);	
    addWeight(wad, votes[msg.sender]);
}

function free(uint wad) public note{	
    deposits[msg.sender] = sub(deposits[msg.sender], wad);	
    subWeight(wad, votes[msg.sender]);	
    IOU.burn(msg.sender, wad);	
    GOV.push(msg.sender, wad);
}

系列文章:

  1. 智能合约定义
    智能合约的历史和定义, 介绍在cita上使用智能合约
  2. 智能合约开发
    智能合约的基础语法介绍,cita ide编辑器介绍,如何cita开发Dapp
  3. 智能合约安全性(本文)
    智能合约一些高级技巧
  4. 智能合约场景
    两种智能合约代币ERC20,ERC721的标准接口讲解
2赞