status合约分析
status产生背景
传统社交网络一般有3个角色:
- owner: 网络拥有者;目标是吸引用户, 把用户留在平台;
- advertiser: 卖方; 购买用户数据,提供服务,owner可以提取价值;
- user: 用户;只要用这个网络, 就必须选择相信owner和advertiser;
现在出现了社交经济网络,用户都是利益相关者, 自然形成有利于所有参与者的社交网络;
Status基于以太坊平台,用SNT代币来创建利益相关者的网络,提供不可逆的自由交易,点对点支付,加密点对点通信的入口; 在该网络中的服务都需要SNT代币来购买.
在status网络中, 任何给定的区块下, 可以简单地生成和父token相同余额的新token,目的是保留对早期支持者的公平,也不随着时间推移和项目演变,对测试新的SNT代币模式加以限制.
SNT应用举例
- 投票token, 当你投票时, 花费相应的token,可以使一个token代表一票; 比如投票选择软件方向的决策;
- github赏金机器人, 任何人对github问题创建赏金, 如果开发者实现了功能并提交到代码库, 赏金就给开发者. (可能会参考aragon,boardroom的项目管理).
- 用SNT创建半公开群聊, 比如活动组织者C用bancor发行一种token,充当音乐节门票,并允许持有者加入群聊;
- 购买推送服务;
- 花SNT注册用户名; DApp开发者B只授权status的注册用户可以进入讨论版发帖; (需要依赖uport);
- 表情服务,持有SNT作为卖家提供表情, 在表情市场售卖,用户可以花SNT购买;
status合约关系图
MiniME token对token的扩展
token 克隆
任何人可以从任何的token克隆一个token, 初始发行是父token在克隆开始block中的token数量.
function createCloneToken(
string _cloneTokenName,
uint8 _cloneDecimalUnits,
string _cloneTokenSymbol,
uint _snapshotBlock,
bool _isConstant
) returns(address) {
token余额查询
查询token发行量和每个人的余额变化的历史:
function totalSupplyAt(uint _blockNumber) constant returns(uint)
function balanceOfAt(address _holder, uint _blockNumber) constant returns (uint)
token的控制器
控制token销售,销毁, 交易冻结.
function generateTokens(address _holder, uint _value) onlyController
function destroyTokens(address _holder, uint _value) onlyController
function enableTransfers(bool _transfersEnabled) onlyController
status中的token
SNT分配
29%用于储备(multisig)
20%来自现场团队和创始人(multisig,2年归属合同,6个月悬崖)
剩余的51%在初始贡献期本身和SGT之间分配,SGT <=总供给的10%。
SGT 白名单
地址可以列入白名单,并保证参与达到最大数量,忽略动态上限。发送ETH到智能合约地址应该没有什么不同,不管是否列入白名单。状态创世SGT是一个MinimeToken,总共提供5亿个,不超过总供应量的10%。即。如果分配了2.5亿SGT,则SGT将占总供应量的5%。 SGT可以在贡献期后兑换为SNT。
动态上限
一个曲线,指定特定块间隔的一系列隐藏的上限,可以在贡献期间随时显示。在完成众筹之前,必须披露整个曲线。只要整个曲线已经被揭示,可以在曲线期间的任何时刻完成众筹。白名单地址忽略上限。
SNT 杂项
SNT在投放期后1周不可转让,并以每1个ETH兑换10,000 SNT进行铸造。
多重签名钱包
社区钱包
签名者有5个; 需要3个以上签名同意;
社区钱包拥有SNTtoken,并加入广泛的社区确保status的使命得以实施。
管理29%的保留SNT的分配;
解决status研究与开发中的假设"死锁"问题,以确保资源不会被锁定,项目可以继续进行。
开发和研究钱包
签名者有3个; 需要2个以上签名同意;
持有研究与开发用的以太基金和SNTtoken。
合约流程分析
1. 合约部署和初始化
- 按循序部署合约,记录合约地址;
- 设置SNT的controller为statusContribution合约;
- 设置3条隐藏曲线,上限分别是1000ether,21000ether,61000ether。
- 执行statusContribution.initialize(...);
- 保存前面部署到相关的合约地址到变量中;其中包括接受用户ether的账户地址等。
2. 用户购买SNT(ICO)
- 有2种途径购买SNT。
- 用户向statusContribution合约地址转账;
- 用户向 SNT合约地址转账,(通过SNT的controller购买,即向statusContribution合约);
- 用户向合约地址转账, 转账金额进入该合约的balance中;
- 然后执行该合约的fallback函数,fallback通过proxyPayment(msg.sender)将转账金额转移到指定的账户中;
- 如果是白名单用户,根据该白名单用户配置的上限购买SNT(buyGuaranteed);
- 普通用户通过buyNormal购买;
- 检查gasprice,不能大于50GWei;防止有用户出高价“插队”;
- 也不允许合约用户进行购买的行为;
- 先控制购买频率(间隔至少100个block);
- 计算本次可以购买的最大金额(单位wei);
- 如果最大金额比用户转账的金额还大,那么全部金额用来购买SNT;
- 否则只能最多购买最大金额,剩下的退还给用户;
- 计算可以购买的SNT(wei*10000);
- SNT合约发售token给该用户;
- 购买资金转移到指定账户上;
3. 动态上限合约分析
3.1 setHiddenCurves 设置隐藏曲线
- 实现将计算好的曲线hash保存在curves.hash中;
- 一共有3条曲线,7个“占位”用的hash, 共10个hash;
3.2 revealMulti 揭露一条曲线
- 需要输入该曲线上限,斜率因子,允许单次购买的最小金额, salt;
- 计算hash,如果是待揭露的曲线的hash, 那么揭露该曲线;
- 揭露的信息保存在curves数组中, 并更新揭露的曲线条数;
3.3 moveTo 移动曲线
- 只有创建者才有权移动曲线;
- 移动必须是在已揭露的曲线上移动,并且一次只能向前移动1条;
3.4 toCollect 计算这次交易允许用户购买的最大的金额(单位wei)
- 需要输入当前已经筹集的资金数(单位wei)
- 如果没有曲线揭露, 那么允许购买的资金也是0;
- 如果当前已筹集金额的大于当前曲线上限
- 先移到下一条曲线;
- 如果下一条曲线还没有揭露, 不允许用户再购买(返回0);
- 如果下一条已揭露,但是当前已筹集金额比下一条的上限还大,那么也不允许购买;
- 否则计算当前可以购买的绝对金额(上限减去已募集资金数);
- 再计算真正允许用户购买的金额(绝对金额除以30);
- 在已经筹集金额接近上限时,计算出来的真正可购买金额可能会比允许购买的最小金额还要小,那么根据计算出的绝对金额和允许购买的最小金额比较,如果前者大于后者,那么返回允许购买的最小金额, 后者返回计算出来的绝对金额。
3.5 动态上限曲线的使用
- 先计算曲线hash,并保管好salt;3条曲线上限依次变大(1000,21000,61000);
- 把hash保存到合约中;
- 创建者选择合适的时机,依次揭露3条曲线;
- 筹集资金达到上限后,会自动触发进入下一跳已揭露的曲线;
- 如果募集参与的人少,创建者可以手动移动到下一条曲线, 1. 提高单次购买上限,吸引用户购买;
- 在用户每笔购买交易中动态计算本次可购买的最大金额,然后根据用户支付的以太币数量,如果超过最大金额, 只能购买最大金额, 剩下的返还给用户,否则全部以太币用于购买SNT。
4 ICO完成的处理
- 只有合约创建者才能提前结束ICO,或者在ICO截止时间之后结束;
- 曲线必须全部都已揭露才能结束;
- 如果全部曲线已揭露,并且还没有到结束的时间,那么只有当众筹资金超过最后一天曲线的上限,才允许结束ICO。
- 发售预留的token;
- 计算实际SGT的token数, 如果超过10%,则限制上限为10%,否则按照实际比例发售;剩下的token公开发售;
- 公开发售 = 41% + (10% - % to SGT holders)
- 发售给开发者的20%token;
- 发售预留token,占29%;
- 计算实际SGT的token数, 如果超过10%,则限制上限为10%,否则按照实际比例发售;剩下的token公开发售;
- 把SNT的controller切为SNTplaceHolder合约地址;
SNT的交易
SNT合约实现标准的ERC20接口,交易所通过标准接口进行用户间交易。
contract ERC20Token {
///发行的token总量
uint256 public totalSupply;
//owner的余额
function balanceOf(address _owner) constant returns (uint256 balance);
/// @notice send `_value` token to `_to` from `msg.sender`
function transfer(address _to, uint256 _value) returns (bool success);
/// @notice 交易的发起者发送 `_value` token 从`_from` 给 `_to` .
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
/// @notice `msg.sender` 授权 `_spender` 可以花最多的 `_value` tokens
function approve(address _spender, uint256 _value) returns (bool success);
//返回允许spender可以花owner的token数目
function allowance(address _owner, address _spender) constant returns (uint256 remaining);
//token发送事件
event Transfer(address indexed _from, address indexed _to, uint256 _value);
//授权事件
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
status合约安全举例
根据https://www.kingoftheether.com/contract-safety-checklist.html 分析status合约的安全防御:
异常数据输入处理
) public onlyOwner {
// Initialize only once
require(address(SNT) == 0x0);
SNT = MiniMeToken(_snt);
require(SNT.totalSupply() == 0);
require(SNT.controller() == address(this));
require(SNT.decimals() == 18);
函数的访问控制
- 通过修改器实现,比如:
function generateTokens(address _owner, uint _amount ) onlyController returns (bool) {
只有controller才能生成token, 合约创建者如果不是controller,也没有权限生成token.
- 通过constant限制get函数不能修改storage的数据:
function getValueAt(Checkpoint[] storage checkpoints, uint _block ) constant internal returns (uint) {
已知潜在安全问题的修复
function approve(address _spender, uint256 _amount) returns (bool success) {
if (!transfersEnabled) throw;
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender,0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
if ((_amount!=0) && (allowed[msg.sender][_spender] !=0)) throw;
不要使用tx.origin
用msg.sender
计算溢出的检查
比如safeMath.sol:
乘法:计算出结果后,通过除法判断结果是否溢出.
function mul(uint a, uint b) internal returns (uint) {
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
除法: 不检查除数是否等于0, 因为solidity会检查并throw;
减法: 不允许小减大;
加法: 判断是否溢出:
function add(uint a, uint b) internal returns (uint) {
uint c = a + b;
assert(c >= a);
return c;
}
直接判断是否溢出,如下:
function generateTokens(address _owner, uint _amount
) onlyController returns (bool) {
uint curTotalSupply = getValueAt(totalSupplyHistory, getBlockNumber());
if (curTotalSupply + _amount < curTotalSupply) throw; // Check for overflow