Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions lib/contracts/abis/mainnet/InstitutionalVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export const InstitutionalVault = <const>[
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'NON_RESTORAKING_WITHDRAWAL_CREDENTIALS_FACTORY',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'UPGRADE_INTERFACE_VERSION',
Expand Down Expand Up @@ -176,6 +183,20 @@ export const InstitutionalVault = <const>[
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getEigenPodWithdrawalCredentials',
outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getNoRestakingWithdrawalCredentials',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getNonRestakedValidatorETH',
Expand Down Expand Up @@ -210,9 +231,9 @@ export const InstitutionalVault = <const>[
},
{
inputs: [],
name: 'isConsumingScheduledOp',
outputs: [{ internalType: 'bytes4', name: '', type: 'bytes4' }],
stateMutability: 'view',
name: 'initializerV2',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
Expand Down Expand Up @@ -288,13 +309,6 @@ export const InstitutionalVault = <const>[
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'proxiableUUID',
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'uint256', name: 'shareAmount', type: 'uint256' }],
name: 'queueWithdrawals',
Expand All @@ -315,11 +329,19 @@ export const InstitutionalVault = <const>[
},
{
inputs: [
{ internalType: 'address', name: 'newAuthority', type: 'address' },
{
components: [
{ internalType: 'bytes', name: 'pubkey', type: 'bytes' },
{ internalType: 'uint64', name: 'amountGwei', type: 'uint64' },
],
internalType: 'struct IEigenPodTypes.WithdrawalRequest[]',
name: 'requests',
type: 'tuple[]',
},
],
name: 'setAuthority',
name: 'requestWithdrawal',
outputs: [],
stateMutability: 'nonpayable',
stateMutability: 'payable',
type: 'function',
},
{
Expand Down Expand Up @@ -435,7 +457,13 @@ export const InstitutionalVault = <const>[
stateMutability: 'nonpayable',
type: 'function',
},
{ stateMutability: 'payable', type: 'receive' },
{
inputs: [],
name: 'withdrawNonRestakedETH',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'bytes[]', name: 'srcPubkeys', type: 'bytes[]' },
Expand Down
71 changes: 71 additions & 0 deletions lib/contracts/abis/mainnet/NonRestakingWithdrawalCredentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export const NonRestakingWithdrawalCredentials = <const>[
{
inputs: [],
name: 'authority',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getConsolidationRequestFee',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getWithdrawalRequestFee',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'bytes', name: 'srcPubkey', type: 'bytes' },
{ internalType: 'bytes', name: 'targetPubkey', type: 'bytes' },
],
internalType: 'struct IEigenPodTypes.ConsolidationRequest[]',
name: 'requests',
type: 'tuple[]',
},
],
name: 'requestConsolidation',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'bytes', name: 'pubkey', type: 'bytes' },
{ internalType: 'uint64', name: 'amountGwei', type: 'uint64' },
],
internalType: 'struct IEigenPodTypes.WithdrawalRequest[]',
name: 'requests',
type: 'tuple[]',
},
],
name: 'requestWithdrawal',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [],
name: 'vault',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'withdrawETH',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ describe('InstitutionalVaultHandler', () => {
expect(result.toLowerCase()).toBe(address);
});

it('should get the non-restaking withdrawal credentials factory', async () => {
const address = generateAddress();
contractTestingUtils.mockCall(
'NON_RESTORAKING_WITHDRAWAL_CREDENTIALS_FACTORY',
[address],
);

const result =
await handler.getNonRestakingWithdrawalCredentialsFactory();
expect(result.toLowerCase()).toBe(address);
});

it('should get the WETH contract address', async () => {
const address = generateAddress();
contractTestingUtils.mockCall('WETH', [address]);

const result = await handler.getWeth();
expect(result.toLowerCase()).toBe(address);
});

