-
Notifications
You must be signed in to change notification settings - Fork 1
feat: cover runtime OAV injection, refresh registry, bump to 1.7.0 [eng-3209] #34
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,7 +110,7 @@ | |
| transactionType: TransactionType, | ||
| userAddress: string, | ||
| args?: ActionArguments, | ||
| _context?: ValidationContext, | ||
| context?: ValidationContext, | ||
| ): ValidationResult { | ||
| const decoded = this.decodeEVMTransaction(unsignedTransaction); | ||
| if (!decoded.isValid || !decoded.transaction) { | ||
|
|
@@ -127,12 +127,12 @@ | |
|
|
||
| // Get and validate chain ID from transaction | ||
| const chainId = this.getNumericChainId(tx); | ||
| if (!chainId) { | ||
|
Check warning on line 130 in src/validators/evm/erc4626/erc4626.validator.ts
|
||
| return this.blocked('Chain ID not found in transaction'); | ||
| } | ||
|
|
||
| // Ensure destination address exists | ||
| if (!tx.to) { | ||
|
Check warning on line 135 in src/validators/evm/erc4626/erc4626.validator.ts
|
||
| return this.blocked('Transaction has no destination address'); | ||
| } | ||
|
|
||
|
|
@@ -194,7 +194,7 @@ | |
| // Route to appropriate validation based on transaction type | ||
| switch (transactionType) { | ||
| case TransactionType.APPROVAL: | ||
| return this.validateApproval(tx, chainId, declaredAmount); | ||
| return this.validateApproval(tx, chainId, declaredAmount, context); | ||
| case TransactionType.WRAP: | ||
| return this.validateWrap(tx, chainId, declaredAmount); | ||
| case TransactionType.SUPPLY: | ||
|
|
@@ -204,6 +204,7 @@ | |
| chainId, | ||
| receiverAddress, | ||
| declaredAmount, | ||
| context, | ||
| ); | ||
| case TransactionType.WITHDRAW: | ||
| return this.validateWithdraw( | ||
|
|
@@ -213,6 +214,7 @@ | |
| receiverAddress, | ||
| declaredAmount, | ||
| declaredShareAmount, | ||
| context, | ||
| ); | ||
| case TransactionType.UNWRAP: | ||
| return this.validateUnwrap(tx, chainId); | ||
|
|
@@ -230,6 +232,7 @@ | |
| tx: EVMTransaction, | ||
| chainId: number, | ||
| declaredAmount?: string, | ||
| context?: ValidationContext, | ||
| ): ValidationResult { | ||
| // APPROVAL should not send ETH | ||
| const value = BigInt(tx.value ?? '0'); | ||
|
|
@@ -259,10 +262,15 @@ | |
| // Get spender (should be vault address) | ||
| const [spender] = parsed.args; | ||
|
|
||
| // Validate spender is a whitelisted vault | ||
| const vaultInfo = this.vaultInfoMap.get( | ||
| `${chainId}:${spender.toLowerCase()}`, | ||
| ); | ||
| // Validate spender is a whitelisted vault (static registry, then injected OAV) | ||
| const spenderAddress = spender.toLowerCase(); | ||
| let vaultInfo = this.vaultInfoMap.get(`${chainId}:${spenderAddress}`); | ||
| if ( | ||
| !vaultInfo && | ||
| this.getInjectedAllocatorVaults(context).has(spenderAddress) | ||
| ) { | ||
| vaultInfo = this.getBaseVaultForChain(chainId); | ||
| } | ||
| if (!vaultInfo) { | ||
| return this.blocked('Approval spender is not a whitelisted vault', { | ||
| spender, | ||
|
|
@@ -304,7 +312,7 @@ | |
| ): ValidationResult { | ||
| // Get WETH address for this chain | ||
| const wethAddress = this.getWethAddress(chainId); | ||
| if (!wethAddress) { | ||
|
Check warning on line 315 in src/validators/evm/erc4626/erc4626.validator.ts
|
||
| return this.blocked('WETH address not configured for chain', { chainId }); | ||
| } | ||
|
|
||
|
|
@@ -368,8 +376,9 @@ | |
| chainId: number, | ||
| receiverAddress?: string, | ||
| declaredAmount?: string, | ||
| context?: ValidationContext, | ||
| ): ValidationResult { | ||
| const resolved = this.resolveVault(tx, chainId); | ||
| const resolved = this.resolveVault(tx, chainId, context); | ||
| if ('error' in resolved) return resolved.error; | ||
| const { vaultInfo } = resolved; | ||
|
|
||
|
|
@@ -457,8 +466,9 @@ | |
| receiverAddress?: string, | ||
| declaredAmount?: string, | ||
| declaredShareAmount?: string, | ||
| context?: ValidationContext, | ||
| ): ValidationResult { | ||
| const resolved = this.resolveVault(tx, chainId); | ||
| const resolved = this.resolveVault(tx, chainId, context); | ||
| if ('error' in resolved) return resolved.error; | ||
| const { vaultInfo } = resolved; | ||
|
|
||
|
|
@@ -647,32 +657,29 @@ | |
| private resolveVault( | ||
| tx: EVMTransaction, | ||
| chainId: number, | ||
| context?: ValidationContext, | ||
| ): { vaultInfo: VaultInfo } | { error: ValidationResult } { | ||
| const vaultAddress = tx.to?.toLowerCase(); | ||
| if (!vaultAddress) { | ||
| return { error: this.blocked('Transaction has no destination address') }; | ||
| } | ||
|
|
||
| if (!this.vaultInfoMap.has(`${chainId}:${vaultAddress}`)) { | ||
| return { | ||
| error: this.blocked('Vault address not whitelisted', { | ||
| vaultAddress, | ||
| chainId, | ||
| }), | ||
| }; | ||
| } | ||
|
|
||
| const vaultInfo = this.vaultInfoMap.get(`${chainId}:${vaultAddress}`); | ||
| if (!vaultInfo) { | ||
| return { | ||
| error: this.blocked('Vault address not whitelisted', { | ||
| vaultAddress, | ||
| chainId, | ||
| }), | ||
| }; | ||
| const staticVault = this.vaultInfoMap.get(`${chainId}:${vaultAddress}`); | ||
| if (staticVault) return { vaultInfo: staticVault }; | ||
| // Runtime, DB-sourced OAV: accept if injected via context | ||
| if (this.getInjectedAllocatorVaults(context).has(vaultAddress)) { | ||
| const base = this.getBaseVaultForChain(chainId); | ||
| if (base) { | ||
| return { | ||
| vaultInfo: { ...base, address: vaultAddress }, | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| return { vaultInfo }; | ||
| return { | ||
| error: this.blocked('Vault address not whitelisted', { | ||
| vaultAddress, | ||
| chainId, | ||
| }), | ||
| }; | ||
| } | ||
|
|
||
| private isAllocatorTarget(txTo: string, vaultInfo: VaultInfo): boolean { | ||
|
|
@@ -691,4 +698,23 @@ | |
| private getWethAddress(chainId: number): string | null { | ||
| return WETH_ADDRESSES[chainId] || null; | ||
| } | ||
|
|
||
| private getInjectedAllocatorVaults(context?: ValidationContext): Set<string> { | ||
| const injected = new Set<string>(); | ||
| for (const fee of context?.feeConfiguration ?? []) { | ||
| if (isNonEmptyString(fee.allocatorVaultAddress)) { | ||
| injected.add(fee.allocatorVaultAddress.toLowerCase()); | ||
|
Comment on lines
+702
to
+706
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Agentic Security Review Caller-supplied The JSON validate path forwards Impact: Users who sign after a Shield Reviewed by Cursor Security Reviewer for commit 14074fc. Configure here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Intentional. context.feeConfiguration is trusted control-plane input, not end-user input. Embedded validation never takes context from the transaction submitter. Standalone/JSON callers must not forward dApp-supplied context; they should inject only their own OAV addresses. Remaining invariants still apply (from/owner/receiver = user, method + calldata checks). We’ll document this; schema address format is hardening only. |
||
| } | ||
| } | ||
| return injected; | ||
| } | ||
|
|
||
| // The instance is yield-scoped to one base vault; use it as the template | ||
| // for a context-injected OAV (input token + protocol metadata). | ||
| private getBaseVaultForChain(chainId: number): VaultInfo | undefined { | ||
| for (const vault of this.vaultInfoMap.values()) { | ||
| if (vault.chainId === chainId) return vault; | ||
| } | ||
| return undefined; | ||
| } | ||
| } | ||


Uh oh!
There was an error while loading. Please reload this page.