Erc-20

Solidity 0.5.2 - OpenZeppelin - 新的 SafeERC20 callOptionalReturn(token,data)

  • April 4, 2019

我一直在嘗試將 ERC20 功能與 ERC223 功能重載技術混合,並使用 SafeERC20 創建向後兼容的橋樑。

為了相關性,我將 SafeERC20 重命名為 SafeERC223,但問題是,由於它僅適用於 ERC20,它只希望我們始終擁有 1 個傳遞函式,而不是像 ERC223 案例中的 2 個。

到目前為止我們有什麼:

  • 地址庫
  • SafeERC223 庫
  • ERC223介面合約
  • ERC223Receiving Contract // ERC223 tokenFallback() 發射器函式
  • ERC223Token 合約
  • ERC223合約

新的 SafeERC20 更酷的方法為 ERC223 函式重載技術帶來了一個有趣的挑戰,因為它只需要 1 個傳遞函式

Code

pragma solidity ^0.5.2;

/**
* Utility library of inline functions on addresses
*/
library Address {
   /**
    * Returns whether the target address is a contract
    * @dev This function will return false if invoked during the constructor of a contract,
    * as the code is not actually created until after the constructor finishes.
    * @param account address of the account to check
    * @return whether the target address is a contract
    */
   function isContract(address account) internal view returns (bool) {
       uint256 size;
       // XXX Currently there is no better way to check if there is a contract in an address
       // than to check the size of the code at that address.
       // See https://ethereum.stackexchange.com/a/14016/36603
       // for more details about how this works.
       // TODO Check this again before the Serenity release, because all addresses will be
       // contracts then.
       // solhint-disable-next-line no-inline-assembly
       assembly { size := extcodesize(account) }
       return size > 0;
   }
}

/** 
* @title SafeMath
* @dev Unsigned math operations with safety checks that revert on error.
*/
library SafeMath {
   /**
    * @dev Multiplies two unsigned integers, reverts on overflow.
    */
   function mul(uint256 a, uint256 b) internal pure returns (uint256) {
       // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
       // benefit is lost if 'b' is also tested.
       // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
       if (a == 0) {
           return 0;
       }

       uint256 c = a * b;
       require(c / a == b);

       return c;
   }

   /**
    * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
    */
   function div(uint256 a, uint256 b) internal pure returns (uint256) {
       // Solidity only automatically asserts when dividing by 0
       require(b > 0);
       uint256 c = a / b;
       // assert(a == b * c + a % b); // There is no case in which this doesn't hold

       return c;
   }

   /**
    * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
    */
   function sub(uint256 a, uint256 b) internal pure returns (uint256) {
       require(b <= a);
       uint256 c = a - b;

       return c;
   }

   /**
    * @dev Adds two unsigned integers, reverts on overflow.
    */
   function add(uint256 a, uint256 b) internal pure returns (uint256) {
       uint256 c = a + b;
       require(c >= a);

       return c;
   }

   /**
    * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
    * reverts when dividing by zero.
    */
   function mod(uint256 a, uint256 b) internal pure returns (uint256) {
       require(b != 0);
       return a % b;
   }
}

