-
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,world): add splice hooks, expose spliceStaticData, spliceDynamicData #1531
Conversation
🦋 Changeset detectedLatest commit: 5154f15 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 |
4e0224b
to
25f19ae
Compare
25f19ae
to
8f913f3
Compare
packages/store/src/IStoreHook.sol
Outdated
IStoreHook.onBeforeDeleteRecord.selector ^ | ||
IStoreHook.onAfterDeleteRecord.selector ^ | ||
ERC165_INTERFACE_ID; | ||
|
||
interface IStoreHook is IERC165 { | ||
function onBeforeSetRecord( | ||
bytes32 tableId, | ||
bytes32[] memory keyTuple, | ||
bytes32[] calldata keyTuple, |
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.
will we have similar issues as #1528 (comment) by changing this?
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.
Changed all to memory
. Had calldata
initially bc it would be cheaper if called with calldata, but we're in fact calling all these functions with memory from the World
, so needs to be memory here.
uint8 constant BEFORE_SPLICE_DYNAMIC_DATA = 1 << 4; | ||
uint8 constant AFTER_SPLICE_DYNAMIC_DATA = 1 << 5; | ||
uint8 constant BEFORE_DELETE_RECORD = 1 << 6; | ||
uint8 constant AFTER_DELETE_RECORD = 1 << 7; |
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.
this is what I was referring to btw when I thought we maybe needed more than 8 slots for hooks
do we wanna consider a bump to uint16 or uint32 for future proofing?
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'd prefer to only bump to uint16
once we actually need it (don't think we'll need it for this iteration of the protocol) - although one could argue we could avoid a breaking change if we already use uint16
now and later add additional functionality for other types of hook 🤔
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 my main motivation is reducing breaking changes later
I guess either way it'd require a redeploy of the world, so maybe it's fine to leave this and expand when we need it
@@ -16,7 +16,9 @@ contract EchoSubscriber is StoreHook { | |||
bytes memory dynamicData, | |||
FieldLayout fieldLayout | |||
) public { | |||
emit HookCalled(abi.encode(tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout)); | |||
emit HookCalled( | |||
abi.encodeCall(this.onBeforeSetRecord, (tableId, keyTuple, staticData, encodedLengths, dynamicData, fieldLayout)) |
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.
neat
) public { | ||
if (tableId != tableId) revert("invalid tableId"); |
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.
😅
import { Storage } from "../src/Storage.sol"; | ||
|
||
/** | ||
* Tes helper function to set the length of the dynamic data (in bytes) for the given value field layout and index |
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.
* Tes helper function to set the length of the dynamic data (in bytes) for the given value field layout and index | |
* Test helper function to set the length of the dynamic data (in bytes) for the given value field layout and index |
PackedCounter encodedLengths = PackedCounter.wrap(Storage.load({ storagePointer: dynamicDataLengthSlot })); | ||
|
||
// Update the encoded lengths | ||
encodedLengths = encodedLengths.setAtIndex(dynamicSchemaIndex, newLengthAtIndex); |
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.
encodedLengths = encodedLengths.setAtIndex(dynamicSchemaIndex, newLengthAtIndex); | |
encodedLengths = encodedLengths.setAtIndex(dynamicFieldIndex, newLengthAtIndex); |
@@ -63,11 +63,41 @@ contract KeysInTableHook is StoreHook { | |||
// NOOP | |||
} | |||
|
|||
function onBeforeSetField(bytes32 tableId, bytes32[] memory keyTuple, uint8, bytes memory, FieldLayout) public { | |||
function onBeforeSpliceStaticData(bytes32, bytes32[] calldata, uint48, uint40, bytes calldata) public pure { | |||
// NOOP | |||
} |
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.
if we add method stubs to StoreHook
with a NotImplemented
error, we could remove these here
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.
good idea!
BEFORE_SPLICE_STATIC_DATA | | ||
AFTER_SPLICE_STATIC_DATA | | ||
BEFORE_SPLICE_DYNAMIC_DATA | | ||
AFTER_SPLICE_DYNAMIC_DATA | | ||
BEFORE_DELETE_RECORD | | ||
AFTER_DELETE_RECORD |
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.
a nice thing we can do now that we are operating with bit flags is add shortcuts, e.g. BEFORE_ALL
and AFTER_ALL
like
uint8 constant BEFORE_ALL = BEFORE_SET_RECORD | BEFORE_SPLICE_STATIC_DATA | BEFORE_SPLICE_DYNAMIC_DATA | BEFORE_DELETE_RECORD;
packages/store/src/StoreCore.sol
Outdated
@@ -984,53 +971,13 @@ library StoreCoreInternal { | |||
} | |||
|
|||
/** | |||
* Get the length of the dynamic data for the given value field layout and index | |||
* Load the encoded dynamic data length from storage for the given tableId and key tuple |
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 prob be
given table ID and key tuple
or
given tableId and keyTuple
packages/store/src/StoreCore.sol
Outdated
Storage.store({ storagePointer: dynamicDataLengthSlot, data: encodedLengths.unwrap() }); | ||
// Load the previous length of the field to set from storage to compute where to start to push | ||
PackedCounter oldEncodedLengths = _loadEncodedDynamicDataLength(tableId, keyTuple); | ||
uint40 oldFieldLength = uint40(oldEncodedLengths.atIndex(dynamicFieldIndex)); |
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 tend to prefer "previous" instead of "old" for these sorts of operations, feels a bit more specific/accurate.
packages/store/test/StoreMock.sol
Outdated
) public virtual { | ||
StoreCore.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, startWithinField, deleteCount, data); | ||
} | ||
|
||
// Set partial data at schema index |
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.
// Set partial data at schema index | |
// Set partial data at field index |
there's a few more below too
|
||
function testShorthand() public { | ||
assertEq(ALL, BEFORE_CALL_SYSTEM | AFTER_CALL_SYSTEM); | ||
} |
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.
is this a useful test? 😆
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.
haha just want to make sure ALL
is updated in case we add more hooks
skipping CI, only updated a comment since last CI pass |
onBeforeSetField
andonAfterSetField
, replace withonBeforeSpliceStaticData
,onAfterSpliceStaticData
,onBeforeDynamicData
,onAfterDynamicData
so we don't have to read the entire dynamic field when calling partial field methods (push, pop, update)spliceStaticData
andspliceDynamicData
as methods onIStore
would be really useful for hooks (otherwise even simple hooks like "mirror the data to another table" are very complicated to implement to because the hook data doesn't match any store method)FieldLayout
valuesStoreCore
to use the newspliceStaticData
andspliceDynamicData
instead of duplicating the splice logic in every single dynamic data functionwill add more detailed context to the changeset once this PR is ready