Best memory practices and optimizations for Solidity gas fees - optimization

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.

Related

Smart Contracts - How to agree on a value between parties?

I am writing a smart contract in Solidity about co-selling an NFT.
When initializing the contract, a division for the benefits is chosen by one of the co-seller. For example, three co-seller could choose 50%, 25%, 25%.
My goal is that each co-seller must agree on the division value before the selling can take place. As I want to do it "on-chain", I'm wondering what is the best way to do it.
Include a function in the smart contract that set an "agreed" boolean for each co-seller
Ask the co-seller to sign the current repartition and send it to the smart contract
Other
As a bonus, I'm also wondering what to do in case of a disagreement. Is there any known pattern for negotiation of values "on-chain" ?
Thanks in advance for your answers :)
let me give this a shot:
contract NFT_Price_Agreement {
bool reachedAgreement = false;
uint numParties = 3;
uint numSigned = 0;
uint totalVal = 0;
uint nftPrice = 1000000000000000000; // = 1 ether
mapping(address => uint) public nftStake;
function makeReq(uint reqStake) public {
uint stake = nftPrice * (reqStake / 100);
totalVal += stake; // to make sure 100% ownership at the end
nftStake[msg.sender] = stake;
numSigned++;
}
function confirmReq() public {
require(numSigned == numParties, "Not everyone has agreed on what percent ownership.");
require(totalVal == nftPrice);
reachedAgreement = true;
}
}
While this will currently yield an error, this is the general idea. To abort request and make a new one, you will just have to have a function that restates what your mappings are to 0 for their requested stake and decrement 1 from numSigned. Even though this code has some issues, this is the general idea.

Synthetix Staking contract epochs lifecycle

I am trying to understand how to make a staking contract that blocks funds for a period of time and give rewards in epochs for the period that the funds are locked. I understand that synthetix contract is a very good starting point, but I have some trouble with understanding how their epochs works
How the Synthetix Staking Contract starts a new epoch? From what I am seeing, they have a function notifyRewardAmount which seems to add a reward duration and an amount for that period. But how they call this function with precision on each end of a period?
function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) {
if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(rewardsDuration);
}
// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint balance = rewardsToken.balanceOf(address(this));
require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
emit RewardAdded(reward);
}
I believe that they are using some sort of a script, but this is a single point of failure and sounds like it's a bad idea to do

Reverse Lotto Search has CRAZY fees (new to solidity): Gas estimation errored with the following message

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.

How can i set 2 max supply's in a ERC721

I have a problem. The problem is that i want a max mint supply for the whitelist sale and public sale. So for example;
In total i have 10.000 NFT's. There will be an whitelist sale and a public sale. For the whitelist sale i have 2.000 wallet addresses, but i only want them to be able to mint 1.500 NFT's. and in the public sale i want the remaining 8.500 NFT's to be sold.
I already tried somethings with the normal maxsupply but can't figure it out. I want a max mint per wallet like 10 and cant seem to limit the supply to 1500 for only the whitelist
Does anyone can explain this to me or have a code example?
I don't recommend limiting your whitelist per-wallet minting amount (you want to mint out, don't you?!), but I understand the reasons why you might. I'll provide both options. All code is abbreviated for brevity sake:
*Note 1: We will be using OpenZeppelin Utilities - Counters for tracking minted progress. You can also consider using totalSupply(), however, if burned tokens are a concern totalSupply() will decrement and throw off your count, whereas Counters will not.
Note 2: This assumes you're whitelist occurs before public and that you're not also juggling a reserve count as well - additional checks and counters would be required for that.
Note 3: This covers ONLY the check for limiting whitelist; you will obviously also need additional checks for valid whitelist account, sufficient payment, etc.
Limit Whitelist Total Supply
...
import "#openzeppelin/contracts/utils/Counters.sol";
...
error ExceededWhitelistSupply();
...
using Counters for Counters.Counter;
uint256 public maxSupply = 10000;
uint256 public maxWhitelistSupply = 1500;
Counters.Counter private totalWhitelistSupply;
...
function mintWhitelist(uint256 _qty) external payable {
if ( totalWhitelistSupply.current() + _qty > maxWhitelistSupply ) revert ExceededWhitelistSupply();
for (uint256 i = 0; i < _qty; i++) {
totalWhitelistSupply.increment();
}
_mint(msg.sender, _qty, '', true);
}
Limit Wallet && Limit Whitelist Total Supply
...
import "#openzeppelin/contracts/utils/Counters.sol";
...
error ExceededWhitelistSupply();
error ExceededMaxPerWallet();
...
using Counters for Counters.Counter;
uint256 public maxSupply = 10000;
uint256 public maxWhitelistSupply = 1500;
uint256 public maxWhitelistPerWallet = 10;
Counters.Counter private totalWhitelistSupply;
mapping(address => uint256) public whitelistMintedAmount;
...
function mintWhitelist(uint256 _qty) external payable {
if ( whitelistMintedAmount[msg.sender] + _qty > maxWhitelistPerWallet ) revert ExceededMaxPerWallet();
if ( totalWhitelistSupply.current() + _qty > maxWhitelistSupply ) revert ExceededWhitelistSupply();
for (uint256 i = 0; i < _qty; i++) {
totalWhitelistSupply.increment();
}
whitelistMintedAmount[msg.sender] += _qty;
_mint(msg.sender, _qty, '', true);
}
Here, we've used mapping - good tutorial here - to track the number of NFTs that have been minted to this wallet (preferred method, as this won't be fooled by the account transferring NFTs out of the wallet and then minting more). If you want to go WAY down the rabbit hole, you can also look at trash-canning this whole approach and learn up on this approach for handling your whitelist.
Keep in mind that there are more checks and balances that you'll need to add (e.g., visualizing this in the front end of your dApp to avoid minting when they shouldn't be able to, additional validation layers in your mint functions, etc.), but this should provide you with the core pieces needed for limiting by a max wallet and max supply. I apologize for any code errors - this is my first StackOverflow answer and the short-handing and readability of the code is a bit difficult to error check.
Have a whitelistMaxSupply
Have a getMaxSupply function
Have a finishWhitelist function
Have a maxSupply
Unless whitelist finished, getMaxSupply will return whitelistMaxSupply, after finished, it will return maxSupply
Profit
There are many ways to solve your problem, this is just the first one that came to my mind
function getMaxSupply() view public returns(uint256){
if(whitelistFinished){
return maxSupply;
}
return whitelistMaxSupply;
}
function finishWhitelist() public{
whitelistFinished = true;
}

First function call success, second call fails. (Solidity with Hardhat)

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.