EOSCrowdsale Contract Inspect

in #eos7 years ago (edited)

这两天 EOS 众筹正火,蹭个热点,技术面分析下 EOS的众筹合约

项目地址

https://github.com/EOSIO/eos-token-distribution

项目目录

├── Dappfile                  // dapphub 出品的智能合约开发工具
├── Makefile
├── bin
│   └── deploy                // 部署脚本
├── lib                       // 第三方依赖
│   ├── ds-auth               // 权限控制
│   ├── ds-exec
│   ├── ds-guard
│   ├── ds-math               // 数学运算
│   ├── ds-test               // 测试框架
│   ├── ds-token              // Token 框架
│   └── gnosis-multisig
└── src                       // 源码
        ├── eos.sol
        └── eos.t.sol

本项目使用 Dapphub 出品的智能合约开发框架 dapp 开发。Dapphub 开源了很多实用的库,大大简化了智能合约的开发成本。

代码分析

变量

DSToken  public  EOS;                  // EOS Token
uint128  public  totalSupply;          // Token 总量
uint128  public  foundersAllocation;   // 开发团队保留份额
string   public  foundersKey;          // 保留份额 Holder Address
uint     public  openTime;             // window 0 开始时间
uint     public  createFirstDay;       // window 0 供应量
uint     public  startTime;            // window 1 开始时间
uint     public  numberOfDays;         // window 总数
uint     public  createPerDay;         // 每日供应量

EOS 的 ICO 规则为:前五天为一个 window 总共发行 2 亿 Token。五天之后,每23个小时为一个window,发行 2 百万 Token。总共发行 10 亿 Token。

构造函数

// src/eos.sol

function EOSSale(
    uint     _numberOfDays,
    uint128  _totalSupply,
    uint     _openTime,
    uint     _startTime,
    uint128  _foundersAllocation,
    string   _foundersKey
) {
        ...

        // window 0 Token 供应量
    createFirstDay = wmul(totalSupply, 0.2 ether);

    // window 1 以及以后的每天 Token 供应量
    createPerDay = div(
        sub(sub(totalSupply, foundersAllocation), createFirstDay),
        numberOfDays
    );
    
    ...
}

wmuldapp_math 提供的一个方法。 dapp_math 中定义两种精度:WADRAY ,分别代表 18 位和 27 位的精度。以 w 开头表示该运算精度为 18 位。

由于 solidity 里面没有 float 类型,想要乘以 0.2 可以通过 乘以 0.2 ether 实现。

初始化 Token

// src/eos.sol

function initialize(DSToken eos) auth {
    ...

    EOS = eos;
    // 设置供应总量
    EOS.mint(totalSupply);

    // 保留 Token 发往 0xb1 这个地址
    EOS.push(0xb1, foundersAllocation);
    keys[0xb1] = foundersKey;
}

mint 方法是 dapp_token 提供,用来设置 Token 供应总量。

开发团队保留的份额被发送到 0xb1 。这个地址是无人可用的,等于丢掉此数量的 Token ,但是又保证 Token 总供应量不变。EOS 团队不需要这个代币,因为在 EOS 上线之后,现在的 Token 会兑换为 EOS 链上的 Token。

window time

// Each window is 23 hours long so that end-of-window rotates
// around the clock for all timezones.
function dayFor(uint timestamp) constant returns (uint) {
        return timestamp < startTime
            ? 0
            : sub(timestamp, startTime) / 23 hours + 1;
}

前五天都是 window 0 。之后每23小时为一个 window 。这么做的好处是让每个 window 的开始时间是滚动的,不同时区的投资者更方便参与。

购买逻辑

function buyWithLimit(uint day, uint limit) payable {
    // 限制时间
    assert(time() >= openTime && today() <= numberOfDays);
    // 最小购买额度 0.01 ether
    assert(msg.value >= 0.01 ether);

    // 购买记录
    userBuys[day][msg.sender] += msg.value;
    dailyTotals[day] += msg.value;
}

Token 兑换

