首先贴出地址:https://dapp.review/dapp/138
点击下方的合约,然后点击的code即可看到合约代码
![](https://img.haomeiwen.com/i7220971/3f9efd766ae74bb6.png)
-
交易规则
合约规定,交易分挂单方和收单方,挂单方贴出自己想用多少(amoutGive)什么种类(tokenGive)的代币,去兑换多少(amoutGet)什么种类(tokenGet)的代币。然后收单方看到这笔单子,并按照挂单方贴出的详情进行支付和兑换。
其中交易完成的时候,挂单方需要收取挂单手续费(make),收单方要收取收单手续费(take),合约会声明一个feeAmount来收取手续费,手续费的币种是收单方支付的币种(tokenGet),其中收单手续费根据用户级别会有不同程度的折扣(后面会详细说明),
-
代码实现
-
首先是引入了SafeMath和ERC-20代币接口
contract SafeMath { function safeMul(uint a, uint b) internal returns (uint) { uint c = a * b; assert(a == 0 || c / a == b); return c; } function safeSub(uint a, uint b) internal returns (uint) { assert(b <= a); return a - b; } function safeAdd(uint a, uint b) internal returns (uint) { uint c = a + b; assert(c>=a && c>=b); return c; } function assert(bool assertion) internal { if (!assertion) throw; } } contract Token { /// @return total amount of tokens function totalSupply() constant returns (uint256 supply) {} /// @param _owner The address from which the balance will be retrieved /// @return The balance function balanceOf(address _owner) constant returns (uint256 balance) {} /// @notice send `_value` token to `_to` from `msg.sender` /// @param _to The address of the recipient /// @param _value The amount of token to be transferred /// @return Whether the transfer was successful or not function transfer(address _to, uint256 _value) returns (bool success) {} /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` /// @param _from The address of the sender /// @param _to The address of the recipient /// @param _value The amount of token to be transferred /// @return Whether the transfer was successful or not function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {} /// @notice `msg.sender` approves `_addr` to spend `_value` tokens /// @param _spender The address of the account able to transfer the tokens /// @param _value The amount of wei to be approved for transfer /// @return Whether the approval was successful or not function approve(address _spender, uint256 _value) returns (bool success) {} /// @param _owner The address of the account owning tokens /// @param _spender The address of the account able to transfer the tokens /// @return Amount of remaining tokens allowed to spent function allowance(address _owner, address _spender) constant returns (uint256 remaining) {} event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); uint public decimals; string public name; }
-
然后实现了一个标准代币合约(StandardToken),和一个可以创造和销毁代币的合约(ReserveToken)
为什么说是生成和销毁代币呢,因为这里的create和destory方法都对totalSupply进行了加减,是会对整个合约里的代币数量产生影响的
contract StandardToken is Token { function transfer(address _to, uint256 _value) returns (bool success) { //Default assumes totalSupply can't be over max (2^256 - 1). //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap. //Replace the if with this one instead. if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) { //if (balances[msg.sender] >= _value && _value > 0) { balances[msg.sender] -= _value; balances[_to] += _value; Transfer(msg.sender, _to, _value); return true; } else { return false; } } function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { //same as above. Replace this line with the following if you want to protect against wrapping uints. if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) { //if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) { balances[_to] += _value; balances[_from] -= _value; allowed[_from][msg.sender] -= _value; Transfer(_from, _to, _value); return true; } else { return false; } } function balanceOf(address _owner) constant returns (uint256 balance) { return balances[_owner]; } function approve(address _spender, uint256 _value) returns (bool success) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } function allowance(address _owner, address _spender) constant returns (uint256 remaining) { return allowed[_owner][_spender]; } mapping(address => uint256) balances; mapping (address => mapping (address => uint256)) allowed; uint256 public totalSupply; } contract ReserveToken is StandardToken, SafeMath { // 注意这里虽然为minter,挖坑,但是和区块链中的挖矿是两回事 address public minter; function ReserveToken() { minter = msg.sender; } /// @notice 为某一个账户(account)生成一些代币(amount) function create(address account, uint amount) { if (msg.sender != minter) throw; balances[account] = safeAdd(balances[account], amount); totalSupply = safeAdd(totalSupply, amount); } /// @notice 在某一个账户(account)上销毁一些代币(amount) function destroy(address account, uint amount) { if (msg.sender != minter) throw; if (balances[account] < amount) throw; balances[account] = safeSub(balances[account], amount); totalSupply = safeSub(totalSupply, amount); } }
-
定义账户级别
一共有三种账户级别,普通用户,银级账户,金级账户
- 普通用户需要支付make(挂单手续费)和take(挂单手续费),且不享受折扣
- 银级用户不需要支付make(挂单手续费),需要支付take(收单手续费),且可获得收单方支付的一定比例的take(收单手续费)
- 金级用户既不需要支付make(挂单手续费),需要支付take(收单手续费),且享受收单方支付的全部take(收单手续费)
contract AccountLevels { //given a user, returns an account level //0 = regular user (pays take fee and make fee) //1 = market maker silver (pays take fee, no make fee, gets rebate) //2 = market maker gold (pays take fee, no make fee, gets entire counterparty's take fee as rebate) function accountLevel(address user) constant returns(uint) {} } contract AccountLevelsTest is AccountLevels { mapping (address => uint) public accountLevels; function setAccountLevel(address user, uint level) { accountLevels[user] = level; } function accountLevel(address user) constant returns(uint) { return accountLevels[user]; } }
-
然后开始进入正题,交易的核心代码--EtherDelta
这里的代码比较多,我们分几个小节来看
-
合约定义的成员变量
address public admin; //the admin address 管理员账户 address public feeAccount; //the account that will receive fees 收取手续费的账户 address public accountLevelsAddr; //the address of the AccountLevels contract 定义用户级别的合约地址 uint public feeMake; //percentage times (1 ether) 挂单手续费费率 uint public feeTake; //percentage times (1 ether) 收单手续费费率 uint public feeRebate; //percentage times (1 ether) 折扣率--针对银级账户 mapping (address => mapping (address => uint)) public tokens; //mapping of token addresses to mapping of account balances (token=0 means Ether) 这是一个嵌套映射,内层映射的账户地址->token余额,外层的的映射是代币的合约地址->每个用户的余额情况。他的含义是存储了每种代币的每个用户的余额情况 mapping (address => mapping (bytes32 => bool)) public orders; //mapping of user accounts to mapping of order hashes to booleans (true = submitted by user, equivalent to offchain signature) 这也是一个嵌套映射,内层映射是存的订单的hash值(摘要信息)->订单是否有效,外层的key为用户账户。他的含义是每个用户的每笔订单是否有效 mapping (address => mapping (bytes32 => uint)) public orderFills; //mapping of user accounts to mapping of order hashes to uints (amount of order that has been filled) 这也是一个嵌套映射,内层映射是存的订单的hash值(摘要信息)->订单已经完成的token数量,外层的key为用户账户。他的含义是每个用户的每笔订单的已经完成的token数量
-
合约定义的事件,分别时挂单,收单(交易),充值,退款
event Order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user); event Cancel(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s); event Trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, address get, address give); event Deposit(address token, address user, uint amount, uint balance); event Withdraw(address token, address user, uint amount, uint balance);
-
合约的构造函数和空函数以及一些setter,都是简单的设置成员变量的值
function EtherDelta(address admin_, address feeAccount_, address accountLevelsAddr_, uint feeMake_, uint feeTake_, uint feeRebate_) { admin = admin_; feeAccount = feeAccount_; accountLevelsAddr = accountLevelsAddr_; feeMake = feeMake_; feeTake = feeTake_; feeRebate = feeRebate_; } // 空函数是在调用合约的一个不存在的方法时调用的方法,这里的逻辑是抛出异常 function() { throw; } function changeAdmin(address admin_) { if (msg.sender != admin) throw; admin = admin_; } function changeAccountLevelsAddr(address accountLevelsAddr_) { if (msg.sender != admin) throw; accountLevelsAddr = accountLevelsAddr_; } function changeFeeAccount(address feeAccount_) { if (msg.sender != admin) throw; feeAccount = feeAccount_; } function changeFeeMake(uint feeMake_) { if (msg.sender != admin) throw; if (feeMake_ > feeMake) throw; feeMake = feeMake_; } function changeFeeTake(uint feeTake_) { if (msg.sender != admin) throw; if (feeTake_ > feeTake || feeTake_ < feeRebate) throw; feeTake = feeTake_; } function changeFeeRebate(uint feeRebate_) { if (msg.sender != admin) throw; if (feeRebate_ < feeRebate || feeRebate_ > feeTake) throw; feeRebate = feeRebate_; }
-
充值退款的实现
这里充值提款分为两类,充值代币和充值以太币,其中以太币以0账户作为代币标示,转账直接转以太币给账户,提款类似
/// @notice 充值以太币,需要将合约设置为payable,直接支付以太币给合约 function deposit() payable { tokens[0][msg.sender] = safeAdd(tokens[0][msg.sender], msg.value); Deposit(0, msg.sender, msg.value, tokens[0][msg.sender]); } /// @notice 提款以太币,由合约账户像用户支付以太币 function withdraw(uint amount) { if (tokens[0][msg.sender] < amount) throw; tokens[0][msg.sender] = safeSub(tokens[0][msg.sender], amount); if (!msg.sender.call.value(amount)()) throw; Withdraw(0, msg.sender, amount, tokens[0][msg.sender]); } /// 充值token,主要是调用ERC-20标准代币合约的接口完成转账操作,并维护tokens成员变量 function depositToken(address token, uint amount) { // remember to call Token(address).approve(this, amount) or this contract will not be able to do the transfer on your behalf. // 这里需要用户先调用合约的approve方法给合约账户设置一些提取额度 if (token==0) throw; // 调用ERC-20标准代币合约的接口完成转账操作 if (!Token(token).transferFrom(msg.sender, this, amount)) throw; // 维护tokens成员变量 tokens[token][msg.sender] = safeAdd(tokens[token][msg.sender], amount); Deposit(token, msg.sender, amount, tokens[token][msg.sender]); } /// 提取token function withdrawToken(address token, uint amount) { if (token==0) throw; if (tokens[token][msg.sender] < amount) throw; tokens[token][msg.sender] = safeSub(tokens[token][msg.sender], amount); if (!Token(token).transfer(msg.sender, amount)) throw; Withdraw(token, msg.sender, amount, tokens[token][msg.sender]); }
-
挂单
挂单有两种方式,一种是用户调用合约的order的方法进行挂单(如下);另一种需要用户自己先完成数字签名并发送给收单方,由收单方调用合约的trade方法完成交易(第6节会进行介绍)
/// @notice 挂单,这里的操作很简单,就是对订单信息进行hash得到摘要,并维护orders记录订单状态 /// @param tokenGet 要支付的代币类型 /// @param amountGet 要支付的代币数量 /// @param tokenGive 要获取的代币类型 /// @param amountGive 要获取的代币数量 /// @param expires 过期的区块数 function order(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce) { // 对订单信息进行hash得到摘要 bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); // 维护orders记录订单状态 orders[msg.sender][hash] = true; Order(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender); }
-
收单(交易)
如5.所述,挂单有两种方式,第二种在trade方法中需要完成签名验证,这个过程是通过ecrecover方法完成的,这个方法的说明请参考:http://me.tryblockchain.org/web3js-sign-ecrecover-decode.html
/// @notice 收单,首先进行交易的验证,如5.所述,有两种方式 /// @param tokenGet 要支付的代币类型 /// @param amountGet 要支付的代币数量 /// @param tokenGive 要获取的代币类型 /// @param amountGive 要获取的代币数量 /// @param expires 过期的区块数 /// @param v,r,s 是签名验证所需的参数 /// @param amount 挂单方想要amountGet个token,但是收单方可能不够这么多,所以只换amout个 function trade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount) { //amount is in amountGet terms bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); // 首先进行条件判断,符合条件才能完成交易操作 if (!( // || 运算符的两侧是5.中提到的两种挂单方式 (orders[user][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == user) && // 验证当前区块小于过期区块数 block.number <= expires && // 判断如果进行了这次交易,是不是会超出挂单方的预期收入 safeAdd(orderFills[user][hash], amount) <= amountGet )) throw; // 执行实际的转账操作 tradeBalances(tokenGet, amountGet, tokenGive, amountGive, user, amount); // 维护orderFills,记录该笔挂单已经完成交易的代币数量 orderFills[user][hash] = safeAdd(orderFills[user][hash], amount); // 触发事件 Trade(tokenGet, amount, tokenGive, amountGive * amount / amountGet, user, msg.sender); }
/// @noice 具体的转账操作 function tradeBalances(address tokenGet, uint amountGet, address tokenGive, uint amountGive, address user, uint amount) private { // 首先计算挂单手续费和收单手续费 uint feeMakeXfer = safeMul(amount, feeMake) / (1 ether); uint feeTakeXfer = safeMul(amount, feeTake) / (1 ether); // 默认折扣率为0 uint feeRebateXfer = 0; /// 如果用户级别定义合约存在,则按照之前声明的规则修改折扣率 if (accountLevelsAddr != 0x0) { uint accountLevel = AccountLevels(accountLevelsAddr).accountLevel(user); // 这里感觉和之前注释里的规则有些冲突,因为金银用户还是要支付make,感觉应该加一条 // if (accountLevel>0) feeMakeXfer = 0; if (accountLevel==1) feeRebateXfer = safeMul(amount, feeRebate) / (1 ether); if (accountLevel==2) feeRebateXfer = feeTakeXfer; } // sender.tokenGet - amount - feeTakeXfer(收单方支付收单手续费) tokens[tokenGet][msg.sender] = safeSub(tokens[tokenGet][msg.sender], safeAdd(amount, feeTakeXfer)); // user.tokenGet + amount + feeRebateXfer - feeMakeXfer(挂单方支付挂单手续费) tokens[tokenGet][user] = safeAdd(tokens[tokenGet][user], safeSub(safeAdd(amount, feeRebateXfer), feeMakeXfer)); // feeAccount.tokenGet + feeMakeXfer + feeTakeXfer - feeRebateXfer tokens[tokenGet][feeAccount] = safeAdd(tokens[tokenGet][feeAccount], safeSub(safeAdd(feeMakeXfer, feeTakeXfer), feeRebateXfer)); // user.tokenGive - (amountGive * amount) / amountGet 根据比例计算这次user需要支付的tokenGive的数量 tokens[tokenGive][user] = safeSub(tokens[tokenGive][user], safeMul(amountGive, amount) / amountGet); // sender.tokenGive + (amountGive * amount) / amountGet tokens[tokenGive][msg.sender] = safeAdd(tokens[tokenGive][msg.sender], safeMul(amountGive, amount) / amountGet); // 可以看到上面的计算,合起来正好达到一个平衡 }
-
交易前的详细的账户余额验证
结合上面的交易过程,这个验证应该不能理解
function testTrade(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s, uint amount, address sender) constant returns(bool) { if (!( tokens[tokenGet][sender] >= amount && availableVolume(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, user, v, r, s) >= amount )) return false; return true; } function availableVolume(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint) { bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); if (!( (orders[user][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == user) && block.number <= expires )) return 0; uint available1 = safeSub(amountGet, orderFills[user][hash]); // 这个available2是验证user(挂单方)还有足够的tokenGive可以支付 uint available2 = safeMul(tokens[tokenGive][user], amountGet) / amountGive; if (available1<available2) return available1; return available2; }
-
获得当前订单状态和取消订单
/// @noice 获得当前订单状态 function amountFilled(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, address user, uint8 v, bytes32 r, bytes32 s) constant returns(uint) { bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); return orderFills[user][hash]; } /// @noice 取消订单:操作为将当前订单的已完成交易的代币数量(orderFills)设置为上限 function cancelOrder(address tokenGet, uint amountGet, address tokenGive, uint amountGive, uint expires, uint nonce, uint8 v, bytes32 r, bytes32 s) { bytes32 hash = sha256(this, tokenGet, amountGet, tokenGive, amountGive, expires, nonce); if (!(orders[msg.sender][hash] || ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash),v,r,s) == msg.sender)) throw; // 将当前订单的已完成交易的代币数量(orderFills)设置为上限 orderFills[msg.sender][hash] = amountGet; Cancel(tokenGet, amountGet, tokenGive, amountGive, expires, nonce, msg.sender, v, r, s); }
-
-
网友评论