Why does gas cost depends on function input? - ethereum

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?

Related

How does assigning to `uint` save "storage read" costs in Ethereum?

I'm taking a look at the uniswapv2 tutorial walkthrough.
The followng is in reference to this function in the github repo and the tutorial states the following:
uint _kLast = kLast; // gas savings
The kLast state variable is located in storage, so it will have a
value between different calls to the contract. Access to storage is a
lot more expensive than access to the volatile memory that is released
when the function call to the contract ends, so we use an internal
variable to save on gas.
So in traditional programming, _kLast would be a reference to kLast. _kLast is referenced 3 more times after it's instantiation.
Had they used just kLast as the variable, and not assigned it to uint, would it cost a storage read each time kLast is used?
If this is NOT the case, then I really don't understand how they're saving on gas, can someone explain?
Each storage read (opcode sload) of the same slot costs 2,100 gas the first time during a transaction, and then 100 gas each other time during the same transaction. (After the EIP-2929 implemented in the Berlin hardfork. Before that, it was 800 for each read no matter how many times you performed the reads.)
Each memory write (opcode mstore) and each memory read (opcode mload) cost 3 gas.
So in traditional programming, _kLast would be a reference to kLast
In this particular Solidity snippet, _kLast is not a reference pointing to the storage. It's a memory variable that has value assigned from the storage variable.
So 3 storage reads - not creating the memory variable - would cost 2,300 gas (= 2,100 + 100 + 100).
But because the code creates the memory variable, it performs one storage read (2,100 gas), one memory write (3 gas), and three memory reads (3 x 3 gas) - totalling 2,112 gas. Which is a cheaper option.
Some other EVM-compatible networks, such as BSC, might still use the original gas calculation of 800 per each sload. Which would make even larger difference - non-optimized 2,400 gas (3 x 800), and optimized 812 gas (800 + 3 + 3x3).

Are there more complex functions to call from within Solidity (Smart Contracts/Ethereum)?

I am wondering besides these below mathematical expressions are there any other functions available to call inside a smart contract? Like math functions, like pi, sin, cosine, random() etc?
I am wondering if one can write smart contracts that require a little more than just basic arithmetic.
Below Image is taken from this page:
https://docs.soliditylang.org/en/develop/cheatsheet.html#function-visibility-specifiers
Solidity doesn't natively support storing floating point numbers both in storage and memory, probably because the EVM (Ethereum Virtual Machine; underlying layer) doesn't support it.
It allows working with them to some extent such as uint two = 3 / 1.5;.
So most floating point operations are usually done by defining a uint256 (256bit unsigned integer) number and another number defining the decimal length.
For example token contracts generally use 18 decimal places:
uint8 decimals = 18;
uint256 one = 1000000000000000000;
uint256 half = 500000000000000000;
There are some third-party libraries for calculating trigonometric functions (link), working with date time (link) and other use cases, but the native language currently doesn't support many of these features.
As for generating random numbers: No native function, but you can calculate a modulo of some pseudo-random variables such as block.hash and block.timestamp. Mind that these values can be (to some extent) manipulated by a miner publishing the currently mined block.
It's not recommended to use them in apps that work with money (pretty much most of smart contracts), because if the incentive is big enough, there can be a dishonest miner who can use the advantage of knowing the values before rest of the network and being able to modify them to some extent to their own benefit.
Example:
// a dishonest miner can publish a block with such params,
// that will result in the condition being true
// and their own tx to be the first one in the block that executes this function
function win10ETH() external {
if (uint256(blockhash(block.number)) % 12345 == 0) {
payable(msg.sender).transfer(10 ether);
}
}
If you need a random number that is not determinable by a miner, you can use the oracle approach, where an external app (called oracle) listens to transactions in a predefined format (generally also from&to specific addresses), performs an off-chain action (such as generating a random number, retrieving a google search result, or basically anything) and afterwards sends another transaction to your contract, containing the result of the off-chain action.

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.

Is there any API that I can call inside of the smartcontract that can get a transaction detail by txid?

I'm learning ethereum developing and want to check the transaction detail by txid in the smart contract, but I didn't find any interface that can help me to do that, anyone has any clue?
I think you want to call getTransaction-like RPC call in smart contract. However, that's not possible.
All global variables which can be used in Solidity is following.
Global Variables
block.coinbase (address): current block miner’s address
block.difficulty (uint): current block difficulty
block.gaslimit (uint): current block gaslimit
block.number (uint): current block number
block.blockhash (function(uint) returns (bytes32)): hash of the given block - only works for 256 most recent blocks
block.timestamp (uint): current block timestamp
msg.data (bytes): complete calldata
msg.gas (uint): remaining gas
msg.sender (address): sender of the message (current call)
msg.value (uint): number of wei sent with the message
now (uint): current block timestamp (alias for block.timestamp)
tx.gasprice (uint): gas price of the transaction
tx.origin (address): sender of the transaction (full call chain)
sha3(...) returns (bytes32): compute the Ethereum-SHA3 hash of the (tightly packed) arguments
sha256(...) returns (bytes32): compute the SHA256 hash of the (tightly packed) arguments
ripemd160(...) returns (bytes20): compute RIPEMD of 256 the (tightly packed) arguments
ecrecover(bytes32, uint8, bytes32, bytes32) returns (address): recover public key from elliptic curve signature
addmod(uint x, uint y, uint k) returns (uint): compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256.
mulmod(uint x, uint y, uint k) returns (uint): compute (x * y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2**256.
this (current contract’s type): the current contract, explicitly convertible to address
super: the contract one level higher in the inheritance hierarchy
selfdestruct(address): destroy the current contract, sending its funds to the given address
.balance: balance of the address in Wei
.send(uint256) returns (bool): send given amount of Wei to address, returns false on failure.
Of course, there is some tricky solution which is using Oraclize.
I recommend to see this website :) https://docs.oraclize.it/
In conclusion, for your question, getting transaction detail from solidity is not possible in native-way, you should use offchain solution like Oraclize. :)
Smart Contract only has the Access to the current state of the blockchain. Solidity is use to only creating the rules for the Transactions and updating the state of the variable. For getting the Transaction you have to use web3 libraray.

Assert vs. require within Solidity?

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).