Say we have a contract with a dynamic state array fooArray of uint256. At the same contract there is a function with the following loop:
for (uint256 i = 0; i < fooArray.length; ++i) {
// Some code here, that does not modify array in any way.
}
Does checking the loop condition require reading the length of the fooArray from the storage on each iteration or it is not considered as reading a value from the storage?
Should I preliminarily save length of the array to a local variable and use this variable as a loop condition to save gas?
I created new contract and then executed it 3 times without temp variable and then again created new contract and executed with temp variable. Results from Remix Javscript VM and Rinkeby networks were precisely the same and function with temp variable required less gas. It is possible to read bytecode and Gas Costs from Yellow Paper however I think it is quite clear that it is better to use temp variable.
Solidity code:
pragma solidity ^0.8.0;
contract EstimateGas {
uint[] public fooArray;
constructor (){
for (uint256 i = 0; i < 500; ++i) {
fooArray.push(i);
}
}
function changeNoTemp() public{
for (uint256 i = 0; i < fooArray.length; ++i) {
fooArray[i] = fooArray[i] +1;
}
}
function changeWithTemp() public{
uint arrayLen = fooArray.length;
for (uint256 i = 0; i < arrayLen; ++i) {
fooArray[i] = fooArray[i] +1;
}
}
}
Gas execution costs
changeNoTemp()
1) 2965426 gas
2) 2948326 gas
3) 2948326 gas
changeWithTemp()
1) 2911461 gas
2) 2894361 gas
3) 2894361 gas
Related
I am new to solidity and for this project I am trying to do a reverse lottery drawing, where the losers are drawn and appended until only the winner remains.
I keep getting a gas estimation error:
Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
gas required exceeds allowance (29970705)
I have tried upping my gas when I deploy on remix and all that does is up my cost when I run - it currently estimates 75000ethererum for running: drawLoser()
I use fulfullRandomWords which calls Chainlink VRF - which gives me a verified random seed, I then use this seed in drawLoser(). current_supply is hardcoded at 10000 for testing. I then use my random seed to make a random number and check if that number exists in my losers array, if it does not exist, I increment and generate a new number from the same seed and repeat until I find a new number. I am trying to do all of this without storing anything except my uint16 entry in my loser array, but I am guessing I am doing something stupid and storing more than I realize on the blockchain since the gas fee is absurd.
Thank you for any help!
function fulfillRandomWords(
uint256, /* requestId */
uint256[] memory randomWords
) internal override {
s_randomWords = randomWords;
}
function exists1(uint16 num) public view returns (bool) {
for (uint i = 0; i < losers.length; i++) {
if (losers[i] == num) {
return true;
}
}
return false;
}
//Right now I need to fulfill randomwords and then drawLoser - draw loser does not generate a new seed
function drawLoser() public {
//fulfillRandomWords;
uint16 drawing;
uint i = 0;
uint j = 1;
//generate 10% of total entrys as losers
while(i < 10*(current_supply-getCountLosers())/100) { //current_supply is entrys in lotto
drawing = uint16(uint256(keccak256(abi.encode(s_randomWords[0], j)))%(current_supply-losers.length)+1);
if (exists1(drawing) == true){
j++;
}
if (exists1(drawing) == false){
losers.push(drawing);
i++;
}
}
}
error: Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending? gas required exceeds allowance (29970705)
This means that your transaction is going to fail to tripped require() or revert.
I've been trying to store 10000 16byte strings (the string that I used is 'abcdefghijklmnop') in a solidity mapping: mapping(uint256 => string)
I've tried sending all 10000 strings in one transaction and it always exceeds the maximum gas limit and fails.
Divided those 10000 strings into equal 10 arrays and tried to do 10 transactions.. The first transaction, with the first 1000 strings happens successfully but the following transactions fail due to a gas issue
Solidity Code:
uint256 public totalGenomes;
mapping(uint256 => string) public genomesList;
function addGenome(string[] memory genome) public {
for (uint256 index = totalGenomes; index < (1000 + totalGenomes); index++) {
genomesList[index] = genome[index];
}
totalGenomes += 1000;
}
Hardhat script: there are 10 files genomes0.txt, genomes1.txt, genomes2.txt .... each containing 1000 lines of 'abcdefghijklmnop'
for (i = 0; i < 10; i++) {
let filepath = `./img/genomes${i}.txt`;
let genomes1000 = [];
genomes1000 = fs.readFileSync(filepath).toString().split('\n');
console.log(filepath);
await svgNFT.addGenome(genomes1000);
}
Error:
Error: cannot estimate gas; transaction may fail or may require manual gas limit
I also intend to minimise the gas price for the whole process.
Have anyone here got prior experience on such a scenario?
UPDATE: I got past the above problem.
I did this suggested by Miguel_LZPF on Hardhat discord:
contract.function(param1, param2..., lastfunctionparam, {gasLimit: xxxxx})
and define the gasLimit and gasPrice manually. And now, I'm stuck in another problem.
https://rinkeby.etherscan.io/address/0x11740C2367a0F0465d31b3612B3A5464dC7c8Afb
Warning! Error encountered during contract execution [execution
reverted]
Still the first transaction in the loop happens to be successful the rest fails.
suppose I have this solidity contract, I would like to calculate the result of minAmountOut2, without actually paying the gas gas fee. Is it possible to achieve it? I think this should be theoretically possible, but I'm not sure how to achieve it practically.... Thanks!
pragma solidity >=0.7.0 <0.9.0;
contract Storage {
constructor() payable {
// uint256 number;
address wbnb_addres = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
address pancake_swap_v2 = 0x10ED43C718714eb63d5aA57B78B54704E256024E;
uint amount = msg.value ;
address target_token_address = 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82;
address sender_address = tx.origin;
address[] memory address_input = new address[](2);
address_input[0] = wbnb_addres;
address_input[1] = target_token_address;
uint[] memory result = IPancakeRouter02(pancake_swap_v2).getAmountsOut(amount,address_input);
uint minAmountOut = result[1];
uint deadline = 1e30;
address[] memory address_output2 = new address[](2);
address_output2[0] = target_token_address;
address_output2[1] = wbnb_addres;
uint[] memory result2 = IPancakeRouter02(pancake_swap_v2).getAmountsOut(minAmountOut,address_output2);
uint minAmountOut2 = result2[1];
}
}
One possible way is that I convert the result into string and trigger a revert function.... Then when I run estimatgas function in web3, then i got the result using error and exception handling.
Far as I know estimateGas is the only such function. estimateGas should be almost equal to web3.eth.sendTransaction, the only difference between them is estimateGas does not send the transaction out actually, and if your operation will fail when call a function, you can not get a valid result by estimateGas.
You have now all code in constructor. You probably want to split the code up in smaller functions and then call estimateGas.
I have a state variable in my smart contract called "_ownerAddresses". It is an array of addresses. In one of my functions, I remove addresses from this array depending on a condition. I was wondering what is the best approach to doing this in a fast and memory-efficient way. Should I delete addresses while in the for loop and shrink the array? Should I create a new storage array that just adds the addresses I am keeping and then assign "_ownerAddresses" to this new array? Here is the excerpt of code:
struct Sub {
uint256 amount;
uint256 createdTime;
}
mapping(address => Sub) private _addrBalances;
address[] private _ownerAddresses;
uint MONTH = 30 days;
uint256 private _totalSupply;
function expireAccounts() external onlyOwner(_owner) {
address[] storage _ownersLeft;
for (uint i = 0; i < _ownerAddresses.length; i++) {
if ((_addrBalances[_ownerAddresses[i]].createdTime + MONTH) >= block.timestamp) {
_totalSupply -= _addrBalances[_ownerAddresses[i]].amount;
_addrBalances[_ownerAddresses[i]].amount = 0;
_addrBalances[_ownerAddresses[i]].createdTime = 0;
} else {
_ownersLeft.push(_ownerAddresses[i]);
}
}
_ownerAddresses = _ownersLeft;
}
Any input would be great. Thanks!
Once my team conducted a study of the efficiency of EVM in processing iterative cycles - the results are reflected in the article https://habr.com/ru/company/raiffeisenbank/blog/354122/ (unfortunately, in Russian). In short, with the number of iterations over 1000, the cost of even the simplest operations will reach millions of gas units. Therefore, if the size of the array is several thousand, then the cost of execution expireAccounts() can become very high, and there is even a risk that it will exceed the gas limit per block and will not be executed at all.
Therefore, instead of concentrating the "expire" functionality in a single function for all accounts, I would insert it into the functions for processing the single accounts. Most likely, in this case, you can also refuse the _ownerAddresses array.
I know that if I have a function like:
public int addOne(int a){
return (a+1)
}
The time complexity order will be O(1) since we only do one operation (the sum).
But what if I have a function that doesn't do any operations, just assigns some values to some global variables. Like this:
public void assignValues(){
a = 2;
b = 3;
c = 4;
//maybe more
}
What would the time complexity be for this function? My guess is that it would still O(1). Is that correct?
When you discuss the time complexity of an algorithm, you first have to define the variable parameter(s). For example, it doesn't make any sense to say that something is O(n) without defining what you measure by n (e.g. the length of an array? The size of the contents of an array? The bit-length of a number? The absolute value of an integer?).
In your particular case, you have a function that takes no parameters. Assuming that the operations within the function don't depend on any other external parameters, the complexity of such a function is always trivially O(1), no matter what operations you perform inside. For example, the following function is also O(1):
public static int DoSth() {
int x = 0;
const int n = 1000000;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
x++;
return x;
}
As already mentioned, this assumes that the parameter-less function has no external dependencies. Consider e.g. the following function
public static void DoSth() {
int n = DateTime.Now.Ticks;
for(int i = 0; i < n; i++)
Console.WriteLine(i);
}
This function is O(n log n) if n is the amount of time that has passed since 1.1.0001.