Assert vs. require within Solidity? - ethereum

My understanding is that "require" refunds the remaining gas if it fails whereas "assert" doesn't.
I'm a bit confused though b/c what is meant by remaining gas? I often specify a gas limit of a very large number even when the contract I'm calling requires very little.
If I were to specify 4,700,000 gas limit, and only 50,000 gas was required, but "assert" failed in the called contract, would I lose all 4,700,000 gas?

Yes, you would lose the entire amount (or close to it). assert is effectively the catastrophic bail out of the transaction due to something completely unexpected. It should be used to check for things like making sure your contract hasn't wound up in an invalid state, avoid divide by 0, over/underflow, etc.
require, on the other hand, will only consume the gas used up to the point of failure. The remaining gas will be refunded.
The gas limit you specify shouldn't be arbitrarily high. When you initiate a transaction, the full amount of gas you specify is sent and whatever is left at the end of execution is refunded back to you. Not only are you exposing yourself to asserts in your own contract, but if you call other contracts from your own, you are giving that contract a lot of bandwidth to do whatever it wants with the gas you pass on to it.
Example showing gas consumption:
pragma solidity ^0.4.16;
contract Test {
function run(uint8 i) public pure {
uint8 total = 0;
for (uint8 j = 0; j < 10; j++)
total += j;
assert (i < 20);
require (i < 10);
for (j = 0; j < 10; j++)
total += j;
}
}
All tests run with gas limit = 5000000
Test #1 - run(8): Function runs successfully - 1860 gas consumed.
Test #2 - run(15): Function passes assert, fails at require. Only first loop is run - 1049 gas consumed.
Test #3 - run(25): Function fails at assert. Again, only first loop is run - 4978536 gas consumed (ouch).

Related

Getting "out of gas" when sending whole amount of eth

Here is an example of such transaction:
etherscan
It was 569730199030000 Wei on a balance,
I used current gas price(by web3.eth.getGasPrice()) 19888345864 Wei.
So my estimated fee was (19888345864 * 21000) 417655263144000 Wei.
I was trying to send (balance - estimated fee) = 152074935886000 Wei.
The result as you can see "out of gas".
Why it is possible that regular send was required more than 21000 gas? If I understand properly in case if gas price is not enough then miners simply must ignore this tx, but in case if they are agree with the gas price they have to run this transaction normally, and normally sending eth requires gas limit 21000. Where is mistake in my calculations and how to execute such tx properly?
Generally, this often happens when you're trying to send ETH to a contract that either doesn't implement a special function to accept the ETH - or the contract does implement the function but it explicitly rejects the incoming transfer.
Specifically, the first-hand recipient of your transaction is a proxy contract deployed at address 0xB233903ACec807C61eeeCc4F69dd795A617a1732, that redirects the request to a target contract deployed at address 0xd332254f274cc65aa11178b74734e2992b8f349e.
Author of the target contract has not shared its source code, but from the decompiled bytecode it seems to accept incoming ETH transfer only if series of conditions (depending on the payload of the incoming transaction and the state of another contract) is met. Otherwise it rejects the transaction with the revert statement.
def _fallback() payable: # default function
require ext_code.size(0xa24787320ede4cc19d800bf87b41ab9539c4da9d)
static call 0xa24787320ede4cc19d800bf87b41ab9539c4da9d.0x4fef8ec4 with:
gas gas_remaining wei
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]
require return_data.size >= 32
delegate ext_call.return_data[0] with:
funct call.data[0 len 4]
gas gas_remaining wei
args call.data[4 len calldata.size - 4]
if not delegate.return_code:
revert with ext_call.return_data[0 len return_data.size]
return ext_call.return_data[0 len return_data.size]

Why does gas cost depends on function input?

I was testing this code using Remix. I was wondering why the execution cost of the function (in gas) depends on the input x. The cost seems to increase in multiples of 12 as the value of x increases. I haven't found a pattern.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
contract Example{
function test (uint x) external pure returns(bool z) {
if(x > 0)
z=true;
else
z=false;
}
}
https://github.com/wolflo/evm-opcodes/blob/main/gas.md#a0-0-intrinsic-gas
The bytes sent to the contract indeed decides the gas cost as can be seen from the link.
gas_cost += 4 * bytes_zero: gas added to base cost for every zero byte of memory data
gas_cost += 16 * bytes_nonzero: gas added to base cost for every nonzero byte of memory data
so if you send 0x0001 or 0x0010 it will cost the same amount of gas. But if you send 0x0011 it will cost 12(16-4) gas more than the previous case.
Gas is charged in three scenarios
The computation of an operation
For contract creation or message calls
An increase in the use of memory. Function args and local variables in functions are memory data. In Ethereum Solidity, what is the purpose of the "memory" keyword?

