美文网首页区块链大学区块链研习社区块链学习笔记
ForkDelta-针对以太坊的代币交易所-合约代码详解

ForkDelta-针对以太坊的代币交易所-合约代码详解

作者: 9c0ddf06559c | 来源:发表于2018-10-24 13:17 被阅读5次

首先贴出地址:https://dapp.review/dapp/138
点击下方的合约,然后点击的code即可看到合约代码

image.png
  • 交易规则

    合约规定,交易分挂单方和收单方,挂单方贴出自己想用多少(amoutGive)什么种类(tokenGive)的代币,去兑换多少(amoutGet)什么种类(tokenGet)的代币。然后收单方看到这笔单子,并按照挂单方贴出的详情进行支付和兑换。

    其中交易完成的时候,挂单方需要收取挂单手续费(make),收单方要收取收单手续费(take),合约会声明一个feeAmount来收取手续费,手续费的币种是收单方支付的币种(tokenGet),其中收单手续费根据用户级别会有不同程度的折扣(后面会详细说明),

  • 代码实现

    1. 首先是引入了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;
      }
      
    2. 然后实现了一个标准代币合约(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);
        }
      }
      
    3. 定义账户级别

      一共有三种账户级别,普通用户,银级账户,金级账户

      • 普通用户需要支付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];
        }
      }
      
    4. 然后开始进入正题,交易的核心代码--EtherDelta

      这里的代码比较多,我们分几个小节来看

      1. 合约定义的成员变量

          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数量
        
      2. 合约定义的事件,分别时挂单,收单(交易),充值,退款

        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);
        
      3. 合约的构造函数和空函数以及一些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_;
          }
        
      4. 充值退款的实现

        这里充值提款分为两类,充值代币和充值以太币,其中以太币以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]);
          }
        
      5. 挂单

        挂单有两种方式,一种是用户调用合约的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);
          }
        
      6. 收单(交易)

        如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);
            // 可以看到上面的计算,合起来正好达到一个平衡
          }
        
      7. 交易前的详细的账户余额验证

        结合上面的交易过程,这个验证应该不能理解

          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;
          }
        
      8. 获得当前订单状态和取消订单

         /// @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);
          }
        

相关文章

网友评论

    本文标题:ForkDelta-针对以太坊的代币交易所-合约代码详解

    本文链接:https://www.haomeiwen.com/subject/tzkttqtx.html