Skip to content

review(ens): pre-draft calldata verification for kpk Endowment permissions Update #10 - #102

Open
alextnetto wants to merge 14 commits into
mainfrom
ens/kpk-endowment-permissions-update-10
Open

review(ens): pre-draft calldata verification for kpk Endowment permissions Update #10#102
alextnetto wants to merge 14 commits into
mainfrom
ens/kpk-endowment-permissions-update-10

Conversation

@alextnetto

@alextnetto alextnetto commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

Pre-draft calldata security review of Endowment permissions to KPK Update #10.

There is no Tally draft yet. The reviewed artifact is the Safe Transaction Builder payload published with the forum post: karpatkey/client-configs @ ens-dao-manager-harvest-rwa-yield, commit 3e447f8d21 (clients/ens-dao/mainnet/payloads/ensPermissionsUpdate10.json, 54 transactions).

What the test does

  • _generateCallData() manually derives the full 54-transaction MultiSend body from the forum specification, the payload, and Solidity interfaces (no hex blobs, every selector from an interface, every address a named constant)
  • _assertDerivedPayloadMatches() diffs the derivation byte for byte against expectedMultiSend.txt, the published payload re-encoded verbatim (verified with a negative control: a one-byte change fails the test)
  • _beforeProposal() proves the new venues are unreachable today and that the three reward distributors are already claimable with payouts pinned to the Safe
  • _afterExecution() asserts every new permission positively and negatively: vault deposit/withdraw/redeem pinned to the Safe, Aave Horizon pinned to RLUSD, Pendle pinned to the PT-sUSDS-26NOV2026 market with the external-router escape hatch shut, approve() spenders pinned, 19 pre-existing approvals preserved, and signOrder probes proving the syrup tokens are not on the CoW lists
  • _assertAssumedHarvestArchitecture() simulates the working assumption for the sub-Roles module: kpk configures the Harvest role on it post-execution (operator 0x14C2…97de, per their config repo). Verified: claims pass with payouts to the Safe; redirected payouts are blocked by the main MANAGER layer even with the sub-role configured wide open; the operator can reach nothing beyond the three distributors

Verification result

Derived calldata matches the published payload exactly. Permission shapes are tight. No permissions removed (confirmed by simulation and by the additions-only diff of roles/MANAGER in karpatkey's config repo).

Findings (see forum-post.md for the questions put to kpk)

  1. The payload deploys a karpatkey-owned sub-Roles Modifier chained under MANAGER, undisclosed in the forum post. Working assumption (pending kpk confirmation): it will host the Harvest role of item 6, configured off-chain. Simulation shows the design is sound and payouts cannot be redirected; the governance consequence remains that kpk can grant Manager-scoped access without a DAO vote
  2. Spec items 5 (syrup tokens on CoW lists) and 6 (Harvest role) are absent from the payload; item 6's absence is explained by the assumption above, item 5 remains unimplemented
  3. The item 6 distributor claims already exist on MANAGER today, recipient-pinned; under the assumption, item 6's contribution is a dedicated operator, not new permissions
  4. The forum table lists a Steakhouse address with no code on mainnet; the payload uses the correct vault

Recommendation: NEEDS_REVIEW. Questions for kpk are drafted in forum-post.md.

Test

forge test --match-path "src/ens/proposals/ep-kpk-update-10/*" -vv

1 passed. Will be re-run against the final payload when the Tally draft is published.

@alextnetto
alextnetto force-pushed the ens/kpk-endowment-permissions-update-10 branch from 3df746f to 74faa82 Compare August 1, 2026 16:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3df746fa89

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +615 to +616
_assertTargetNotAllowed(SYRUP_USDC, _approveCall(GPV2_VAULT_RELAYER));
_assertTargetNotAllowed(SYRUP_USDT, _approveCall(GPV2_VAULT_RELAYER));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Test the CoW order token lists directly

If the finalized payload implements item 5 by adding syrupUSDC and syrupUSDT to the existing ICowSwapOrderSigner.signOrder sell/buy-token conditions, these calls will still target the syrup token contracts themselves and remain TargetAddressNotAllowed. Consequently, the assertions will keep passing even though the documented gap has been fixed, contrary to the stated expectation that the test should flag the update. Exercise signOrder with each syrup token as the sell and buy token instead; token approval permissions are independent of the CoW token lists.

Useful? React with 👍 / 👎.

Comment on lines +149 to +150
Unlike #14, **#15 is still Queued** (`state(15) = Queued`); the node shows its pools still point at their old
controllers, so this batch has **not** taken effect yet — there is still a window to revoke before it can execute. ✅

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refresh the expired proposal-state snapshot

For anyone relying on this report after the reviewed commit date, this promise of a remaining pre-execution window is already inconsistent with the report's own timeline: proposal #15 was created on July 9, and §5 says its createTime → eta interval is exactly seven days, while this commit is dated August 1. The block-25,535,332 snapshot can therefore no longer establish that the proposal is still pre-executable; by publication it must have advanced to Grace, Executed, or Expired. Refresh the state at publication time and timestamp the result instead of telling exposed users that they still have time before execution.

Useful? React with 👍 / 👎.

Comment on lines +8 to +9
We reconstructed all 54 transactions of the payload from the specification and from the public contract interfaces, and
compared our reconstruction against the published payload byte for byte. The two are identical. The permissions granted

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disclose the payload-derived reconstruction inputs

The claim that all 54 transactions were reconstructed from the specification and public interfaces cannot hold for this payload: the same post says transactions 0–7 deploy and configure a sub-Roles module that the specification does not mention, and the README says transaction 53's annotation content was extracted from the payload; the unmentioned approval-list extensions likewise are not derivable from the specification. The byte comparison still validates the hand encoding, but presenting it as a wholly specification-derived reconstruction overstates its independence. Identify which fields came from the executable payload or configuration repository and reserve the specification-derived claim for the transactions it actually describes.

Useful? React with 👍 / 👎.

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.

1 participant