Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save gas by writing to storage once rather than twice in .updateOffer() #46

Open
ghost opened this issue Nov 19, 2018 · 5 comments
Open
Assignees

Comments

@ghost
Copy link

ghost commented Nov 19, 2018

Description

.updateOffer() updates both the offer.total property and the offer.expiresAt property of an offer struct.

However, it writes each of these changes in two separate writes to storage.

We can save gas by making a single write to storage, rather than two.

Scenario

Every time that .updateOffer() is called to raise the price of an offer, we waste gas by writing to storage twice rather than once.

Impact

Medium: It costs 5,000 gas for each write to storage. If we do a single write to storage (and pack the properties more efficiently as mentioned in Issue #45), we can save 5,000 gas every single time the function is run.

Reproduction

(1) In .updateOffer(), we retrieve a pointer to the offer struct that we will update:

Offer storage offer = tokenIdToOffer[_tokenId];

(2) If msg.value is greater than zero, we update offer.total. Since this is a pointer to storage that we are updating, this costs 5,000 gas, a very expensive operation gas-wise:

offer.total += uint128(msg.value);

(3) Only after that do we update a different property of the same struct in storage, again costing us another 5,000 gas:

offer.expiresAt = uint64(newExpiresAt);

Fix

Instead, we can make both changes locally, and then do a single write to storage. If we combine this with the better struct-packing from Issue #45, we will save 5,000 gas on every write. Change this:

uint256 newExpiresAt = now + globalDuration;

// Check if the caller wants to raise the offer as well
if (msg.value > 0) {
    // Set the new price
    offer.total += uint128(msg.value);
}

offer.expiresAt = uint64(newExpiresAt);

to this:

uint256 newExpiresAt = now + globalDuration;

Offer updatedOffer = offer;

// Check if the caller wants to raise the offer as well
if (msg.value > 0) {
    // Set the new price
    updatedOffer.total += uint128(msg.value);
}

updatedOffer.expiresAt = uint64(newExpiresAt);

offer = updatedOffer;

We would also need to reorder the properties of the Offer struct, as outlined in Issue #45.

@sunsetlover
Copy link

sunsetlover commented Nov 19, 2018

Not sure if you saw my comment here: #39 (comment), but it is related. Initially, I thought the same as you. However, I am doubtful now that you can indeed save 5k gas by just updating 1 word in the 2 word struct due to the solidity compiler. I think the problem is--at least compiled with solidity-- you are rewriting both of the words in the struct even if one word stays the same (i.e. the code calls SSTORE twice) by setting it equal to an Offer type variable.

I just tried this with remix and it costs 10k gas to update according to your fix, so it does not save any gas.

The only fix I can think of to save gas is to create two mappings. One that contains the expiry time of an offer and one that contains the Offer struct except with the expiry time removed (so it is a struct with one 256 word). Then, you will only need to update the second mapping when someone updates their bid and it will only cost 5k gas even though you are changing two variables in the struct.

@ghost
Copy link
Author

ghost commented Nov 19, 2018

This issue still stands even if it's the case that we can only ever write the entire struct to storage.

The following lines are two separate writes to storage:

updatedOffer.total += uint128(msg.value);
updatedOffer.expiresAt = uint64(newExpiresAt);

It still would save gas to write to storage once rather than twice by using the temporary variable updatedOffer, as outlined above.

@hwrdtm hwrdtm self-assigned this Nov 19, 2018
@hwrdtm
Copy link
Contributor

hwrdtm commented Nov 19, 2018

Thanks @michaelKim4736 for the feedback! We will take this into consideration.

@sunsetlover
Copy link

Ah I see. Right. I guess another way to even save on top of that is two create two mappings as I’ve suggested. Then it would only cost 5k instead of the current 20k.

@arthcmr
Copy link
Contributor

arthcmr commented Nov 27, 2018

Thanks for your participation, @michaelKim4736! Our team has reviewed your submission, and we are pleased to reward you for your report.

Severity: Note
Points: 50

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants