-
Notifications
You must be signed in to change notification settings - Fork 196
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
feat(store): prevent onchain state and indexer state from diverging #1581
Conversation
…ison" This reverts commit 31fe746.
🦋 Changeset detectedLatest commit: 7893115 The changes in this PR will be included in the next version bump. This PR includes changesets to release 29 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
packages/store/gas-report.json
Outdated
"gasUsed": 103978 | ||
"gasUsed": 104908 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gas only increased around 1k for set record here (i expected 2k for the additional sload, but seems like some of it was offset by less memory being passed around between functions and the optimized getFieldLayout
)
.changeset/wicked-squids-do.md
Outdated
``` | ||
|
||
- All methods that are only valid for dynamic fields (`pushToField`, `popFromField`, `updateInField`, `getFieldSlice`) | ||
have been renamed to make this more explicit (`pushToDynamicField`, `popFromDynamicField`, `updateInDynamicField`, `getDynamicFieldSlice`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we make updateInDynamicField
more like a spliceDynamicField
function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is a spliceDynamicField
function too! updateInDynamicField
was there before but now basically just calls spliceDynamicField
with the correct deleteCount
value and does a range check. In fact the range check is not even necessary, since _spliceDynamicField
does a range check already. Could argue updateInDynamicField
not really necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I am leaning towards us removing, the name was always confusing to me
- `IStore` now exposes `setStaticField` and `setDynamicField` to save gas by avoiding the dynamic inference of whether the field is static or dynamic. | ||
|
||
- The `getDynamicFieldSlice` method no longer accepts reading outside the bounds of the dynamic field. | ||
This is to avoid returning invalid data, as the data of a dynamic field is not deleted when the record is deleted, but only its length is set to zero. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious about the gas difference between actually deleting and setting length to zero - isn't there a gas return when freeing storage slots?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so too and actually implemented the actual removal today. Surprisingly setting it to 0 was a lot more expensive, and the cost grew linearly. Eventually I realized that you still have to provide the entire gas for the transaction to complete, and at the end of the tx receive up to 20% refund. So in the end it's almost always cheaper for us to not touch the dynamic data at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See 004fe9c and the following 3 commits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry ethereum for the state bloat we're about to create
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's what ethereum gets for being so stingy with the refund
@@ -8,7 +8,7 @@ When a record or a single field is edited, or when a record is deleted, Store em | |||
|
|||
```solidity | |||
event Store_SetRecord(bytes32 indexed tableId, bytes32[] keyTuple, bytes staticData, bytes32 encodedLengths, bytes dynamicData), | |||
event Store_SpliceStaticData(bytes32 indexed tableId, bytes32[] keyTuple, uint48 start, uint40 deleteCount, bytes data), | |||
event Store_SpliceStaticData(bytes32 indexed tableId, bytes32[] keyTuple, uint48 start, bytes data), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry lermchair
packages/common/src/getByteLength.ts
Outdated
@@ -0,0 +1,6 @@ | |||
import { Hex } from "viem"; | |||
|
|||
export function getByteLength(data: Hex): number { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
viem has size
for this purpose: https://github.com/wagmi-dev/viem/blob/main/src/utils/data/size.ts#L12
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh nice, i figured there must exist something for a super general util like this one
packages/world/gas-report.json
Outdated
}, | ||
{ | ||
"file": "test/WorldDynamicUpdate.t.sol", | ||
"test": "testPopFromField", | ||
"test": "testPopFromDyanmicField", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"test": "testPopFromDyanmicField", | |
"test": "testPopFromDynamicField", |
packages/world/gas-report.json
Outdated
}, | ||
{ | ||
"file": "test/WorldDynamicUpdate.t.sol", | ||
"test": "testPopFromField", | ||
"test": "testPopFromDyanmicField", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"test": "testPopFromDyanmicField", | |
"test": "testPopFromDynamicField", |
Fixes #1449
Fixes #1137
Fixes #1523
Details on the change in the changeset.
More context
setRecord
static data, dynamic data lengths, dynamic datasnumDynamicFields
value infieldLayout
is invalid (e.g. 0), the indexer would override the dynamic field data, while it would stay its previous value onchain.fieldLayout
from storage instead of accepting it as a passed in parameterfieldLayout
as part of the event, so indexers can imitate the onchain behaviorspliceStaticData
static datadata.length
, so we should imitate that behavior offchaindeleteCount
in the event or function signaturedeleteRecord
static data, dynamic data lengthsgetFieldSlice
after deleting the dataspliceDynamicData
dynamic data length, dynamic data ✅previousEncodedLengths
from storage, so can’t be tampered withstartWithinField
is what we use onchain, and we use the provided field index to find the right storage slotdeleteCount
is at the end of the field to guarantee the indexer doesn’t cut into another field or shifts data at the end of the field