it('should get the eigen pod', async () => {
const address = generateAddress();
contractTestingUtils.mockCall('getEigenPod', [address]);
Expand All @@ -81,6 +101,27 @@ describe('InstitutionalVaultHandler', () => {
expect(result.toLowerCase()).toBe(address);
});

it('should get the eigen pod withdrawal credentials', async () => {
const credentials =
'0x010000000000000000000000abcdef1234567890abcdef1234567890abcdef12';
contractTestingUtils.mockCall('getEigenPodWithdrawalCredentials', [
credentials,
]);

const result = await handler.getEigenPodWithdrawalCredentials();
expect(result.toLowerCase()).toBe(credentials);
});

it('should get the no-restaking withdrawal credentials', async () => {
const address = generateAddress();
contractTestingUtils.mockCall('getNoRestakingWithdrawalCredentials', [
address,
]);

const result = await handler.getNoRestakingWithdrawalCredentials();
expect(result.toLowerCase()).toBe(address);
});

it('should get the allowance', async () => {
const owner = generateAddress();
const spender = generateAddress();
Expand Down Expand Up @@ -336,6 +377,21 @@ describe('InstitutionalVaultHandler', () => {
expect(isHash(txHash)).toBeTruthy();
});

it('should request withdrawal from the EigenPod', async () => {
const requests = [
{
pubkey:
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as `0x${string}`,
amountGwei: BigInt(32_000_000_000),
},
];
const value = 1_000_000_000n; // 1 gwei
contractTestingUtils.mockTransaction('requestWithdrawal');

const txHash = await handler.requestWithdrawal(requests, value);
expect(isHash(txHash)).toBeTruthy();
});

it('should redeem shares to the receiver', async () => {
const shares = 10n;
const receiver = generateAddress();
Expand Down Expand Up @@ -442,5 +498,12 @@ describe('InstitutionalVaultHandler', () => {
);
expect(isHash(txHash)).toBeTruthy();
});

it('should withdraw non-restaked ETH', async () => {
contractTestingUtils.mockTransaction('withdrawNonRestakedETH');

const txHash = await handler.withdrawNonRestakedETH();
expect(isHash(txHash)).toBeTruthy();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { WalletClient, PublicClient, isHash } from 'viem';
import {
setupTestWalletClient,
setupTestPublicClient,
} from '../../../../test/setup-test-clients';
import { mockAccount, testingUtils } from '../../../../test/setup-tests';
import { NonRestakingWithdrawalCredentials } from '../../abis/mainnet/NonRestakingWithdrawalCredentials';
import { NonRestakingWithdrawalCredentialsHandler } from '../non-restaking-withdrawal-credentials-handler';
import { generateAddress } from '../../../../test/mocks/address';
import { InvalidContractAddressError } from '../../../errors/validation-errors';
import { Chain } from '../../../chains/constants';

describe('NonRestakingWithdrawalCredentialsHandler', () => {
const contractTestingUtils = testingUtils.generateContractUtils(
NonRestakingWithdrawalCredentials,
);
let handler: NonRestakingWithdrawalCredentialsHandler;
let walletClient: WalletClient;
let publicClient: PublicClient;

beforeEach(() => {
walletClient = setupTestWalletClient(Chain.Holesky, undefined, mockAccount);
publicClient = setupTestPublicClient();

handler = new NonRestakingWithdrawalCredentialsHandler(
Chain.Holesky,
walletClient,
publicClient,
);
});

it('should set and get the address used by the handler', () => {
const address = generateAddress();
handler.withAddress(address);

expect(handler.getAddress()).toBe(address);
});

it('should throw an error if the address is not set and a method is called', () => {
const address = generateAddress();
contractTestingUtils.mockCall('authority', [address]);

expect(() => handler.authority()).toThrow(InvalidContractAddressError);
});

describe('with address set', () => {
beforeEach(() => {
handler.withAddress(generateAddress());
});

it('should get the authority', async () => {
const address = generateAddress();
contractTestingUtils.mockCall('authority', [address]);

const result = await handler.authority();
expect(result.toLowerCase()).toBe(address);
});

it('should get the consolidation request fee', async () => {
const fee = 1000000000n; // 1 gwei
contractTestingUtils.mockCall('getConsolidationRequestFee', [fee]);

const result = await handler.getConsolidationRequestFee();
expect(result).toBe(fee);
});

it('should get the withdrawal request fee', async () => {
const fee = 1000000000n; // 1 gwei
contractTestingUtils.mockCall('getWithdrawalRequestFee', [fee]);

const result = await handler.getWithdrawalRequestFee();
expect(result).toBe(fee);
});

it('should get the vault address', async () => {
const address = generateAddress();
contractTestingUtils.mockCall('vault', [address]);

const result = await handler.vault();
expect(result.toLowerCase()).toBe(address);
});

it('should request consolidation', async () => {
const requests = [
{
srcPubkey:
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as `0x${string}`,
targetPubkey:
'0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678' as `0x${string}`,
},
];
const value = 1000000000n; // 1 gwei
contractTestingUtils.mockTransaction('requestConsolidation');

const txHash = await handler.requestConsolidation(requests, value);
expect(isHash(txHash)).toBeTruthy();
});

it('should request withdrawal', async () => {
const requests = [
{
pubkey:
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as `0x${string}`,
amountGwei: BigInt(32_000_000_000),
},
];
const value = 1000000000n; // 1 gwei
contractTestingUtils.mockTransaction('requestWithdrawal');

const txHash = await handler.requestWithdrawal(requests, value);
expect(isHash(txHash)).toBeTruthy();
});

it('should withdraw ETH', async () => {
contractTestingUtils.mockTransaction('withdrawETH');

const txHash = await handler.withdrawETH();
expect(isHash(txHash)).toBeTruthy();
});
});
});
Loading