TransactionError when using Brownie on Optimism - Tx dropped without known replacement

I have a Python script using Brownie that occasionally triggers a swap on Uniswap by sending a transaction to Optimism Network.
It worked well for a few days (did multiple transactions successfully), but now each time it triggers a transaction, I get an error message:
TransactionError: Tx dropped without known replacement
However, the transaction goes through and get validated, but the script stops.
swap_router = interface.ISwapRouter(router_address)
params = (
weth_address,
dai_address,
3000,
account.address,
time.time() + 86400,
amount * 10 ** 18,
0,
0,
)
amountOut = swap_router.exactInputSingle(params, {"from": account})
There is a possibility that one of your methods seeks data off-chain and is being called prematurely before the confirmation is received.
I had the same problem, and I managed to sort it out by adding
time.sleep(60)
at the end of the function that seeks for data off-chain
"Dropped and replaced" means the transaction is being replaced by a new one, Eth is being overloaded with a new gas fee. My guess is that you need to increase your gas costs in order to average the price.

Solidity gas estimation - always out of gas when setting to 0

I'm trying to figure out a solution to this problem, to do with transaction confirmation order and setting values to 0
pragma solidity ^0.5.17;
contract Test {
uint256 amount;
constructor() public {}
function join() public {
amount += 100;
}
function leave() public {
amount -= 100;
}
}
Given these transactions (tested on ropsten):
tx 1) Call Join Confirmed
amount == 100
tx 2) Call Join (gas price 1) Pending
amount == 100 should tx3 get mined first
tx 3) Call Leave (gas price 100) Pending
amount == 0
However tx 2 will always fail with an out of gas error for as long as the amount is set back to 0. This doesn't happen if the value is any higher than 0. My understanding is that it costs more gas to set a value to its 0 state instead of a positive integer, and the gas estimation isn't taking this into account. I've tried delete hoping this would give a gas refund to compensate for the too-low gas limit, but it still failed.
Is there an elegant way to handle this scenario? The only ways I can think of are over-estimating the gas for all join transactions, which has its obvious drawbacks, or never setting amount back to 0.
You are making correct observations.
by setting it to 0 you delete storage, and get gas refund. This is why it takes less amount of gas. But gas refunds have top limit, so you can't use it to store ETH.
It costs 20,000 gas to store one value in a slot (source:https://github.com/ethereum/go-ethereum/blob/d13c59fef0926af0ef0cff0a2e793f95d46442f0/params/protocol_params.go#L41 )
it costs 2,200 gas to load one value from a storage slot (source: https://github.com/ethereum/go-ethereum/blob/d13c59fef0926af0ef0cff0a2e793f95d46442f0/params/protocol_params.go#L89)
That's why you are seeing different gas consumption values.
Just set your gasLimit to some empirically found roughly-estimated value.
Do a trace of your transaction and you will see all gas consumption values.
Now, the way gas refunds work is that during the state transition function, you're asked for the full gas for the run of your contract. During this run, all gas refunds are accumulated in StateDB (temporary state object). At the end of the state transition function, you will get refunds for all storage releases your contract is going to make. This is why you have to set higher gas limit that Etherscan shows, because lets say your contract needs 15,000 gas to run, after storage is released (say for 5,000 gas) , Etherscan will show like the transaction needed 10,000 gas. This is not true because you have got gas refunds at the end, but at the beginning you needed the whole amount of gas (15,000). Gas refunds are sponsored by the miner, his account is going to get less ETH because he is paying you these refunds.

What happens when the sender run out of gas in the middle of a function execution?

Suppose we have a contract with the following defined function:
function send(address receiver, uint amount) public {
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
and suppose that sender runs out of gas just after the following line:
balances[msg.sender] -= amount;
What happened with the state variables? Are incomplete tx included in the block or not?
If you run out of gas in the middle of a transaction it will fail. You will only pay for the computation used, meaning that all the gas used until it failed will not be returned, but the rest will.
You can read more about gas in this chapter of the Ethereum Book
A transaction that runs out of gas will fail and none of the state variables will be updated. Failed transactions are still included in a block, as you can see in this out of gas example.
In your example, balances[msg.sender] -= result will not be executed, and balances[msg.sender] will remain the exact same as before the transaction.
The sender of the transaction will still pay a fee to the miner for including the transaction in the block.
This post does a good job of walking through various failure scenarios.