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.