contract ERC223Interface {
   function totalSupply() external view returns (uint256);
   function balanceOf(address who) external view returns (uint256);
   function transfer(address to, uint256 value) external returns (bool);
   function transfer(address to, uint256 value, bytes calldata data) external returns (bool);
   function approve(address spender, uint256 value) external returns (bool);
   function allowance(address owner, address spender) external view returns (uint256);
   function transferFrom(address from, address to, uint256 value) external returns (bool);
   function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
   function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
   event Transfer(address indexed from, address indexed to, uint256 value);
   event Transfer(address indexed from, address indexed to, uint256 value, bytes data);
   event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title SafeERC223
* @dev Wrappers around ERC223 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC223 for ERC223;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC223 {
   using SafeMath for uint256;
   using Address for address;

   function safeTransfer(ERC223Interface token, address to, uint256 value) internal {
       require(token.transfer(to, value)); // this method works but i'm not sure if it's safe to use
   }

   function safeTransfer(ERC223Interface token, address to, uint256 value) internal {
       callOptionalReturn(token, abi.encodeWithSelector(msg.sig, to, value)); // this method does not work when a contract executes safeTransfer
   }

   function safeTransferFrom(ERC223Interface token, address from, address to, uint256 value) internal {
       callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
   }

   function safeApprove(ERC223Interface token, address spender, uint256 value) internal {
       // safeApprove should only be called when setting an initial allowance,
       // or when resetting it to zero. To increase and decrease it, use
       // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
       require((value == 0) || (token.allowance(address(this), spender) == 0));
       callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
   }

   function safeIncreaseAllowance(ERC223Interface token, address spender, uint256 value) internal {
       uint256 newAllowance = token.allowance(address(this), spender).add(value);
       callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
   }

   function safeDecreaseAllowance(ERC223Interface token, address spender, uint256 value) internal {
       uint256 newAllowance = token.allowance(address(this), spender).sub(value);
       callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
   }

   /**
    * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    * on the return value: the return value is optional (but if data is returned, it must not be false).
    * @param token The token targeted by the call.
    * @param data The call data (encoded using abi.encode or one of its variants).
    */
   function callOptionalReturn(ERC223Interface token, bytes memory data) private {
       // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
       // we're implementing it ourselves.

       // A Solidity high level call has three parts:
       //  1. The target address is checked to verify it contains contract code
       //  2. The call itself is made, and success asserted
       //  3. The return value is decoded, which in turn checks the size of the returned data.

       require(address(token).isContract());

       // solhint-disable-next-line avoid-low-level-calls
       (bool success, bytes memory returndata) = address(token).call(data);
       require(success);

       if (returndata.length > 0) { // Return data is optional
           require(abi.decode(returndata, (bool)));
       }
   }
}

contract ReentrancyGuard {
   using SafeMath for uint256;
   uint256 private _guardCounter;

   constructor () internal {
       _guardCounter = 1;
   }

   modifier nonReentrant() {
       _guardCounter = _guardCounter.add(1);
       uint256 localCounter = _guardCounter;
       _;
       require(localCounter == _guardCounter);
   }
}

contract ERC223ReceivingContract { 
   /**
    * @dev Standard ERC223 function that will handle incoming token transfers.
    *
    * @param _from  Token sender address.
    * @param _value Amount of tokens.
    * @param _data  Transaction metadata.
    */
   function tokenFallback(address _from, uint256 _value, bytes memory _data) public;
}

contract ERC223Token is ERC223Interface {
   using SafeMath for uint256;

   address private _owner; 
   // i know it's not really private :)) but clogs remix compiler :)
   // https://medium.com/swlh/ethereum-aint-hiding-your-secrets-703e89088937

   string  public  constant name = "ERC223";
   string  public  constant symbol = "ERC223";
   uint8   public  constant decimals = 18;
   uint256 private constant _totalSupply = 10000000 * (uint256(10) ** decimals);

   mapping (address => uint256) private _balances;
   mapping (address => mapping (address => uint256)) private _allowed;

   constructor() public {
       _owner = msg.sender;
       _balances[_owner] = _totalSupply;
       emit Transfer(address(0), _owner, _totalSupply);
   }

   function totalSupply() public view returns (uint256) {
       return _totalSupply;
   }

   function balanceOf(address owner) public view returns (uint256 balance) {
       return _balances[owner];
   }

   function transfer(address to, uint256 value) public returns (bool success) {
       require(to != address(0));
       require(value > 0 && balanceOf(msg.sender) >= value);
       require(balanceOf(to).add(value) > balanceOf(to));

       uint256 codeLength;
       bytes memory empty;

       assembly {
           codeLength := extcodesize(to)
       }

       _balances[msg.sender] = _balances[msg.sender].sub(value);
       _balances[to] = _balances[to].add(value);

       if(codeLength>0) {
           ERC223ReceivingContract receiver = ERC223ReceivingContract(to);
           receiver.tokenFallback(msg.sender, value, empty);
       }

       emit Transfer(msg.sender, to, value, empty);
       return true;
   }

   function transfer(address to, uint256 value, bytes memory data) public returns (bool success) {
       require(to != address(0));
       require(value > 0 && balanceOf(msg.sender) >= value);
       require(balanceOf(to).add(value) > balanceOf(to));

       uint256 codeLength;

       assembly {
           codeLength := extcodesize(to)
       }

       _balances[msg.sender] = _balances[msg.sender].sub(value);
       _balances[to] = _balances[to].add(value);

       if(codeLength>0) {
           ERC223ReceivingContract receiver = ERC223ReceivingContract(to);
           receiver.tokenFallback(msg.sender, value, data);
       }

       emit Transfer(msg.sender, to, value, data);
       return true;
   }

   function approve(address spender, uint256 value) public returns (bool success) {
       _allowed[msg.sender][spender] = value;
       emit Approval(msg.sender, spender, value);
       return true;
   }

   function allowance(address owner, address spender) public view returns (uint256) {
       return _allowed[owner][spender];
   }

   function transferFrom(address from, address to, uint256 value) public returns (bool success) {
       require(to != address(0));
       require(value <= _balances[from]);
       require(value <= _allowed[from][msg.sender]);

       _balances[from] = _balances[from].sub(value);
       _balances[to] = _balances[to].add(value);
       _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
       emit Transfer(from, to, value);
       return true;
   }

   function increaseAllowance(address spender, uint256 addedValue) public returns (bool success) {
       _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add(addedValue);
       emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
       return true;
   }

   function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool success) {
       uint256 oldValue = _allowed[msg.sender][spender];
       if (subtractedValue > oldValue) {
           _allowed[msg.sender][spender] = 0;
       } else {
           _allowed[msg.sender][spender] = oldValue.sub(subtractedValue);
       }
       emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
       return true;
   }

   function unlockERC20Tokens(address tokenAddress, uint256 tokens) public returns (bool success) {
       require(msg.sender == _owner);
       return ERC223Interface(tokenAddress).transfer(_owner, tokens);
   }

   function () external payable {
       revert("This contract does not accept ETH");
   }

}

