From c9dc9666536d7ceb1a878a7c335980151b60bbb9 Mon Sep 17 00:00:00 2001 From: Akash Jag Date: Tue, 14 Oct 2025 15:11:04 -0700 Subject: [PATCH 1/4] fix(lido validator): verify tx calldata not tampered --- src/validators/evm/base.validator.ts | 34 ++++ .../evm/lido/lido.validator.test.ts | 171 ++++++++++++++++++ src/validators/evm/lido/lido.validator.ts | 162 +++++++++-------- 3 files changed, 295 insertions(+), 72 deletions(-) diff --git a/src/validators/evm/base.validator.ts b/src/validators/evm/base.validator.ts index eef6c1e..979853c 100644 --- a/src/validators/evm/base.validator.ts +++ b/src/validators/evm/base.validator.ts @@ -1,6 +1,7 @@ import { BaseValidator } from '../base.validator'; import { ValidationResult } from '../../types'; import { isDefined, isNonEmptyString } from '../../utils/validation'; +import { ethers } from 'ethers'; export interface EVMTransaction { to: string | null; @@ -100,4 +101,37 @@ export abstract class BaseEVMValidator extends BaseValidator { } return null; } + + protected ensureCalldataNotTampered( + originalCalldata: string, + iface: ethers.Interface, + parsedTx: ethers.TransactionDescription, + ): ValidationResult | null { + try { + // Re-encode the function call with the parsed arguments + const expectedCalldata = iface.encodeFunctionData( + parsedTx.name, + parsedTx.args, + ); + + // Normalize both to lowercase for comparison + const normalizedOriginal = originalCalldata.toLowerCase(); + const normalizedExpected = expectedCalldata.toLowerCase(); + + // Check if they match exactly + if (normalizedOriginal !== normalizedExpected) { + return this.blocked('Transaction calldata has been tampered with', { + expectedLength: expectedCalldata.length, + actualLength: originalCalldata.length, + lengthDifference: originalCalldata.length - expectedCalldata.length, + }); + } + + return null; + } catch (error) { + return this.blocked('Failed to validate calldata integrity', { + error: error instanceof Error ? error.message : String(error), + }); + } + } } diff --git a/src/validators/evm/lido/lido.validator.test.ts b/src/validators/evm/lido/lido.validator.test.ts index 3c749ce..2d561a1 100644 --- a/src/validators/evm/lido/lido.validator.test.ts +++ b/src/validators/evm/lido/lido.validator.test.ts @@ -166,6 +166,34 @@ describe('LidoValidator via Shield', () => { expect(result.reason).toContain('No matching operation pattern found'); // Previously: toContain('Invalid referral address'); }); + it('should reject stake transaction with appended bytes', () => { + const tx = { + to: lidoStEthAddress, + from: userAddress, + value: '0xde0b6b3a7640000', + data: '0xa1903eab' + referralAddress.slice(2).padStart(64, '0') + 'deadbeef', // Extra bytes appended + nonce: 0, + gasLimit: '0x30d40', + gasPrice: '0x4a817c800', + chainId: 1, + type: 0, + }; + + const serialized = JSON.stringify(tx); + const result = shield.validate({ + yieldId, + unsignedTransaction: serialized, + userAddress, + }); + + expect(result.isValid).toBe(false); + expect(result.reason).toContain('No matching operation pattern found'); + const stakeAttempt = result.details?.attempts?.find( + (a: any) => a.type === TransactionType.STAKE, + ); + expect(stakeAttempt?.reason).toContain('calldata has been tampered'); + }); + it('should validate STAKE transaction', () => { const real = { from: '0x4546fC1b71375eA0fa4D8cA32B9F2C2ED4FB2E82', @@ -341,6 +369,77 @@ describe('LidoValidator via Shield', () => { expect(result.reason).toContain('No matching operation pattern found'); // Previously: toContain('Withdrawal amounts array is empty'); }); + it('should reject unstake transaction with appended bytes', () => { + const tx = { + to: lidoWithdrawalQueueAddress, + from: userAddress, + value: '0x0', + data: + '0xd6681042' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '000000000000000000000000' + + userAddress.slice(2) + + '0000000000000000000000000000000000000000000000000000000000000001' + + '0000000000000000000000000000000000000000000000000de0b6b3a7640000' + + 'cafebabe', // Extra bytes + nonce: 0, + gasLimit: '0x493e0', + gasPrice: '0x4a817c800', + chainId: 1, + type: 0, + }; + + const serialized = JSON.stringify(tx); + const result = shield.validate({ + yieldId, + unsignedTransaction: serialized, + userAddress, + }); + + expect(result.isValid).toBe(false); + expect(result.reason).toContain('No matching operation pattern found'); + const unstakeAttempt = result.details?.attempts?.find( + (a: any) => a.type === TransactionType.UNSTAKE, + ); + expect(unstakeAttempt?.reason).toContain('calldata has been tampered'); + }); + + it('should reject unstake with multiple amounts and appended bytes', () => { + // requestWithdrawals([1 ETH, 2 ETH], owner) + extra bytes + const tx = { + to: lidoWithdrawalQueueAddress, + from: userAddress, + value: '0x0', + data: + '0xd6681042' + + '0000000000000000000000000000000000000000000000000000000000000040' + + '000000000000000000000000' + + userAddress.slice(2) + + '0000000000000000000000000000000000000000000000000000000000000002' + // 2 amounts + '0000000000000000000000000000000000000000000000000de0b6b3a7640000' + // 1 ETH + '0000000000000000000000000000000000000000000000001bc16d674ec80000' + // 2 ETH + '12345678', // Extra bytes + nonce: 0, + gasLimit: '0x493e0', + gasPrice: '0x4a817c800', + chainId: 1, + type: 0, + }; + + const serialized = JSON.stringify(tx); + const result = shield.validate({ + yieldId, + unsignedTransaction: serialized, + userAddress, + }); + + expect(result.isValid).toBe(false); + const unstakeAttempt = result.details?.attempts?.find( + (a: any) => a.type === TransactionType.UNSTAKE, + ); + expect(unstakeAttempt?.reason).toContain('calldata has been tampered'); + }); + it('should validate UNSTAKE transaction', () => { const real = { from: '0x034A21184A8832EBa5D9fcD61D533D0d641ECe12', @@ -563,6 +662,78 @@ describe('LidoValidator via Shield', () => { expect(result.reason).toContain('No matching operation pattern found'); // Previously: toContain('arrays length mismatch'); }); + it('should reject claimWithdrawal with appended bytes', () => { + const requestId = 123n; + const encodedParams = requestId.toString(16).padStart(64, '0'); + + const tx = { + to: lidoWithdrawalQueueAddress, + from: userAddress, + value: '0x0', + data: '0xf8444436' + encodedParams + '12345678', // Extra bytes + nonce: 0, + gasLimit: '0x30d40', + gasPrice: '0x4a817c800', + chainId: 1, + type: 0, + }; + + const serialized = JSON.stringify(tx); + const result = shield.validate({ + yieldId, + unsignedTransaction: serialized, + userAddress, + }); + + expect(result.isValid).toBe(false); + expect(result.reason).toContain('No matching operation pattern found'); + const claimAttempt = result.details?.attempts?.find( + (a: any) => a.type === TransactionType.CLAIM_UNSTAKED, + ); + expect(claimAttempt?.reason).toContain('calldata has been tampered'); + }); + + it('should reject claimWithdrawals with appended bytes', () => { + // Generate VALID calldata using ethers + const { ethers } = require('ethers'); + const iface = new ethers.Interface([ + 'function claimWithdrawals(uint256[] _requestIds, uint256[] _hints)', + ]); + + const validCalldata = iface.encodeFunctionData('claimWithdrawals', [ + [1n, 2n], // requestIds + [100n, 200n], // hints + ]); + + // Append malicious bytes + const tamperedCalldata = validCalldata + 'cafebabe'; + + const tx = { + to: lidoWithdrawalQueueAddress, + from: userAddress, + value: '0x0', + data: tamperedCalldata, + nonce: 0, + gasLimit: '0x30d40', + gasPrice: '0x4a817c800', + chainId: 1, + type: 0, + }; + + const serialized = JSON.stringify(tx); + const result = shield.validate({ + yieldId, + unsignedTransaction: serialized, + userAddress, + }); + + expect(result.isValid).toBe(false); + const claimAttempt = result.details?.attempts?.find( + (a: any) => a.type === TransactionType.CLAIM_UNSTAKED, + ); + expect(claimAttempt?.reason).toContain('calldata has been tampered'); + }); + it('should validate batch CLAIM_UNSTAKED transaction', () => { const real = { from: '0x6877BB79f680216BbdF01704939037F22193e771', diff --git a/src/validators/evm/lido/lido.validator.ts b/src/validators/evm/lido/lido.validator.ts index 0c8f2b9..0482dd2 100644 --- a/src/validators/evm/lido/lido.validator.ts +++ b/src/validators/evm/lido/lido.validator.ts @@ -86,29 +86,23 @@ export class LidoValidator extends BaseEVMValidator { }); } - try { - const parsed = this.lidoInterface.parseTransaction({ - data: tx.data ?? '0x', - value: tx.value, + const result = this.parseAndValidateCalldata(tx); + if ('error' in result) return result.error; + + const { parsed } = result; + + if (parsed.name !== 'submit') { + return this.blocked('Invalid method for staking', { + expected: 'submit', + actual: parsed.name, }); + } - if (!isDefined(parsed) || parsed.name !== 'submit') { - return this.blocked('Invalid method for staking', { - expected: 'submit', - actual: parsed?.name ?? 'unknown', - }); - } - - const [referral] = parsed.args; - if (referral.toLowerCase() !== LIDO_REFERRAL.toLowerCase()) { - return this.blocked('Invalid referral address', { - expected: LIDO_REFERRAL, - actual: referral, - }); - } - } catch (error) { - return this.blocked('Invalid transaction data for staking', { - error: error instanceof Error ? error.message : String(error), + const [referral] = parsed.args; + if (referral.toLowerCase() !== LIDO_REFERRAL.toLowerCase()) { + return this.blocked('Invalid referral address', { + expected: LIDO_REFERRAL, + actual: referral, }); } @@ -133,37 +127,31 @@ export class LidoValidator extends BaseEVMValidator { }); } - try { - const parsed = this.lidoInterface.parseTransaction({ - data: tx.data ?? '0x', - value: tx.value, - }); + const result = this.parseAndValidateCalldata(tx); + if ('error' in result) return result.error; + + const { parsed } = result; - if (!isDefined(parsed) || parsed.name !== 'requestWithdrawals') { - return this.blocked('Invalid method for unstaking', { - expected: 'requestWithdrawals', - actual: parsed?.name ?? 'unknown', - }); - } + if (parsed.name !== 'requestWithdrawals') { + return this.blocked('Invalid method for unstaking', { + expected: 'requestWithdrawals', + actual: parsed.name, + }); + } - const [amounts, owner] = parsed.args; + const [amounts, owner] = parsed.args; - if (owner.toLowerCase() !== userAddress.toLowerCase()) { - return this.blocked('Withdrawal request owner is not user address', { - expected: userAddress, - actual: owner, - }); - } - - if (!Array.isArray(amounts) || amounts.length === 0) { - return this.blocked('Withdrawal amounts array is empty'); - } - } catch (error) { - return this.blocked('Invalid transaction data for unstaking', { - error: error instanceof Error ? error.message : String(error), + if (owner.toLowerCase() !== userAddress.toLowerCase()) { + return this.blocked('Withdrawal request owner is not user address', { + expected: userAddress, + actual: owner, }); } + if (!Array.isArray(amounts) || amounts.length === 0) { + return this.blocked('Withdrawal amounts array is empty'); + } + return this.safe(); } @@ -182,42 +170,72 @@ export class LidoValidator extends BaseEVMValidator { }); } + const result = this.parseAndValidateCalldata(tx); + if ('error' in result) return result.error; + + const { parsed } = result; + + if (parsed.name === 'claimWithdrawal') { + return this.safe(); + } else if (parsed.name === 'claimWithdrawals') { + const [requestIds, hints] = parsed.args; + + if (requestIds.length === 0) { + return this.blocked('Request IDs array is empty'); + } + + if (requestIds.length !== hints.length) { + return this.blocked('Request IDs and hints arrays length mismatch', { + requestIdsLength: requestIds.length, + hintsLength: hints.length, + }); + } + + return this.safe(); + } else { + return this.blocked('Invalid method for claiming', { + expected: 'claimWithdrawal or claimWithdrawals', + actual: parsed.name, + }); + } + } + + /** + * Parse transaction and validate calldata integrity + */ + private parseAndValidateCalldata( + tx: EVMTransaction, + ): { parsed: ethers.TransactionDescription } | { error: ValidationResult } { try { const parsed = this.lidoInterface.parseTransaction({ data: tx.data ?? '0x', value: tx.value, }); - if (!isDefined(parsed)) { - return this.blocked('Invalid transaction data for claiming'); + if (!parsed) { + return { + error: this.blocked('Failed to parse transaction data'), + }; } - if (parsed.name === 'claimWithdrawal') { - return this.safe(); - } else if (parsed.name === 'claimWithdrawals') { - const [requestIds, hints] = parsed.args; - - if (requestIds.length === 0) { - return this.blocked('Request IDs array is empty'); - } - - if (requestIds.length !== hints.length) { - return this.blocked('Request IDs and hints arrays length mismatch', { - requestIdsLength: requestIds.length, - hintsLength: hints.length, - }); - } - return this.safe(); - } else { - return this.blocked('Invalid method for claiming', { - expected: 'claimWithdrawal or claimWithdrawals', - actual: parsed.name, - }); + // Check for tampering + const tamperErr = this.ensureCalldataNotTampered( + tx.data ?? '0x', + this.lidoInterface, + parsed, + ); + + if (tamperErr) { + return { error: tamperErr }; } + + return { parsed }; } catch (error) { - return this.blocked('Invalid transaction data for claiming', { - error: error instanceof Error ? error.message : String(error), - }); + return { + error: this.blocked('Invalid transaction data', { + error: error instanceof Error ? error.message : String(error), + }), + }; } } } From 1eb5a73a826481fe6dbcdaf36767c6c813e1e8de Mon Sep 17 00:00:00 2001 From: Akash Jag Date: Tue, 14 Oct 2025 15:31:42 -0700 Subject: [PATCH 2/4] fix(lido-validator): linting issues --- src/validators/evm/base.validator.ts | 54 +++++++++---------- .../evm/lido/lido.validator.test.ts | 11 ++-- src/validators/evm/lido/lido.validator.ts | 12 ++--- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/validators/evm/base.validator.ts b/src/validators/evm/base.validator.ts index 979853c..124f89a 100644 --- a/src/validators/evm/base.validator.ts +++ b/src/validators/evm/base.validator.ts @@ -102,36 +102,36 @@ export abstract class BaseEVMValidator extends BaseValidator { return null; } - protected ensureCalldataNotTampered( - originalCalldata: string, - iface: ethers.Interface, - parsedTx: ethers.TransactionDescription, - ): ValidationResult | null { - try { - // Re-encode the function call with the parsed arguments - const expectedCalldata = iface.encodeFunctionData( - parsedTx.name, - parsedTx.args, - ); - - // Normalize both to lowercase for comparison - const normalizedOriginal = originalCalldata.toLowerCase(); - const normalizedExpected = expectedCalldata.toLowerCase(); + protected ensureCalldataNotTampered( + originalCalldata: string, + iface: ethers.Interface, + parsedTx: ethers.TransactionDescription, + ): ValidationResult | null { + try { + // Re-encode the function call with the parsed arguments + const expectedCalldata = iface.encodeFunctionData( + parsedTx.name, + parsedTx.args, + ); - // Check if they match exactly - if (normalizedOriginal !== normalizedExpected) { - return this.blocked('Transaction calldata has been tampered with', { - expectedLength: expectedCalldata.length, - actualLength: originalCalldata.length, - lengthDifference: originalCalldata.length - expectedCalldata.length, - }); - } + // Normalize both to lowercase for comparison + const normalizedOriginal = originalCalldata.toLowerCase(); + const normalizedExpected = expectedCalldata.toLowerCase(); - return null; - } catch (error) { - return this.blocked('Failed to validate calldata integrity', { - error: error instanceof Error ? error.message : String(error), + // Check if they match exactly + if (normalizedOriginal !== normalizedExpected) { + return this.blocked('Transaction calldata has been tampered with', { + expectedLength: expectedCalldata.length, + actualLength: originalCalldata.length, + lengthDifference: originalCalldata.length - expectedCalldata.length, }); } + + return null; + } catch (error) { + return this.blocked('Failed to validate calldata integrity', { + error: error instanceof Error ? error.message : String(error), + }); } + } } diff --git a/src/validators/evm/lido/lido.validator.test.ts b/src/validators/evm/lido/lido.validator.test.ts index 2d561a1..e4410d9 100644 --- a/src/validators/evm/lido/lido.validator.test.ts +++ b/src/validators/evm/lido/lido.validator.test.ts @@ -1,5 +1,6 @@ import { Shield } from '../../../shield'; import { TransactionType } from '../../../types'; +import { ethers } from 'ethers'; describe('LidoValidator via Shield', () => { const shield = new Shield(); @@ -171,7 +172,10 @@ describe('LidoValidator via Shield', () => { to: lidoStEthAddress, from: userAddress, value: '0xde0b6b3a7640000', - data: '0xa1903eab' + referralAddress.slice(2).padStart(64, '0') + 'deadbeef', // Extra bytes appended + data: + '0xa1903eab' + + referralAddress.slice(2).padStart(64, '0') + + 'deadbeef', // Extra bytes appended nonce: 0, gasLimit: '0x30d40', gasPrice: '0x4a817c800', @@ -695,16 +699,15 @@ describe('LidoValidator via Shield', () => { it('should reject claimWithdrawals with appended bytes', () => { // Generate VALID calldata using ethers - const { ethers } = require('ethers'); const iface = new ethers.Interface([ 'function claimWithdrawals(uint256[] _requestIds, uint256[] _hints)', ]); - + const validCalldata = iface.encodeFunctionData('claimWithdrawals', [ [1n, 2n], // requestIds [100n, 200n], // hints ]); - + // Append malicious bytes const tamperedCalldata = validCalldata + 'cafebabe'; diff --git a/src/validators/evm/lido/lido.validator.ts b/src/validators/evm/lido/lido.validator.ts index 0482dd2..db3a259 100644 --- a/src/validators/evm/lido/lido.validator.ts +++ b/src/validators/evm/lido/lido.validator.ts @@ -88,7 +88,7 @@ export class LidoValidator extends BaseEVMValidator { const result = this.parseAndValidateCalldata(tx); if ('error' in result) return result.error; - + const { parsed } = result; if (parsed.name !== 'submit') { @@ -129,7 +129,7 @@ export class LidoValidator extends BaseEVMValidator { const result = this.parseAndValidateCalldata(tx); if ('error' in result) return result.error; - + const { parsed } = result; if (parsed.name !== 'requestWithdrawals') { @@ -172,7 +172,7 @@ export class LidoValidator extends BaseEVMValidator { const result = this.parseAndValidateCalldata(tx); if ('error' in result) return result.error; - + const { parsed } = result; if (parsed.name === 'claimWithdrawal') { @@ -190,7 +190,7 @@ export class LidoValidator extends BaseEVMValidator { hintsLength: hints.length, }); } - + return this.safe(); } else { return this.blocked('Invalid method for claiming', { @@ -212,7 +212,7 @@ export class LidoValidator extends BaseEVMValidator { value: tx.value, }); - if (!parsed) { + if (!isDefined(parsed)) { return { error: this.blocked('Failed to parse transaction data'), }; @@ -224,7 +224,7 @@ export class LidoValidator extends BaseEVMValidator { this.lidoInterface, parsed, ); - + if (tamperErr) { return { error: tamperErr }; } From e40b0d868fc4839d6568c0144ea4a295163ebba3 Mon Sep 17 00:00:00 2001 From: Akash Jag Date: Wed, 15 Oct 2025 19:10:51 -0700 Subject: [PATCH 3/4] fix(lido-validator): moved parseAndValidate function to evm base class --- src/validators/evm/base.validator.ts | 37 +++++++++++++++++++ src/validators/evm/lido/lido.validator.ts | 45 ++--------------------- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/validators/evm/base.validator.ts b/src/validators/evm/base.validator.ts index 124f89a..f561ae3 100644 --- a/src/validators/evm/base.validator.ts +++ b/src/validators/evm/base.validator.ts @@ -102,6 +102,43 @@ export abstract class BaseEVMValidator extends BaseValidator { return null; } + protected parseAndValidateCalldata( + tx: EVMTransaction, + iface: ethers.Interface, + ): { parsed: ethers.TransactionDescription } | { error: ValidationResult } { + try { + const parsed = iface.parseTransaction({ + data: tx.data ?? '0x', + value: tx.value, + }); + + if (!parsed) { + return { + error: this.blocked('Failed to parse transaction data'), + }; + } + + // Check for tampering + const tamperErr = this.ensureCalldataNotTampered( + tx.data ?? '0x', + iface, + parsed, + ); + + if (tamperErr) { + return { error: tamperErr }; + } + + return { parsed }; + } catch (error) { + return { + error: this.blocked('Invalid transaction data', { + error: error instanceof Error ? error.message : String(error), + }), + }; + } + } + protected ensureCalldataNotTampered( originalCalldata: string, iface: ethers.Interface, diff --git a/src/validators/evm/lido/lido.validator.ts b/src/validators/evm/lido/lido.validator.ts index db3a259..29715da 100644 --- a/src/validators/evm/lido/lido.validator.ts +++ b/src/validators/evm/lido/lido.validator.ts @@ -86,7 +86,7 @@ export class LidoValidator extends BaseEVMValidator { }); } - const result = this.parseAndValidateCalldata(tx); + const result = this.parseAndValidateCalldata(tx, this.lidoInterface); if ('error' in result) return result.error; const { parsed } = result; @@ -127,7 +127,7 @@ export class LidoValidator extends BaseEVMValidator { }); } - const result = this.parseAndValidateCalldata(tx); + const result = this.parseAndValidateCalldata(tx, this.lidoInterface); if ('error' in result) return result.error; const { parsed } = result; @@ -170,7 +170,7 @@ export class LidoValidator extends BaseEVMValidator { }); } - const result = this.parseAndValidateCalldata(tx); + const result = this.parseAndValidateCalldata(tx, this.lidoInterface); if ('error' in result) return result.error; const { parsed } = result; @@ -199,43 +199,4 @@ export class LidoValidator extends BaseEVMValidator { }); } } - - /** - * Parse transaction and validate calldata integrity - */ - private parseAndValidateCalldata( - tx: EVMTransaction, - ): { parsed: ethers.TransactionDescription } | { error: ValidationResult } { - try { - const parsed = this.lidoInterface.parseTransaction({ - data: tx.data ?? '0x', - value: tx.value, - }); - - if (!isDefined(parsed)) { - return { - error: this.blocked('Failed to parse transaction data'), - }; - } - - // Check for tampering - const tamperErr = this.ensureCalldataNotTampered( - tx.data ?? '0x', - this.lidoInterface, - parsed, - ); - - if (tamperErr) { - return { error: tamperErr }; - } - - return { parsed }; - } catch (error) { - return { - error: this.blocked('Invalid transaction data', { - error: error instanceof Error ? error.message : String(error), - }), - }; - } - } } From a2c3226b4b03cc1a67046d4e43c935a560ca17c9 Mon Sep 17 00:00:00 2001 From: Akash Jag Date: Wed, 15 Oct 2025 19:17:40 -0700 Subject: [PATCH 4/4] fix(lido-validator): linting issues --- src/validators/evm/base.validator.ts | 4 ++-- src/validators/evm/lido/lido.validator.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/validators/evm/base.validator.ts b/src/validators/evm/base.validator.ts index f561ae3..0f0c53f 100644 --- a/src/validators/evm/base.validator.ts +++ b/src/validators/evm/base.validator.ts @@ -112,7 +112,7 @@ export abstract class BaseEVMValidator extends BaseValidator { value: tx.value, }); - if (!parsed) { + if (!isDefined(parsed)) { return { error: this.blocked('Failed to parse transaction data'), }; @@ -124,7 +124,7 @@ export abstract class BaseEVMValidator extends BaseValidator { iface, parsed, ); - + if (tamperErr) { return { error: tamperErr }; } diff --git a/src/validators/evm/lido/lido.validator.ts b/src/validators/evm/lido/lido.validator.ts index 29715da..8884970 100644 --- a/src/validators/evm/lido/lido.validator.ts +++ b/src/validators/evm/lido/lido.validator.ts @@ -5,7 +5,6 @@ import { ValidationContext, ValidationResult, } from '../../../types'; -import { isDefined } from '../../../utils/validation'; import { BaseEVMValidator, EVMTransaction } from '../base.validator'; const LIDO_CONTRACTS = {