feat: Add ECDSA validator check when permitting an app version#460
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR adds ECDSA validator integration to verify smart account ownership through ZeroDev's ECDSA validator contract when users permit app versions. The PR introduces a new access control mechanism that checks the smart account owner via an external validator contract before allowing operations.
Changes:
- Adds
VincentZeroDevConfigFacetfor managing the ZeroDev ECDSA validator address configuration - Implements
onlySmartAccountOwnermodifier inVincentUserFacetto verify smart account ownership via the ECDSA validator forpermitAppVersion - Updates
onlyAgentOwnermodifier to check internal state for other user functions (unPermitAppVersion,rePermitApp,setAbilityPolicyParameters)
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
contracts/facets/VincentZeroDevConfigFacet.sol |
New facet providing getter/setter for ECDSA validator address |
contracts/facets/VincentUserFacet.sol |
Adds two new modifiers for ownership verification and applies them to user functions |
contracts/libs/LibVincentUserFacet.sol |
Replaces AgentNotRegisteredToUser error with more descriptive NotAgentOwner error |
contracts/VincentDiamond.sol |
Updates constructor to accept and store ECDSA validator address, registers new facet |
contracts/LibVincentDiamondStorage.sol |
Adds VincentZeroDevStorage library for storing ECDSA validator address |
test/TestHelpers.sol |
New helper contract for setting up ECDSA validator in tests using bytecode fixtures |
test/fixtures/ECDSAValidatorBytecode.txt |
Contains ECDSA validator bytecode fetched from Base mainnet |
script/FetchECDSAValidatorBytecode.sol |
Helper script to fetch and save ECDSA validator bytecode from Base |
script/SetEcdsaValidatorAddress.sol |
Script for updating ECDSA validator address on deployed contracts |
script/UpdateFacet.sol |
Adds support for updating VincentZeroDevConfigFacet |
script/DeployVincentDiamond.sol |
Updates deployment to require ECDSA validator address parameter |
Makefile |
Adds VINCENT_ECDSA_VALIDATOR_ADDRESS env var requirement and set-ecdsa-validator command |
foundry.toml |
Adds filesystem permissions for test fixtures directory |
abis/VincentZeroDevConfigFacet.abi.json |
ABI for new config facet |
abis/VincentUserFacet.abi.json |
Updates ABI with new error signature |
test/facets/*.t.sol |
Updates tests to register smart account owners and fix expected error types |
Comments suppressed due to low confidence (1)
packages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol:127
- The
AgentRegisteredToDifferentUsercheck at lines 123-127 is now redundant since theonlySmartAccountOwnermodifier already validates that the caller is the owner according to the ECDSA validator. If the ECDSA validator check passes, this internal state check should always pass as well (assuming state consistency). Consider removing this redundant check or adding a comment explaining why both checks are necessary.
// Check if the agent is already registered to a different user
address registeredUserAddress = us_.registeredAgentAddressToUserAddress[agentAddress];
if (registeredUserAddress != address(0) && registeredUserAddress != sender) {
revert LibVincentUserFacet.AgentRegisteredToDifferentUser(registeredUserAddress);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
DashKash54
left a comment
There was a problem hiding this comment.
Looks good but left some design questions
| } | ||
|
|
||
| /** | ||
| * @notice Validates that the caller is the registered owner of the agent in Vincent's internal state |
There was a problem hiding this comment.
Isn't the user's EOA the registered owner Agent smart account in registeredAgentAddressToUserAddress, can it ever be separate? Why have two separate modifiers onlyAgentOwner and onlySmartAccountOwner?
There was a problem hiding this comment.
it looks like onlySmartAccountOwner is only used on installation. we use it to set the owner, and then onlyAgentOwner checks that owner.
There was a problem hiding this comment.
But why have two if they're doing the same thing?
There was a problem hiding this comment.
One of the modifiers calls the ecdsa validator storage contract which is comparatively expensive, but required when the user permits the app for the smart account for the first time, then because we store the owner in the diamond storage, the second modifier can just read from storage which saves gas
| /** | ||
| * @title FetchECDSAValidatorBytecode | ||
| * @notice Script to fetch the ECDSA validator bytecode from Base Mainnet and save it | ||
| * @dev Run with: forge script script/FetchECDSAValidatorBytecode.sol:FetchECDSAValidatorBytecode --rpc-url https://mainnet.base.org |
There was a problem hiding this comment.
This is used only in our local tests but not used during the deployment or setup of ecdsaValidator?
There was a problem hiding this comment.
Yes, this allows us to test against the actual ecdsa validator that's deployed on Base. The tests will etch the bytecode into the expected address to simulate deployment on Base mainnet/sepolia
| * The ECDSA validator has: mapping(address => address) public ecdsaValidatorStorage; | ||
| * This mapping is at slot 0, so we calculate: keccak256(abi.encode(smartAccount, 0)) | ||
| */ | ||
| function registerSmartAccountOwner(address smartAccount, address owner) internal { |
There was a problem hiding this comment.
How will we set this up for the Agent smart account IRL?
There was a problem hiding this comment.
the onlySmartAccountOwner takes care of this when permitting the app. like onlySmartAccountOwner validates the address matches the smart account owner. and then when permitting the app we store this owner. is this correct @Spacesai1or ?
There was a problem hiding this comment.
Yes, the EOA is set as the validator when the smart account is deployed, then we record the verified owner EOA in the diamond storage when an App is permitted
|
|
||
| // Register smart account owners | ||
| // This simulates Frank and George owning their agents (smart accounts) | ||
| registerSmartAccountOwner(APP_USER_FRANK, USER_FRANK); |
There was a problem hiding this comment.
Any tests to verify that permitApp doesn't work if the signer isn't ECDSA?
There was a problem hiding this comment.
hmm can you clarify this question @DashKash54 ? why would the signer be anything else, and yes, if it was something else (not ecdsa) then it wouldn't work?
There was a problem hiding this comment.
Just to validate that it won't work if the signer is anything else, confirming unhappy path doesn't work
There was a problem hiding this comment.
Approving but we have 2 unresolved comments:
- Why have two separate modifier: #460 (comment)
- How does the ownerSmartAccount owner work for agent: #460 (comment)
Description
packages/libs/contracts-sdk/contracts/facets/VincentZeroDevConfigFacet.solwith a getter/setter for setting the canonical ZeroDev ECDSA Validator address (source from https://github.com/zerodevapp/kernel?tab=readme-ov-file)_ecdsaValidatorAddressparam and handles registering newVincentZeroDevConfigFacetMakefilewith new required ENV:VINCENT_ECDSA_VALIDATOR_ADDRESSand adds new cmd:set-ecdsa-validatorto execute the set validator address scriptVincentZeroDevStoragetopackages/libs/contracts-sdk/contracts/LibVincentDiamondStorage.solto storeecdsaValidatorAddresspackages/libs/contracts-sdk/contracts/facets/VincentUserFacet.sol:onlySmartAccountOwnerwhich is used bypermitAppVersionto check ifmsg.senderis the registered ECDSA signer for theagentAddressonlyAgentOwnerwhich is used byunPermitAppVersion,rePermitApp, andsetAbilityPolicyParametersto check ifmsg.senderisaddress registeredOwner = us_.registeredAgentAddressToUserAddress[agentAddress];packages/libs/contracts-sdk/contracts/libs/LibVincentUserFacet.solAgentNotRegisteredToUserwas replaced withNotAgentOwnerwhich is used by both above function modifierspackages/libs/contracts-sdk/script/FetchECDSAValidatorBytecode.solwhich is used to fetch the ZeroDev ECDSA Validator bytecode from Base deployment and write it to a file so the tests can write the bytecode to the expected Base/Base sepolia addresspackages/libs/contracts-sdk/script/SetEcdsaValidatorAddress.solfor setting the ZeroDev ECDSA Validator address used by theonlySmartAccountOwnermodifier inVincentUserFacetpackages/libs/contracts-sdk/script/UpdateFacet.solto handle newVincentZeroDevConfigFacetType of change
How Has This Been Tested?
NotAgentOwner, after updating them to use the test helper which sets the owner of the smart account to the test address, tests are passingChecklist:
nx release plan) describing my changes and the version bump