contract ERC223Contract is ReentrancyGuard {
   using SafeMath for uint256;

   ERC223Interface private token;

   constructor(ERC223Interface _token) public {
       token = _token;
       emit Created("Successfully created ERC223 Contract");
   }

   function getBlockNumber() public view returns (uint256) {
       return block.number;
   }

   function getData() public pure returns (bytes memory) {
       return msg.data;
   }

   function getSignature() public pure returns (bytes4) {
       return msg.sig;
   }

   function () external {
     //if ether is sent to this address, send it back.
     revert();
   }

   function tokenFallback(address player, uint tokens, bytes memory data) public nonReentrant {
       require(msg.sender == address(token));
       emit DepositedERC223Token(player, tokens, data);
   }

   event Created(string);
   event DepositedERC223Token(address from, uint value, bytes data);
}

contract ICO is ReentrancyGuard, Owned{
   using SafeMath for uint256;
   using SafeERC223 for ERC223Interface;

   // TOKEN INTERFACE,
   ERC223Interface private token;

   // ICO WALLET
   address payable private wallet;

   // TOTAL ETHER RAISED
   uint public totalRaised;

   // TOTAL TOKENS DISTRIBUTED
   uint public tokensSold;

   // KYC VALIDATION, ADDRESS DEPOSIT LIMIT
   mapping(address => bool) private _kyc;
   mapping(address => uint) public senderLimit;

   event TokensPurchased(address indexed purchaser, uint256 value, uint256 amount);

   constructor(ERC223Interface _token, address payable _wallet) public {
       require(address(_token) != address(0));
       require(address(_wallet) != address(0));
       token = _token;
       wallet = _wallet;
   }

   // GET OWNER ADDRESS
   function tokenOwnerAddress() public view returns(address) {
       return token.getOwner();
   }

   // GET ICO ADDRESS
   function icoAddress() public view returns(address) {
       return address(this);
   }

   // GET CONTRACT TOTAL TOKENS LEFT FOR SALE
   function checkBalance() public view returns(uint) {
       return token.balanceOf(address(this));
   }

   // KYC - VALIDATE OR INVALIDATE ADDRESS
   function updateKYC(address userAddress, bool status) public onlyOwner returns(bool) {
       _kyc[userAddress] = status;
       return status;
   }

   // KYC - CHECK IF ADDRESS HAS BEEN VALIDATED
   function checkKYC(address userAddress) public view returns(bool) {
       return _kyc[userAddress];
   }

   // PAYABLE FUNCTION
   function () external payable {
       // buyTokens();
       buyTokens(msg.sender);
   }

   //----------------------------------------------------
   // 1,000 Tokens == 1 ETH => 1 ETH + 50% BONUS = 1,500 LVC
   //----------------------------------------------------
   function buyTokens(address beneficiary) public nonReentrant payable {
       require(checkBalance() != 0);
       require(msg.sender == beneficiary);
       uint256 weiAmount = msg.value;
       uint tokens;

       // PRESALE - 50% BONUS
       tokens = weiAmount * 1500;

       tokensSold = tokensSold.add(tokens);
       totalRaised = totalRaised.add(weiAmount);

       _processPurchase(beneficiary, tokens);
       _forwardFunds();

       senderLimit[beneficiary] = senderLimit[beneficiary].add(weiAmount);

       if(_kyc[beneficiary] == false){
           require(weiAmount >= .1 ether && weiAmount <= 25 ether && senderLimit[beneficiary] <= 25 ether);
       } else if (_kyc[beneficiary] == true) {
           require(weiAmount >= .1 ether && weiAmount <= 100 ether && senderLimit[beneficiary] <= 100 ether);
       }

       emit TokensPurchased(beneficiary, weiAmount, tokens);
   }

   function _deliverTokens(address beneficiary, uint256 tokenAmount) internal {
       token.safeTransfer(beneficiary, tokenAmount);
   }

   function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
       _deliverTokens(beneficiary, tokenAmount);
   }

   function _forwardFunds() internal {
       wallet.transfer(msg.value);
   }

   function _transferToBonusReserve(address beneficiary, uint256 tokenAmount) internal {
       _deliverTokens(beneficiary, tokenAmount);
   }

   function unlockBlockedERC20Tokens(address tokenAddress, uint tokens) public onlyOwner returns (bool success) {
       return ERC223Interface(tokenAddress).transfer(owner, tokens);
   }

   function tokenFallback(address owner, uint tokens, bytes memory data) public nonReentrant {
       require(msg.sender == address(token));
       require(owner == tokenOwnerAddress());
       emit DepositedTokens(owner, tokens, data);
   }

   event DepositedTokens(address from, uint value, bytes data);
}

