2

A contract has a state variable, which is complex. For the example, lets say it is a mapping of structs. Inside a method, I want to access that and store the changes. Like so:

contract FooManager {
  struct Foo {
    uint expiresAt;
    uint balance;
  }
  mapping(address => Foo) public fooIndex;

  public function claimFrom(address minter) public payable {
    require(fooIndex[minter].balance < amount);

    fooIndex[minter].balance += msg.value;
    fooIndex[minter].expiresAt += 1000;
  }
}

In this simple example, the repetition of fooIndex[minter].method is still manageable and readable. But with e.g. child mappings in, say the Foo struct, it quickly becomes tangled and ugly. fooIndex[minter].claimants[msg.sender].balance = msg.value are no exceptions there.

What is the common pattern to DRY this? I can create a memory-variable, like below, but that is not stored. The extra gas for allocating memory, may become an issue if this pattern is repeated, but is proably negligible in most cases.

  public function claimFrom(address minter) public payable {
    Foo memory thisFoo;
    thisFoo = fooIndex[minter];
    require(thisFoo.balance < amount);

    thisFoo.balance += msg.value;
    thisFoo.expiresAt += 1000;
  }
}

I've seen some examples in which, at the end, the memory-variable gets assigned back to the storage variable. Like so:

  public function claimFrom(address minter) public payable {
    Foo memory thisFoo;
    thisFoo = fooIndex[minter];
    require(thisFoo.balance < amount);

    thisFoo.balance += msg.value;
    thisFoo.expiresAt += 1000;

    fooIndex[minter] = thisFoo;
  }

It works, but seems clumsy to me: assigning the wrong thing, or forgetting to re-assign is an obvious mistake to make. It also seems wasteful to me, esp in the case of large structs, in which only the dirty (changed) attributes need to be overridden, and not the entire struct; again, this is more obvious in situations where a struct contains child-mappings with other structs and so on: a tree. I'm not sure, if the compiler can and will optimize this, though.

Did I miss an obvious language construct? Did I misunderstand the storage and memory completely?

2 Answers 2

2

The point is that it is cheaper to use memory variables for elaboration than storage variables.

The approach to use a temporary memory variable is efficient and clean, just make two calculation in order to understand if it is convenient in your particular case.

I mean: if you just add a value to the storage variable and that’s all, it can be evaluated, may be it is not the case; on the contrary in the case you access ten times the temporary variable and then store the value in the storage variable, there are no doubts.

Evaluate your needing: the more clean is the more convenient.

(You can find gas cost for accessing and creating temp variables in the yellow paper or elsewhere very easily)

1

When creating a variable in memory and assigning to it your storage variable you are copying the whole struct into memory. If your struct has many fields and you only use a few of them you are wasting gas.

function claimFrom(address minter) public payable {
    Foo memory thisFoo;
    thisFoo = fooIndex[minter];
    require(thisFoo.balance < amount);

    thisFoo.balance += msg.value;
    thisFoo.expiresAt += 1000;
  }
}

Also this is a copy if you modify it those changes will be discarded unless you store it again.

If you only use a few fields and need to update those fiels it is probably better if you assign to a storage reference

function claimFrom(address minter) public payable {
    // This will create a variable reference to storage
    Foo storage thisFoo = fooIndex[minter];
    require(thisFoo.balance < amount);

    thisFoo.balance += msg.value;
    thisFoo.expiresAt += 1000;
  }
}

Since the variable is a reference to storage the changes to it are immediately reflected in storage.

One use case where assigning to memory make sense is if your function is complex and using more variables causes "Stack too deep" error message, and you do not want to update the storage.

function calcBalanceFrom(address minter, uint value) public payable {
    Foo memory thisFoo = fooIndex[minter];

    thisFoo.balance += (msg.value + thisFoo.value) / value / 2; // complex formula
    thisFoo.expiresAt += 1000 + (thisFoo.balance / 10**12);

    emit NewBalance(thisFoo.balance, thisFoo.expiresAt);
  }
}

All of this only applies when the compiler optimizations are disabled. When optimizations are enabled the compiler might simplify some of these operations (or not). With optimizations enabled it is better to try several approaches.

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.