Solidity

當從一個智能合約呼叫時,ERC20 轉賬會恢復

  • March 20, 2021

MyToken我有一個似乎執行良好的 ERC20 代幣。我有另一個智能合約AirDrop,應該分發 MyToken。該sendTokens()方法接受一個地址數組和一個值數組並將values令牌分發到addresses.

或者至少這是應該發生的。然後sendTokens()呼叫. 當我在松露上執行它時,我總是收到一條錯誤消息:.sendInternally()``ERC20(token).transfer()``Error: VM Exception while processing transaction: revert

我使用 Truffle debug 進行了進一步調查,發現執行實際傳輸的行會ERC20(token).transfer()引發錯誤。逐步使用調試器,它似乎只是到達該行並由於未知原因引發錯誤。我無法確定具體原因。

為了確保問題與方法本身無關transfer,我嘗試呼叫MyToken.transferAnyERC20Token()which also uses ERC20(token).transfer(). 那里工作得很好,但是空投合約中看似相同的呼叫出錯了。AirDrop 合約的初始化地址為MyToken

我也在 Ganache 和 Rinkeby 上嘗試過,結果相同。

我已經發布了下面的所有程式碼:

library SafeMath {

   function mul(uint256 a, uint256 b) internal pure returns (uint256) {
       if (a == 0) {
           return 0;
       }
       uint256 c = a * b;
       assert(c / a == b);
       return c;
   }

   function div(uint256 a, uint256 b) internal pure returns (uint256) {
       return a / b;
   }

   function sub(uint256 a, uint256 b) internal pure returns (uint256) {
       assert(b <= a);
       return a - b;
   }

   function add(uint256 a, uint256 b) internal pure returns (uint256) {
       uint256 c = a + b;
       assert(c >= a);
       return c;
   }
}

contract ERC20 {
   function totalSupply() public constant returns (uint);
   function balanceOf(address tokenOwner) public constant returns (uint balance);
   function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
   function transfer(address to, uint tokens) public returns (bool success);
   function approve(address spender, uint tokens) public returns (bool success);
   function transferFrom(address from, address to, uint tokens) public returns (bool success);

   event Transfer(address indexed from, address indexed to, uint tokens);
   event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

contract Ownable {
   address public owner;


   event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

   constructor() public {
       owner = msg.sender;
   }

   modifier onlyOwner() {
       require(msg.sender == owner);
       _;
   }

   function transferOwnership(address newOwner) public onlyOwner {
       require(newOwner != address(0));
       emit OwnershipTransferred(owner, newOwner);
       owner = newOwner;
   }
}

contract StandardToken is ERC20  {

 using SafeMath for uint256;

 mapping (address => mapping (address => uint256)) internal allowed;

 mapping(address => uint256) public balances;

 uint256 _totalSupply;

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

 function transfer(address _to, uint256 _value) public returns (bool) {
   require(_to != address(0));
   require(_value <= balances[msg.sender]);

   balances[msg.sender] = balances[msg.sender].sub(_value);
   balances[_to] = balances[_to].add(_value);
   emit Transfer(msg.sender, _to, _value);
   return true;
 }

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

 function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
   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 approve(address _spender, uint256 _value) public returns (bool) {
   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 increaseApproval(address _spender, uint _addedValue) public returns (bool) {
   allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
   emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
   return true;
 }

 function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
   uint 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;
 }

}

這是程式碼MyToken

contract MyToken is StandardToken, Ownable {
   using SafeMath for uint256;

   uint256 public constant TOTAL_SUPPLY = 10 ** 9;

   string public constant name = "My Token";
   string public constant symbol = "MYT";
   uint8 public constant decimals = 18;

   mapping (address => string) aliases;
   mapping (string => address) addresses;

   constructor() public {
       _totalSupply = TOTAL_SUPPLY * (10**uint256(decimals));
       balances[owner] = _totalSupply;
       emit Transfer(address(0), owner, _totalSupply);
   }

   function availableSupply() public view returns (uint256) {
       return _totalSupply.sub(balances[owner]).sub(balances[address(0)]);
   }

   function approveAndCall(address spender, uint256 tokens, bytes data) public returns (bool success) {
       allowed[msg.sender][spender] = tokens;
       emit Approval(msg.sender, spender, tokens);
       ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data);
       return true;
   }

   function () public payable {
       revert();
   }

   function transferAnyERC20Token(address tokenAddress, uint256 tokens) public onlyOwner returns (bool success) {
       return ERC20(tokenAddress).transfer(owner, tokens);
   }

}

以及空投程式碼:

contract AirDrop is Ownable {

 ERC20 public token;
 address public tokenWallet;
 address public tokenAddress;

 event TransferredToken(address indexed to, uint256 value);
 event FailedTransfer(address indexed to, uint256 value);

 constructor(address _tokenAddress, address _tokenWallet) public {
     tokenWallet = _tokenWallet;
     tokenAddress = _tokenAddress;
     token = ERC20(_tokenAddress);
 }

 function sendTokens(address[] destinations, uint256[] values, address _tokenAddress, address _tokenWallet) onlyOwner external {
     require(destinations.length == values.length);
     uint256 i = 0;
     while (i < destinations.length) {
         uint256 toSend = values[i] * 10**18;
         sendInternally(destinations[i], toSend, values[i]);
         i++;
     }
 }

 function sendTokensSingleValue(address[] destinations, uint256 value) onlyOwner external {
     uint256 i = 0;
     uint256 toSend = value * 10**18;
     while (i < destinations.length) {
         sendInternally(destinations[i] , toSend, value);
         i++;
     }
 }

 function sendInternally(address recipient, uint256 tokens, uint256 valueToPresent) internal {
   require(recipient != address(0));
   ERC20(tokenAddress).transfer(recipient, tokens);
 }


 function tokensAvailable() constant returns (uint256) {
   return token.allowance(tokenWallet, msg.sender);
 }

 function destroy() onlyOwner public {
   selfdestruct(owner);
 }
}

我嘗試呼叫 MyToken.transferAnyERC20Token(),它也使用 ERC20(token).transfer()。那里工作得很好,但是空投合約中看似相同的呼叫出錯了。

我不是 100% 確定這是解決方案,但是您描述流程的方式暗示了對令牌應該在哪裡的一些混淆。

當您手動測試時,您簽署交易的外部擁有的帳戶需要具有令牌才能發送它們。空投也是如此。它不能發送它沒有的東西。

你還記得在指示空投合約發送它沒有的代幣之前從部署者/鑄幣者賬戶轉移到空投合約嗎?

萬一不清楚。

  1. Alice 部署合約並且 Alice(通常)擁有 100% 的初始供應。
  2. Alice 希望 Airdrop 合約分發代幣,因此 Alice 將足夠的代幣轉移到 Airdrop 合約。
  3. Alice 向 Airdrop 合約簽署交易,指示其將 Airdrop 合約託管的代幣轉發給接收者。

您似乎正在使用的 ERC20 的 OpenZeppelin 實現將在發送方(即 Airdrop 合約)沒有足夠的代幣用於該transfer方法提取的情況下引發異常。

希望能幫助到你。

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