如果我不從 ERC223Interface 註釋掉第二個傳遞函式,這就是我得到的錯誤。

TypeError: Member "transfer" not unique after argument-dependent lookup in 
contract ERC223Interface.callOptionalReturn(token, abi.encodeWithSelector( token.transfer.selector, to, value));

更新

  • 檢查 SafeERC223 - 在舊的 SafeERC223 工作時拋出 msg.sig…
  • 添加了一個 Crowdsale 範例,您可以在其中測試它是如何失敗的

重現步驟:

部署 ERC223 代幣

  1. 部署 ERC223 代幣
  2. 使用 2 個參數(代幣地址、ETH 錢包)部署 ERC223 ICO
  3. 所有者將總供應量的一半轉移到 ICO 契約。
  4. 貢獻者發送 1 個 ETH 並應收到 1500 個代幣。此步驟使用 SafeERC223 並且應該將令牌轉移給貢獻者,但如果我們使用 msg.sig 它會失敗,如果我們只使用舊的做事方式,但我不知道它是否可以安全使用。

提示: SafeTransfer 在 _processPurchase() 中使用 如果我們切換註釋 msg.sig 方法並取消舊的 SafeTransfer 它應該像以前一樣工作,但我不知道這種方式有多安全。

乾杯!

如果我沒記錯的話,問題是token.transfer.selector試圖從原始碼中找出簽名。

這將相當於abi.encodeWithSelector(bytes4(keccak256(bytes(signature)));

https://solidity.readthedocs.io/en/v0.5.2/units-and-global-variables.html?highlight=selector#abi-encoding-and-decoding-functions

以這種方式不可能進行區分,因為您想要“兩者”。

您對交易附帶的函式簽名感興趣。您應該希望它們完美地映射到實施契約。如果我的想法是正確的,那麼將其挑出來msg.sig並通過就足夠了。

msg.sig: https://solidity.readthedocs.io/en/v0.5.2/units-and-global-variables.html?highlight=selector#block-and-transaction-properties

那會給你留下這個:

function safeTransfer(ERC223Interface token, address to, uint256 value) internal {
   callOptionalReturn(token, abi.encodeWithSelector(msg.sig, to, value));
}

沒有保修 ;-)

希望能幫助到你。

引用自:https://ethereum.stackexchange.com/questions/69194