function claim(uint day) {
    // 防止重复兑换
    if (claimed[day][msg.sender] || dailyTotals[day] == 0) {
        return;
    }

    // This will have small rounding errors, but the token is
    // going to be truncated to 8 decimal places or less anyway
    // when launched on its own chain.

    var dailyTotal = cast(dailyTotals[day]);
    var userTotal  = cast(userBuys[day][msg.sender]);
    // 指定 window 的 Token 供应量除以此 window 的 eth 总量
    // 得到兑换比例
    var price      = wdiv(cast(createOnDay(day)), dailyTotal);
    // 兑换比例乘以指定 window 中此用户支付的 eth 数量得到兑换总量
    var reward     = wmul(price, userTotal);
    // 记录兑换标志
    claimed[day][msg.sender] = true;
    // 执行转账
    EOS.push(msg.sender, reward);
}

注册 EOS 共钥

function register(string key) {
    // 众筹结束之后不再提供注册
    assert(today() <=  numberOfDays + 1);
    assert(bytes(key).length <= 64);

    keys[msg.sender] = key;
}

EOS 团队要求投资者在众筹结束之前自行生成 EOS 公私钥,并将生成的共钥注册在合约中。这样在 EOS 正式上线之后,用户可以兑换 EOS 上的 Token 。

ETH 转移

function collect() auth {
    // window 0 不能转移
    assert(today() > 0);
    // 将 eth 转移给调用者
    exec(msg.sender, this.balance);
}

auth 是 dapp_auth 提供的权限控制方法,保证 collect 函数只能被合约 owner 执行。

安全性分析

EOS 众筹合约使用了 dapp_auth 提供的权限控制功能。

// src/eos.sol
// 继承 DSAuth
contract EOSSale is DSAuth {
}

// lib/ds-auth/src/auth.sol
contract DSAuth is DSAuthEvents {
    DSAuthority  public  authority;
    address      public  owner;

    function DSAuth() {
        owner = msg.sender;
    }
}

DSAuth 提供的默认权限控制是基于 owner 的。默认初始化行为将合约创建者设置为 owner 。同时提供 setOwner 函数,可以转移控制权。

function setOwner(address owner_)
    auth
{
    owner = owner_;
    LogSetOwner(owner);
}

setOwner 也需要使用 auth 验证权限。

modifier auth {
    assert(isAuthorized(msg.sender, msg.sig));
    _;
}

function isAuthorized(address src, bytes4 sig)
    internal returns (bool)
{
    // 如果调用者是合约自己,通过。
    if (src == address(this)) {
        return true;
    // 如果调用者是 owner ,通过。
    } else if (src == owner) {
        return true;
    // 如果 authority 未设置,失败。
    } else if (authority == DSAuthority(0)) {
        return false;
    } else {
        // 检查调用者是否有权限。
        return authority.canCall(src, this, sig);
    }
}

在默认行为下 authority 未设置,所以只有 owner 验证。

可以看到,这个合约还可以通过设置自定义的 DSAuthority 来扩展权限验证的逻辑。

contract DSAuthority {
    function canCall(
        address src, address dst, bytes4 sig
    ) constant returns (bool);
}

contract DSAuth is DSAuthEvents {
    function setAuthority(DSAuthority authority_)
        auth
    {
        authority = authority_;
        LogSetAuthority(authority);
    }
}

我们只需要在自己的合约中继承 DSAuthority ,实现 canCall 方法。
然后将实例传入 setAuthority 就可以设置特定的权限逻辑。

商业价值分析

EOS 的主要特性:

  • 免费试用
  • 合约可以升级
  • 出块速度快,达到平均 1.5s
  • 串行/并行性能强,可以达到百万级交易处理规模
  • 账户系统,权限系统等
  • 可插拔的合约虚拟机

EOS 更方便创建大型高频的分布式应用。

Sort:  

Nice

Very cool post, I can learn a lot of new things from you :D

What is this? xd :D

Postnya very useful and hope you are lucky for future in this steemit :)
But do not forget the upvote on my blog too :D
#Thank's

Toooo long to read everything :D

Thanks for sharing, nice

強大的技術貼

有深度的技术贴,棒棒哒