1

I have a struct where I want to increment an item across all struct members. For example, say there is a struct 'Contract', and new customers get their own bytes32 ID, which points to their own struct:

mapping(bytes32 => Contract) public contracts;
bytes32[] public idList;
struct Contract {
        uint data;
        uint vintage;
}

I want to increment each struct contract item vintage by +1 at some time. One way to do this would be to loop through the ID array, pull out their contract, and increment the item.

for (uint i = 0; i < idList.length; i++) {
        Contract storage k = contracts[idList[i]];
          k.vintage += 1;
      }

Is there a way to effectively invoke

*.vintage += 1; 

So all the contracts structs have the element vintage incremented in one swoop?

1 Answer 1

1

No. It would be expensive and it would not scale.

The name of the game is to economize on writes and avoid loops. It's so important that your approach to problems like this may seem counter-intuitive, at first.

You have to work out what the value would be if the data was updated in place. For that, you would just need to know the original number and the number of increments that would have happened.

pragma solidity 0.5.8;

contract Increment {

    uint epoch;

    mapping(bytes32 => Contract) private contracts; // suggest rename

    bytes32[] public idList;
    struct Contract { // here too
            uint data;
            uint vintage;
    }

    function newContract(bytes32 id, uint data, uint vintage) public {
        idList.push(id); // warn: does not check for duplicates
        Contract storage c = contracts[id];
        c.data = data;
        c.vintage = vintage;
    }


    function setEpoch(uint e) public {
        epoch = e;
    }

    function getContract(uint row) public view returns(bytes32 id, uint data, uint age) {
        require(row <= idList.length);
        id = idList[row];
        Contract storage c = contracts[id];
        data = c.data;
        age = epoch - c.vintage; // <== the magic Incrementer 
    }
}

The contracts (suggest renaming that) are marked private to discourage direct use. Instead, rely on the getContract() function to return the computed result you are looking for. I've used a simple setter for the epoch but it can evolve incrementally.

Evolving states can be more intricate than this trivial example. It might be necessary, for example, to iterate some recent transactions to work out the current state. For example, consider a user balance that is the stored balance, plus dividends received since the stored balance was last updated. That also presents a scaling challenge because eventually there will be too many dividends to process it on-the-fly at a reasonable cost.

A solution to that problem is to amortize the work and attend to housekeeping whenever someone is willing to pay for gas.

Suppose you have a function to work out balances on the fly:

function computeBalance(address account) public returns(uint balance) {
  // get stored Balance 
  // add unprocessed dividends 
  // update stored balance <== here's where we sneak in a SSTORE
  // move the cursor/mark dividends processed  <== and here
  // return balance;
}

When that runs in the context of a view transaction, it doesn't update the state, because it can't. It will return the right value and the contract will carry on.

When that runs in the context of a state-changing transaction (doesn't matter who is paying for gas), it will quickly attend to housekeeping on-the-fly. For emphasis, it doesn't matter who is paying for gas, or why. It could be the account owner sending money. It could be someone sending money to the account owner. All that matters is someone is doing something that a) needs the balance and b) costs gas.

You use contractBalance() throughout the contract wherever you need an to up-to-date computed balance. The contract will seize upon any opportunity to perform a little maintenance on individual accounts (never the whole works, because that won't scale).

On rare occasions, there could be so much worked backlogged on inactive accounts that higher-order functions are no longer possible. This is something to be careful about. computeBalance() is public, so anyone who is willing to pay for gas can nudge it forward at the minimum possible cost (no other concerns). It may be handy to watch (off-chain) for dormant accounts and determine a response policy (Pay for it as a public service? Encourage the owner to pay for it? Cancel the subscription? Something else?).

The idea is to avoid iterating a big table at all costs. https://blog.b9lab.com/getting-loopy-with-solidity-1d51794622ad

Hope it helps.

4
  • (epoch - vintage[id]) clearly solves the problem, as then merely the variable epoch needs to be incremented. Thanks. Commented Oct 26, 2019 at 18:07
  • Thanks for accepting and upvoting the answer if you found it useful. Commented Oct 26, 2019 at 19:57
  • alas, I have too few points to move the upvote! Commented Oct 28, 2019 at 13:52
  • You can mark the answer correct. Commented Oct 28, 2019 at 14:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.