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

feat(store,world): add splice hooks, expose spliceStaticData, spliceDynamicData #1531

Merged
merged 16 commits into from
Sep 19, 2023

Conversation

alvrs
Copy link
Member

@alvrs alvrs commented Sep 17, 2023

  • remove onBeforeSetField and onAfterSetField, replace with onBeforeSpliceStaticData, onAfterSpliceStaticData, onBeforeDynamicData, onAfterDynamicData so we don't have to read the entire dynamic field when calling partial field methods (push, pop, update)
  • while replacing the hooks I realized that exposing spliceStaticData and spliceDynamicData as methods on IStore 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)
    • exposing those methods is not a concern for diverging onchain and offchain indexer data, since it's already possible to modify records in arbitrary ways by providing different FieldLayout values
    • this change triggered a larger refactor of StoreCore to use the new spliceStaticData and spliceDynamicData instead of duplicating the splice logic in every single dynamic data function

will add more detailed context to the changeset once this PR is ready

@changeset-bot
Copy link

changeset-bot bot commented Sep 17, 2023

🦋 Changeset detected

Latest commit: 5154f15

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 29 packages
Name Type
@latticexyz/store Major
@latticexyz/world Major
@latticexyz/cli Major
@latticexyz/dev-tools Major
@latticexyz/react Major
@latticexyz/store-indexer Major
@latticexyz/store-sync Major
@latticexyz/abi-ts Major
@latticexyz/block-logs-stream Major
@latticexyz/common Major
@latticexyz/config Major
create-mud Major
@latticexyz/ecs-browser Major
@latticexyz/faucet Major
@latticexyz/gas-report Major
@latticexyz/network Major
@latticexyz/noise Major
@latticexyz/phaserx Major
@latticexyz/protocol-parser Major
@latticexyz/recs Major
@latticexyz/schema-type Major
@latticexyz/services Major
@latticexyz/solecs Major
solhint-config-mud Major
solhint-plugin-mud Major
@latticexyz/std-client Major
@latticexyz/std-contracts Major
@latticexyz/store-cache Major
@latticexyz/utils Major

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

@alvrs alvrs force-pushed the alvrs/splice-hooks branch from 4e0224b to 25f19ae Compare September 17, 2023 20:24
IStoreHook.onBeforeDeleteRecord.selector ^
IStoreHook.onAfterDeleteRecord.selector ^
ERC165_INTERFACE_ID;

interface IStoreHook is IERC165 {
function onBeforeSetRecord(
bytes32 tableId,
bytes32[] memory keyTuple,
bytes32[] calldata keyTuple,
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Member

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?

Copy link
Member Author

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 🤔

Copy link
Member

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))
Copy link
Member

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");
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
}
Copy link
Member

@holic holic Sep 17, 2023

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

Copy link
Member Author

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
Copy link
Member

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;

@@ -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
Copy link
Member

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

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));
Copy link
Member

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.

) public virtual {
StoreCore.spliceDynamicData(tableId, keyTuple, dynamicFieldIndex, startWithinField, deleteCount, data);
}

// Set partial data at schema index
Copy link
Member

@holic holic Sep 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Set partial data at schema index
// Set partial data at field index

there's a few more below too

@alvrs alvrs marked this pull request as ready for review September 18, 2023 15:45
@alvrs alvrs requested a review from dk1a as a code owner September 18, 2023 15:45

function testShorthand() public {
assertEq(ALL, BEFORE_CALL_SYSTEM | AFTER_CALL_SYSTEM);
}
Copy link
Member

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? 😆

Copy link
Member Author

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

holic
holic previously approved these changes Sep 18, 2023
@alvrs
Copy link
Member Author

alvrs commented Sep 19, 2023

skipping CI, only updated a comment since last CI pass

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

Successfully merging this pull request may close these issues.

2 participants