I've been working on different ways to perform reentrancy attacks and there is one case which I have still not seen any working example on the internet. In the book Mastering Ethereum, the reentrancy attack is explained with a contract where the function withdraw(uint amount) takes the input amount. The version on Solidity has been updated a lot since then and whenever I try to perform a similar attack, it does not work. It works whenever the function withdraw() takes no arguments and it also works when using older versions.
Could anyone provide an example of a reentrancy attack where the target contract takes the withdraw amount as input?
Thank you!
Let's say you have 1 ether in the contract, and the contract has a total of 10 ether. You're trying to steal all 10 ether with re-entrancy, but that necessarily means the variable tracking your balance must underflow to the equivalent of uint256(-9) ether -- you're trying to withdraw 1 ether 10 times.. This will cause a revert in Solidity 0.8.0 or higher, since it has built in under/overflow protection. If you want it to work in 0.8.0, you have to wrap the balance reduction line with unchecked.
This code is still vulnerable to re-entrancy in 0.8.0, but only because it sets the balance to zero, and can't underflow
mapping(address => uint256) public balance;
function deposit() external payable {
balance[msg.sender] += msg.value;
}
function withdraw() external {
msg.sender.call{value: balance[msg.sender]}(""); // re-entrancy
balance[msg.sender] == 0; // cannot underflow
}
function withdrawV2(uint256 value) external {
require(value <= balance[msg.sender], "you don't have that much"); // not that this does anything...
msg.sender.call{value: balance[msg.sender]}("");
unchecked { // now it can underflow
balance[msg.sender] -= value;
}
}
Related
contract Counter {
uint256 value = 0;
function addOne() external {
value += 1;
}
}
contract MyContract {
Counter a = .....;
Counter b = .....;
// .......
function myFunc() external {
// ......
a.addOne();
b.addOne();
}
}
What will happen if gas uses out while on executing b.addOne()?
If a.value will add one?
How can I avoid it?
Gas is the currency used to incentivize miners to execute OPCODEs on the EVM. Each transaction on Ethereum is ATOMic, meaning every OPCODE in the transaction must run otherwise the entire transaction is rejected. So if you run out of gas, your transaction will simply revert.
You can avoid this by using a gas estimator (e.g. Remix has this integrate out of the box) or doing it by hand (since every OPCODE's gas requirement is known, and so is the gas price, even if it does fluctuate moment to moment).
Then when you make the call, you specify how much gas to use eg: a.addOne{gas: 69696969 }()
If you send too much, the unspent gas will be returned to the caller.
I am experimenting with solidity and I faced an issue for what I could not find a solution.
The program should let addresses buy ticket at a preset price, and the owner can start the "roll the dice" function which randomly selects the winner and transfer the funds to that address.
I beleive that this program would be easier with mapping instead of array, but getting experience with array was the main purpose of this program.
The error happens when the user calls buyTicket function. Based on the response I beleive the contract comsumes too much gas. Can someone tell me why it doesnt work? I appreciate any other helping comment regarding the rest of the code:)
Thanks in advance!
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Lottery {
constructor () {
owner = msg.sender;
}
address[] public Players;
address private owner;
uint public ticketPrice;
uint public price;
uint public nonce;
uint public variations;
address payable winner;
bool hasTicketAnswer;
event Winner(address _winner);
event PriceSet(uint _setPrice);
event TicketBought();
function setTicketPrice(uint _ticketPrice) public {
require(msg.sender == owner, "Only Owner...");
ticketPrice = _ticketPrice;
emit PriceSet(_ticketPrice);
}
function hasTicket(address _sender) private returns(bool) {
hasTicketAnswer = false;
for (uint i = 0; i < Players.length; i++) {
if (Players[i] == _sender) hasTicketAnswer = true;
}
return hasTicketAnswer;
}
function buyTicket() external payable {
require(ticketPrice > 0, "Price did not set, be patient...");
require(hasTicket(msg.sender) == false, "You cannot have two tickets...");
require(msg.sender.balance <= ticketPrice, "Insufficient funds...");
payable(address(this)).transfer(ticketPrice);
Players.push(address(msg.sender));
price += msg.value;
emit TicketBought();
}
function checkBalance() public view returns(uint) {
return address(this).balance;
}
function rollTheDice() public payable {
variations = Players.length;
winner = payable(Players[uint(keccak256(abi.encodePacked(msg.sender, nonce, block.timestamp))) % variations]);
winner.transfer(price);
emit Winner(winner);
}
receive () external payable {
}
}
Besides probably finding the problem, I've read some things that I'd like to comment on.
Your problem
The reason why you're getting the "Insufficient funds" error is because the condition is returning false. You're asking the msg.sender balance to be less than or equal (<=) to ticketPrice, when it should be more than or equal (>=).
Let's say Alice has a balance of 0.05 ETH and interacts with the contract whose ticket price is 0.001 ETH...
require(0.05 ETH <= 0.001 ETH) // Reverting...
Observations
I'm curious if you're intentionally coding the buyTicket function in that way. What it actually does is checking if the msg.sender has enough ETH to buy a ticket in its wallet, which doesn't mean that the user is effectively sending ETH in the transaction (the amount of wei sent in the transaction can be checked with msg.value, you can read more about it here).
My last observation is the payable(address(this)).transfer(ticketPrice) line of code, because it's not necessary to do so, once a payable function receives ETH, it is saved into the contract... In that line you're just making the Bob's contract to send ETH to the Bob's contract, which just wastes gas without reason
I hope I've helped with you and if I wasn't completely clear in any thing I've said don't doubt in asking me
so because of reentrancy attacks I'm structuring my function to not get hacked. So first updating mappings and so on and then sending the payment.
My question is what if the payment fails to go through. Will entire function be reverted of just the payment?
Because if only the payment than that would mean my mappings would be updated as if the payment went through.
Is this the case?
Thanks for answers!
function withdraw(uint256 _amount) external {
balances[msg.sender] -= _amount;
(bool success, ) = payable(msg.sender).call{value: _amount}("");
}
If the low-level .call() is unsuccessful, the value of success is set to false but that doesn't revert the whole transaction. So the balances value would be reflected even though the payment didn't go through.
You can add a require() condition to check the result of the payment. This will make the parent transaction fail if the internal transaction fails.
(bool success, ) = payable(msg.sender).call{value: _amount}("");
require(success);
Or you can use the .transfer() function (member of address payable, do not confuse with token transfers) that also fails the parent transaction if the internal transaction fals:
function withdraw(uint256 _amount) external {
balances[msg.sender] -= _amount;
payable(msg.sender).transfer(_amount);
}
If you're preventing the reentrancy attacks, you might probably use the modifiers. Therefore, when the Reentrancy is detected, the function would be reverted and even not allowed to enter the function. That is, there would be no other parameters updated.
Besides, I can show you some demo code to answer your question.
contract test {
uint public a = 0;
// a will still be a
function addRevert() public{
a += 1;
goRevert();
}
// a = a + 1
function addNoRevert() public{
a += 1;
}
function goRevert() pure public{
revert();
}
}
here is my code snippet and i have no idea why its not working
function Testdeposit(address _assetAddress) public returns (uint256 status)
{
//IERC20 erc20 = IERC20(_assetAddress);
//erc20.transferFrom(senderAddress, address(this), _amount));
//uint256 amount = erc20.allowance(senderAddress, address(this));
uint256 amount = IERC20(_assetAddress).balanceOf(address(this));
return amount;
}
i am using a standard IERC20 interface. just it dsnt have an event to emit. i assume it to return me 0 if there is no balance but its gvng me erro. Transcation not going through.
Any suggestions??
It is not working because you may have not overridden the IERC20 functions. At least as they should. I would highly recommend using the ERC20 standard instead, just for how easy it is to implement.
I'm writing a smart contract in solidity, and I need to send ether from my coinbase (eth.coinbase) to my friend coinbase (address = 0x0123).
If I try to use an address.send(value) the function doesn't decrease my account and doesn't increase my friend coin base.
I only can send ether in geth with "eth.sendTransaction(VALUE, {from:eth.coinbase, to:address})"
so I want to know if its possible to call an eth method in contract or a different way to send ether in smart contract
function withdraw() returns (bool) {
address x = 0x0123;
uint amount = 100 ether;
if (x.send(amount))
return true;
else
return false;
}
address.send does not propagate exception that's why you don't see any issue. Make sure you have enough Eth in your contract.
Have a look on this documentation that will explain how to set up your smart contract: https://developer.ibm.com/clouddataservices/2016/05/19/block-chain-technology-smart-contracts-and-ethereum/
You can use following function. just change variable name with yours.
function Transfer(uint amount,address reciever){
// check sender balance is less than of amount which he wants to send.
if(balance[msg.sender] < amount){
return;
}
// decrease sender's balance.
balance[msg.sender] = balance[msg.sender] - amount;
// increase reciever's balance.
balance[reciever] = balance[reciever] + amount;
// event
// transaction(msg.sender,reciever,amount);
}