diff --git a/README.md b/README.md index fd9aa57..39934b9 100644 --- a/README.md +++ b/README.md @@ -437,12 +437,8 @@ const isValidator = await client.isValidator("0x..."); // Get validator info const validatorInfo = await client.getValidatorInfo("0x..."); -// Join as validator (requires an owner account with funds and the operator key) -const registration = await createOperatorRegistration({ - privateKey: operatorPrivateKey, - ...(await client.getValidatorRegistrationContext()), -}); -const result = await client.validatorJoin({ amount: "42000gen", registration }); +// Join as validator (requires account with funds) +const result = await client.validatorJoin({ amount: "42000gen" }); // Join as delegator const delegateResult = await client.delegatorJoin({ diff --git a/docs/api-references/index.md b/docs/api-references/index.md index c08ef14..232f6bc 100644 --- a/docs/api-references/index.md +++ b/docs/api-references/index.md @@ -204,12 +204,8 @@ const isValidator = await client.isValidator("0x..."); // Get validator info const validatorInfo = await client.getValidatorInfo("0x..."); -// Join as validator (requires an owner account with funds and the operator key) -const registration = await createOperatorRegistration({ - privateKey: operatorPrivateKey, - ...(await client.getValidatorRegistrationContext()), -}); -const result = await client.validatorJoin({ amount: "42000gen", registration }); +// Join as validator (requires account with funds) +const result = await client.validatorJoin({ amount: "42000gen" }); // Join as delegator const delegateResult = await client.delegatorJoin({ diff --git a/docs/api-references/staking.md b/docs/api-references/staking.md index da70e2c..17f05a8 100644 --- a/docs/api-references/staking.md +++ b/docs/api-references/staking.md @@ -9,7 +9,7 @@ Joins as a validator with the specified stake amount. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | amount | `bigint \| string` | yes | | -| registration | `OperatorRegistrationProof` | yes | | +| operator | `Address` | no | | **Returns:** `ValidatorJoinResult` diff --git a/docs/api-references/types.Interface.ValidatorJoinOptions.md b/docs/api-references/types.Interface.ValidatorJoinOptions.md index 20518ac..3406f19 100644 --- a/docs/api-references/types.Interface.ValidatorJoinOptions.md +++ b/docs/api-references/types.Interface.ValidatorJoinOptions.md @@ -1,6 +1,6 @@ # Interface: ValidatorJoinOptions -Defined in: `src/types/staking.ts` +Defined in: [types/staking.ts:152](https://github.com/genlayerlabs/genlayer-js/blob/eaba6adec6803bdd0b4968e3f0763cf22107acd1/src/types/staking.ts#L152) ## Properties @@ -8,15 +8,12 @@ Defined in: `src/types/staking.ts` > **amount**: `string` \| `bigint` -Defined in: `src/types/staking.ts` +Defined in: [types/staking.ts:153](https://github.com/genlayerlabs/genlayer-js/blob/eaba6adec6803bdd0b4968e3f0763cf22107acd1/src/types/staking.ts#L153) *** -### registration +### operator? -> **registration**: `OperatorRegistrationProof` +> `optional` **operator?**: `` `0x${string}` `` -Proof-of-possession package bound to this chain, the validator wallet factory, -and the joining owner address. - -Defined in: `src/types/staking.ts` +Defined in: [types/staking.ts:154](https://github.com/genlayerlabs/genlayer-js/blob/eaba6adec6803bdd0b4968e3f0763cf22107acd1/src/types/staking.ts#L154) diff --git a/src/abi/staking.ts b/src/abi/staking.ts index ad6e6a7..4d25966 100644 --- a/src/abi/staking.ts +++ b/src/abi/staking.ts @@ -1249,10 +1249,14 @@ export const STAKING_ABI = [ name: "validatorJoin", type: "function", stateMutability: "payable", - inputs: [ - {name: "_operatorPubKey", type: "uint256[2]"}, - {name: "_possessionProof", type: "bytes"}, - ], + inputs: [{name: "_operator", type: "address"}], + outputs: [{name: "", type: "address"}], + }, + { + name: "validatorJoin", + type: "function", + stateMutability: "payable", + inputs: [], outputs: [{name: "", type: "address"}], }, { diff --git a/src/abi/vesting.ts b/src/abi/vesting.ts index f874813..0dc7adc 100644 --- a/src/abi/vesting.ts +++ b/src/abi/vesting.ts @@ -135,17 +135,7 @@ export const VESTING_ABI = [ {name: "vestingDelegatorJoin", type: "function", stateMutability: "nonpayable", inputs: [{name: "validator", type: "address"}, {name: "amount", type: "uint256"}], outputs: []}, {name: "vestingDelegatorExit", type: "function", stateMutability: "nonpayable", inputs: [{name: "validator", type: "address"}, {name: "shares", type: "uint256"}], outputs: []}, {name: "vestingDelegatorClaim", type: "function", stateMutability: "nonpayable", inputs: [{name: "validator", type: "address"}], outputs: []}, - { - name: "vestingValidatorJoin", - type: "function", - stateMutability: "nonpayable", - inputs: [ - {name: "operatorPubKey", type: "uint256[2]"}, - {name: "possessionProof", type: "bytes"}, - {name: "amount", type: "uint256"}, - ], - outputs: [], - }, + {name: "vestingValidatorJoin", type: "function", stateMutability: "nonpayable", inputs: [{name: "operator", type: "address"}, {name: "amount", type: "uint256"}], outputs: []}, {name: "vestingValidatorDeposit", type: "function", stateMutability: "nonpayable", inputs: [{name: "wallet", type: "address"}, {name: "amount", type: "uint256"}], outputs: []}, {name: "vestingValidatorExit", type: "function", stateMutability: "nonpayable", inputs: [{name: "wallet", type: "address"}, {name: "shares", type: "uint256"}], outputs: []}, {name: "vestingValidatorClaim", type: "function", stateMutability: "nonpayable", inputs: [{name: "wallet", type: "address"}], outputs: []}, diff --git a/src/index.ts b/src/index.ts index 7e0b13d..fe3a8f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,18 +12,5 @@ export * as abi from "./abi"; export * from "./transactions/fees"; export {isSuccessful} from "./transactions/actions"; export {parseStakingAmount, formatStakingAmount} from "./staking"; -export { - OPERATOR_REGISTRATION_DOMAIN, - createOperatorRegistration, - operatorAddressFromPublicKey, - operatorPossessionMessage, - verifyOperatorRegistration, - vestingActions, -} from "./vesting"; -export type { - CreateOperatorRegistrationOptions, - OperatorPublicKey, - OperatorRegistrationContext, - OperatorRegistrationProof, -} from "./vesting"; +export {vestingActions} from "./vesting"; export {buildGenVmPositionalArgs} from "./contracts/schema"; diff --git a/src/staking/actions.ts b/src/staking/actions.ts index d6eea76..a96f86c 100644 --- a/src/staking/actions.ts +++ b/src/staking/actions.ts @@ -1,9 +1,7 @@ -import {getContract, decodeEventLog, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress, GetContractReturnType, toHex, encodeFunctionData, BaseError, ContractFunctionRevertedError, decodeErrorResult, RawContractError, zeroAddress} from "viem"; +import {getContract, decodeEventLog, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress, GetContractReturnType, toHex, encodeFunctionData, BaseError, ContractFunctionRevertedError, decodeErrorResult, RawContractError} from "viem"; import {GenLayerClient, GenLayerChain, Address} from "@/types"; import {STAKING_ABI, VALIDATOR_WALLET_ABI} from "@/abi/staking"; -import {ADDRESS_MANAGER_ABI, CONSENSUS_ADDRESS_MANAGER_ABI} from "@/abi/vesting"; import {parseStakingAmount, formatStakingAmount} from "./utils"; -import {operatorAddressFromPublicKey, verifyOperatorRegistration} from "@/vesting/operatorRegistration"; import { ValidatorInfo, ValidatorIdentity, @@ -34,7 +32,6 @@ type WalletClientWithAccount = Client; const FALLBACK_GAS = 1000000n; const GAS_BUFFER_MULTIPLIER = 2n; -const VALIDATOR_WALLET_FACTORY_KEY = "ValidatorWalletFactory"; // Combined ABI for error decoding (both staking and validator wallet errors) const COMBINED_ERROR_ABI = [...STAKING_ABI, ...VALIDATOR_WALLET_ABI]; @@ -239,59 +236,22 @@ export const stakingActions = ( }); }; - const getValidatorRegistrationContext = async () => { - if (!client.account) { - throw new Error("Account is required to resolve validator registration context."); - } - - const consensusMain = client.chain.consensusMainContract; - if (!consensusMain?.address || consensusMain.address === zeroAddress) { - throw new Error("Cannot resolve ValidatorWalletFactory without a consensus main contract."); - } - - const [addressManager, chainId] = await Promise.all([ - publicClient.readContract({ - address: consensusMain.address as ViemAddress, - abi: CONSENSUS_ADDRESS_MANAGER_ABI, - functionName: "getAddressManager", - }) as Promise
, - publicClient.getChainId(), - ]); - const registrar = await publicClient.readContract({ - address: addressManager as ViemAddress, - abi: ADDRESS_MANAGER_ABI, - functionName: "getAddress", - args: [VALIDATOR_WALLET_FACTORY_KEY], - }) as Address; - - if (!registrar || registrar === zeroAddress) { - throw new Error( - `ValidatorWalletFactory is not registered in AddressManager under key ${VALIDATOR_WALLET_FACTORY_KEY}.`, - ); - } - - return { - registrar, - owner: client.account.address as Address, - chainId: BigInt(chainId), - }; - }; - return { /** Joins as a validator with the specified stake amount. */ validatorJoin: async (options: ValidatorJoinOptions): Promise => { const amount = parseStakingAmount(options.amount); const stakingAddress = getStakingAddress(); - const context = await getValidatorRegistrationContext(); - if (!await verifyOperatorRegistration(options.registration, context)) { - throw new Error("Operator registration proof does not match the owner, registrar, chain, or public key."); - } - const operator = operatorAddressFromPublicKey(options.registration.operatorPubKey); - const data = encodeFunctionData({ - abi: STAKING_ABI, - functionName: "validatorJoin", - args: [options.registration.operatorPubKey, options.registration.possessionProof], - }); + + const data = options.operator + ? encodeFunctionData({ + abi: STAKING_ABI, + functionName: "validatorJoin", + args: [options.operator as ViemAddress], + }) + : encodeFunctionData({ + abi: STAKING_ABI, + functionName: "validatorJoin", + }); const result = await executeWrite({to: stakingAddress, data, value: amount}); const receipt = await publicClient.getTransactionReceipt({hash: result.transactionHash}); @@ -323,13 +283,11 @@ export const stakingActions = ( blockNumber: receipt.blockNumber, gasUsed: receipt.gasUsed, validatorWallet: validatorWallet!, - operator, + operator: options.operator || (client.account!.address as Address), amount: formatStakingAmount(amount), amountRaw: amount, }; }, - /** Resolves the registrar, owner, and chain binding required to create an operator proof. */ - getValidatorRegistrationContext, /** * Adds additional self-stake to an active validator position. The diff --git a/src/types/staking.ts b/src/types/staking.ts index 237ae4a..9d9c657 100644 --- a/src/types/staking.ts +++ b/src/types/staking.ts @@ -1,7 +1,6 @@ import {Address} from "./accounts"; import {GetContractReturnType, PublicClient, Client, Transport, Chain, Account, Address as ViemAddress} from "viem"; import {STAKING_ABI} from "@/abi/staking"; -import type {OperatorRegistrationContext, OperatorRegistrationProof} from "./vesting"; type WalletClientWithAccount = Client; @@ -156,7 +155,7 @@ export interface DelegatorJoinResult extends StakingTransactionResult { export interface ValidatorJoinOptions { amount: bigint | string; - registration: OperatorRegistrationProof; + operator?: Address; } export interface ValidatorDepositOptions { @@ -212,7 +211,6 @@ export interface DelegatorClaimOptions { export interface StakingActions { validatorJoin: (options: ValidatorJoinOptions) => Promise; - getValidatorRegistrationContext: () => Promise; validatorDeposit: (options: ValidatorDepositOptions) => Promise; validatorExit: (options: ValidatorExitOptions) => Promise; validatorClaim: (options?: ValidatorClaimOptions) => Promise; diff --git a/src/types/vesting.ts b/src/types/vesting.ts index 5e32ee2..1c3d3ae 100644 --- a/src/types/vesting.ts +++ b/src/types/vesting.ts @@ -1,4 +1,4 @@ -import {Account, Address as ViemAddress, Chain, Client, GetContractReturnType, Hex, PublicClient, Transport} from "viem"; +import {Account, Address as ViemAddress, Chain, Client, GetContractReturnType, PublicClient, Transport} from "viem"; import {Address} from "./accounts"; import {VESTING_ABI, VESTING_FACTORY_ABI} from "@/abi/vesting"; @@ -14,24 +14,6 @@ export type VestingFactoryContract = GetContractReturnType Promise
; vestingFactory: (vesting: Address) => Promise
; vestingAddressManager: (vesting: Address) => Promise
; - getVestingValidatorRegistrationContext: (vesting: Address) => Promise; vestingTotalAmount: (vesting: Address) => Promise; vestingStartDate: (vesting: Address) => Promise; vestingCliffDuration: (vesting: Address) => Promise; diff --git a/src/vesting/actions.ts b/src/vesting/actions.ts index 86020f6..5e2cef1 100644 --- a/src/vesting/actions.ts +++ b/src/vesting/actions.ts @@ -41,17 +41,12 @@ import { VestingWithdrawResult, } from "@/types/vesting"; import {formatStakingAmount, parseStakingAmount} from "@/staking/utils"; -import { - operatorAddressFromPublicKey, - verifyOperatorRegistration, -} from "./operatorRegistration"; type WalletClientWithAccount = Client; const FALLBACK_GAS = 1000000n; const GAS_BUFFER_MULTIPLIER = 2n; const VESTING_FACTORY_KEY = "VestingFactory"; -const VALIDATOR_WALLET_FACTORY_KEY = "ValidatorWalletFactory"; const COMBINED_ERROR_ABI = [...VESTING_ABI, ...VESTING_FACTORY_ABI, ...ADDRESS_MANAGER_ABI, ...STAKING_ABI] as const; function extractRevertReason(err: unknown): string { @@ -273,31 +268,6 @@ export const vestingActions = ( return factory; }; - const getVestingValidatorRegistrationContext = async (vesting: Address) => { - const [addressManager, chainId] = await Promise.all([ - readVesting
(vesting, "addressManager"), - publicClient.getChainId(), - ]); - const registrar = await publicClient.readContract({ - address: addressManager as ViemAddress, - abi: ADDRESS_MANAGER_ABI, - functionName: "getAddress", - args: [VALIDATOR_WALLET_FACTORY_KEY], - }) as Address; - - if (!registrar || registrar === zeroAddress) { - throw new Error( - `ValidatorWalletFactory is not registered in AddressManager under key ${VALIDATOR_WALLET_FACTORY_KEY}.`, - ); - } - - return { - registrar, - owner: vesting, - chainId: BigInt(chainId), - }; - }; - const getVestingContract = (vesting: Address): VestingContract => { return getContract({ address: vesting as ViemAddress, @@ -359,22 +329,17 @@ export const vestingActions = ( /** Creates a validator wallet and self-stakes vesting-held tokens. Must be called by the vesting beneficiary. */ vestingValidatorJoin: async (options: VestingValidatorJoinOptions): Promise => { const amount = parseStakingAmount(options.amount); - const context = await getVestingValidatorRegistrationContext(options.vesting); - if (!await verifyOperatorRegistration(options.registration, context)) { - throw new Error("Operator registration proof does not match the vesting, registrar, chain, or public key."); - } - const operator = operatorAddressFromPublicKey(options.registration.operatorPubKey); const data = encodeFunctionData({ abi: VESTING_ABI, functionName: "vestingValidatorJoin", - args: [options.registration.operatorPubKey, options.registration.possessionProof, amount], + args: [options.operator as ViemAddress, amount], }); const result = await executeWrite({to: options.vesting as ViemAddress, data}); return { ...result, vesting: options.vesting, - operator, + operator: options.operator, beneficiary: client.account!.address as Address, amount: formatStakingAmount(amount), amountRaw: amount, @@ -645,7 +610,6 @@ export const vestingActions = ( vestingRevoker: (vesting: Address): Promise
=> readVesting
(vesting, "revoker"), vestingFactory: (vesting: Address): Promise
=> readVesting
(vesting, "factory"), vestingAddressManager: (vesting: Address): Promise
=> readVesting
(vesting, "addressManager"), - getVestingValidatorRegistrationContext, vestingTotalAmount: (vesting: Address): Promise => readVesting(vesting, "totalAmount"), vestingStartDate: (vesting: Address): Promise => readVesting(vesting, "startDate"), vestingCliffDuration: (vesting: Address): Promise => readVesting(vesting, "cliffDuration"), diff --git a/src/vesting/operatorRegistration.ts b/src/vesting/operatorRegistration.ts deleted file mode 100644 index d7b4442..0000000 --- a/src/vesting/operatorRegistration.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - concatHex, - encodeAbiParameters, - getAddress, - hexToBigInt, - keccak256, - recoverMessageAddress, - sliceHex, - stringToHex, - toHex, - type Address, - type Hex, -} from "viem"; -import {privateKeyToAccount, publicKeyToAddress} from "viem/accounts"; -import type { - CreateOperatorRegistrationOptions, - OperatorPublicKey, - OperatorRegistrationContext, - OperatorRegistrationProof, -} from "@/types/vesting"; - -export type { - CreateOperatorRegistrationOptions, - OperatorPublicKey, - OperatorRegistrationContext, - OperatorRegistrationProof, -} from "@/types/vesting"; - -export const OPERATOR_REGISTRATION_DOMAIN = keccak256( - stringToHex("GenLayer/operatorPubKey/proof-of-possession/v1"), -); - -export function operatorAddressFromPublicKey(operatorPubKey: OperatorPublicKey): Address { - const publicKey = concatHex([ - "0x04", - toHex(operatorPubKey[0], {size: 32}), - toHex(operatorPubKey[1], {size: 32}), - ]); - return getAddress(publicKeyToAddress(publicKey)); -} - -export function operatorPossessionMessage( - operatorPubKey: OperatorPublicKey, - context: OperatorRegistrationContext, -): Hex { - return keccak256( - encodeAbiParameters( - [ - {type: "bytes32"}, - {type: "uint256"}, - {type: "address"}, - {type: "address"}, - {type: "uint256"}, - {type: "uint256"}, - ], - [ - OPERATOR_REGISTRATION_DOMAIN, - context.chainId, - context.registrar, - context.owner, - operatorPubKey[0], - operatorPubKey[1], - ], - ), - ); -} - -/** - * Builds the proof package consumed by proof-bearing validator registration. - * The private key is used only in memory and is never included in the result. - */ -export async function createOperatorRegistration( - options: CreateOperatorRegistrationOptions, -): Promise { - const account = privateKeyToAccount(options.privateKey); - const operatorPubKey: OperatorPublicKey = [ - hexToBigInt(sliceHex(account.publicKey, 1, 33)), - hexToBigInt(sliceHex(account.publicKey, 33, 65)), - ]; - const operator = operatorAddressFromPublicKey(operatorPubKey); - - if (operator !== getAddress(account.address)) { - throw new Error("Operator private key and public key derive different identities."); - } - - const possessionProof = await account.signMessage({ - message: {raw: operatorPossessionMessage(operatorPubKey, options)}, - }); - - return {operator, operatorPubKey, possessionProof}; -} - -/** Validates the key identity and the exact registrar/owner/chain-bound proof. */ -export async function verifyOperatorRegistration( - registration: OperatorRegistrationProof, - context: OperatorRegistrationContext, -): Promise { - try { - const operator = operatorAddressFromPublicKey(registration.operatorPubKey); - if (operator !== getAddress(registration.operator)) return false; - - const recovered = await recoverMessageAddress({ - message: {raw: operatorPossessionMessage(registration.operatorPubKey, context)}, - signature: registration.possessionProof, - }); - return getAddress(recovered) === operator; - } catch { - return false; - } -} diff --git a/src/vesting/validator.ts b/src/vesting/validator.ts index df40501..fa01a00 100644 --- a/src/vesting/validator.ts +++ b/src/vesting/validator.ts @@ -7,16 +7,4 @@ export type { VestingValidatorJoinResult, VestingValidatorSetIdentityOptions, VestingValidatorWalletOptions, - CreateOperatorRegistrationOptions, - OperatorPublicKey, - OperatorRegistrationContext, - OperatorRegistrationProof, } from "@/types/vesting"; - -export { - OPERATOR_REGISTRATION_DOMAIN, - createOperatorRegistration, - operatorAddressFromPublicKey, - operatorPossessionMessage, - verifyOperatorRegistration, -} from "./operatorRegistration"; diff --git a/tests/operator-registration.test.ts b/tests/operator-registration.test.ts deleted file mode 100644 index 4a616a0..0000000 --- a/tests/operator-registration.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import {describe, expect, it} from "vitest"; -import {getAddress} from "viem"; -import {privateKeyToAccount} from "viem/accounts"; -import { - OPERATOR_REGISTRATION_DOMAIN, - createOperatorRegistration, - operatorPossessionMessage, - verifyOperatorRegistration, - type OperatorRegistrationContext, -} from "../src/vesting/operatorRegistration"; - -const OPERATOR_KEY = "0x0000000000000000000000000000000000000000000000000000000000000002"; -const OTHER_OPERATOR_KEY = "0x0000000000000000000000000000000000000000000000000000000000000003"; -const CONTEXT: OperatorRegistrationContext = { - registrar: "0x1111111111111111111111111111111111111111", - owner: "0x2222222222222222222222222222222222222222", - chainId: 61999n, -}; - -describe("operator registration", () => { - it("matches the consensus proof-of-possession vector", async () => { - const registration = await createOperatorRegistration({ - privateKey: OPERATOR_KEY, - ...CONTEXT, - }); - - expect(OPERATOR_REGISTRATION_DOMAIN).toBe( - "0x56a1f863be2956668ca2fd6b4010d6fde7a54f2b5a02d6c624a2bad7e5fd5ada", - ); - expect(registration.operator).toBe(getAddress("0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF")); - expect(registration.operatorPubKey).toEqual([ - 89565891926547004231252920425935692360644145829622209833684329913297188986597n, - 12158399299693830322967808612713398636155367887041628176798871954788371653930n, - ]); - expect(operatorPossessionMessage(registration.operatorPubKey, CONTEXT)).toBe( - "0x7823e1bdaf3a8cea679a7bafaf8ddc39c379ac690f35696328650c3a712f36e0", - ); - expect(registration.possessionProof).toBe( - "0x30cedc70f8ab478fbc1a13a3f36e7f6a10eed631f59db4c451e38fe6d94dc640586d7a3202471043dbad68a3850655d39114aaca647df5734b171f8db7e88f161c", - ); - await expect(verifyOperatorRegistration(registration, CONTEXT)).resolves.toBe(true); - }); - - it("rejects wrong-key and cross-domain proofs", async () => { - const registration = await createOperatorRegistration({ - privateKey: OPERATOR_KEY, - ...CONTEXT, - }); - const wrongKey = privateKeyToAccount(OTHER_OPERATOR_KEY); - const wrongKeyProof = await wrongKey.signMessage({ - message: {raw: operatorPossessionMessage(registration.operatorPubKey, CONTEXT)}, - }); - - await expect( - verifyOperatorRegistration({...registration, possessionProof: wrongKeyProof}, CONTEXT), - ).resolves.toBe(false); - await expect( - verifyOperatorRegistration(registration, { - ...CONTEXT, - registrar: "0x3333333333333333333333333333333333333333", - }), - ).resolves.toBe(false); - await expect( - verifyOperatorRegistration(registration, { - ...CONTEXT, - owner: "0x4444444444444444444444444444444444444444", - }), - ).resolves.toBe(false); - await expect( - verifyOperatorRegistration(registration, {...CONTEXT, chainId: CONTEXT.chainId + 1n}), - ).resolves.toBe(false); - }); -}); diff --git a/tests/staking-actions.test.ts b/tests/staking-actions.test.ts index 6465852..f58ce33 100644 --- a/tests/staking-actions.test.ts +++ b/tests/staking-actions.test.ts @@ -2,16 +2,11 @@ import {describe, expect, it, vi} from "vitest"; import {decodeFunctionData, encodeAbiParameters, encodeEventTopics, getAbiItem, parseEther} from "viem"; import {STAKING_ABI, VALIDATOR_WALLET_ABI} from "../src/abi/staking"; import {stakingActions} from "../src/staking/actions"; -import {createOperatorRegistration} from "../src/vesting/operatorRegistration"; const ACCOUNT_ADDRESS = "0x0000000000000000000000000000000000000011"; const STAKING_ADDRESS = "0x0000000000000000000000000000000000000044"; const VALIDATOR_WALLET_ADDRESS = "0x0000000000000000000000000000000000000099"; -const CONSENSUS_MAIN_ADDRESS = "0x0000000000000000000000000000000000000066"; -const ADDRESS_MANAGER_ADDRESS = "0x0000000000000000000000000000000000000077"; -const VALIDATOR_WALLET_FACTORY_ADDRESS = "0x0000000000000000000000000000000000000088"; -const OPERATOR_PRIVATE_KEY = "0x0000000000000000000000000000000000000000000000000000000000000002"; -const OPERATOR_ADDRESS = "0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF"; +const OPERATOR_ADDRESS = "0x00000000000000000000000000000000000000AA"; const GAS_PRICE_HEX = "0x3b9aca00"; const MOCK_TX_HASH = "0x1234000000000000000000000000000000000000000000000000000000001234"; @@ -40,22 +35,8 @@ const baseChain = { rpcUrls: {default: {http: ["http://127.0.0.1"]}}, isStudio: false, stakingContract: {address: STAKING_ADDRESS}, - consensusMainContract: {address: CONSENSUS_MAIN_ADDRESS}, }; -const makeRegistration = () => createOperatorRegistration({ - privateKey: OPERATOR_PRIVATE_KEY, - registrar: VALIDATOR_WALLET_FACTORY_ADDRESS, - owner: ACCOUNT_ADDRESS, - chainId: BigInt(baseChain.id), -}); - -const readRegistrationContract = vi.fn().mockImplementation(async ({functionName}: any) => { - if (functionName === "getAddressManager") return ADDRESS_MANAGER_ADDRESS; - if (functionName === "getAddress") return VALIDATOR_WALLET_FACTORY_ADDRESS; - throw new Error(`Unexpected read: ${functionName}`); -}); - // Local-key harness (byte-for-byte regression anchor for the sign+sendRaw lane). const makeLocalHarness = () => { const signTransaction = vi.fn().mockResolvedValue("0xsigned"); @@ -71,8 +52,7 @@ const makeLocalHarness = () => { sendRawTransaction: vi.fn().mockResolvedValue(MOCK_TX_HASH), waitForTransactionReceipt: vi.fn().mockResolvedValue(makeReceipt()), getTransactionReceipt: vi.fn().mockResolvedValue(makeReceipt()), - readContract: readRegistrationContract, - getChainId: vi.fn().mockResolvedValue(baseChain.id), + readContract: vi.fn(), }; return {actions: stakingActions(client as any, publicClient as any), client, publicClient, signTransaction}; }; @@ -99,8 +79,7 @@ const makeProviderHarness = () => { sendRawTransaction: vi.fn().mockResolvedValue(MOCK_TX_HASH), waitForTransactionReceipt: vi.fn().mockResolvedValue(makeReceipt()), getTransactionReceipt: vi.fn().mockResolvedValue(makeReceipt()), - readContract: readRegistrationContract, - getChainId: vi.fn().mockResolvedValue(baseChain.id), + readContract: vi.fn(), }; return {actions: stakingActions(client as any, publicClient as any), client, publicClient, request, signTransaction}; }; @@ -114,15 +93,14 @@ describe("stakingActions local lane", () => { it("validatorJoin encodes the call, decodes the ValidatorJoin event, and returns the full shape", async () => { const {actions, publicClient, signTransaction} = makeLocalHarness(); - const registration = await makeRegistration(); - const result = await actions.validatorJoin({amount: "2gen", registration}); + const result = await actions.validatorJoin({amount: "2gen", operator: OPERATOR_ADDRESS}); // Encoding routed to the staking contract with msg.value = stake amount. expect(publicClient.call.mock.calls[0][0].to).toBe(STAKING_ADDRESS); expect(publicClient.call.mock.calls[0][0].value).toBe(parseEther("2")); expect(decodeFunctionData({abi: STAKING_ABI, data: publicClient.call.mock.calls[0][0].data})).toEqual({ functionName: "validatorJoin", - args: [registration.operatorPubKey, registration.possessionProof], + args: [OPERATOR_ADDRESS], }); // Local sign+sendRaw path. @@ -139,17 +117,6 @@ describe("stakingActions local lane", () => { amountRaw: parseEther("2"), }); }); - - it("rejects a registration proof that is not bound to the joining owner and registrar", async () => { - const {actions, publicClient} = makeLocalHarness(); - const registration = await makeRegistration(); - - await expect(actions.validatorJoin({ - amount: "2gen", - registration: {...registration, possessionProof: "0x1234"}, - })).rejects.toThrow(/registration proof does not match/i); - expect(publicClient.call).not.toHaveBeenCalled(); - }); }); describe("stakingActions provider lane (Address-only)", () => { @@ -201,7 +168,7 @@ describe("stakingActions provider lane (Address-only)", () => { it("validatorJoin decodes the ValidatorJoin event off the provider-returned hash", async () => { const {actions, request, signTransaction} = makeProviderHarness(); - const result = await actions.validatorJoin({amount: "2gen", registration: await makeRegistration()}); + const result = await actions.validatorJoin({amount: "2gen", operator: OPERATOR_ADDRESS}); // Sent via the provider lane, not signed locally. expect(sentTxParams(request).to).toBe(STAKING_ADDRESS); diff --git a/tests/vesting-actions.test.ts b/tests/vesting-actions.test.ts index 05f9f9a..ec60a1f 100644 --- a/tests/vesting-actions.test.ts +++ b/tests/vesting-actions.test.ts @@ -2,19 +2,17 @@ import {describe, expect, it, vi} from "vitest"; import {decodeFunctionData, parseEther, toHex, zeroAddress} from "viem"; import {VESTING_ABI} from "../src/abi/vesting"; import {vestingActions} from "../src/vesting/actions"; -import {createOperatorRegistration} from "../src/vesting/operatorRegistration"; const ACCOUNT_ADDRESS = "0x0000000000000000000000000000000000000011"; const BENEFICIARY_ADDRESS = ACCOUNT_ADDRESS; const VESTING_ADDRESS = "0x0000000000000000000000000000000000000022"; const VALIDATOR_ADDRESS = "0x0000000000000000000000000000000000000033"; const VALIDATOR_WALLET_ADDRESS = "0x0000000000000000000000000000000000000099"; +const OPERATOR_ADDRESS = "0x00000000000000000000000000000000000000AA"; const NEW_OPERATOR_ADDRESS = "0x00000000000000000000000000000000000000bb"; const CONSENSUS_MAIN_ADDRESS = "0x0000000000000000000000000000000000000044"; const ADDRESS_MANAGER_ADDRESS = "0x0000000000000000000000000000000000000055"; const FACTORY_ADDRESS = "0x0000000000000000000000000000000000000066"; -const VALIDATOR_WALLET_FACTORY_ADDRESS = "0x0000000000000000000000000000000000000077"; -const OPERATOR_KEY = "0x0000000000000000000000000000000000000000000000000000000000000002"; const MOCK_TX_HASH = "0x1234000000000000000000000000000000000000000000000000000000001234"; const makeReceipt = () => ({ @@ -56,7 +54,6 @@ const makeHarness = () => { prepareTransactionRequest: vi.fn().mockImplementation(async request => request), sendRawTransaction: vi.fn().mockResolvedValue(MOCK_TX_HASH), waitForTransactionReceipt: vi.fn().mockResolvedValue(makeReceipt()), - getChainId: vi.fn().mockResolvedValue(1), readContract: vi.fn(), }; @@ -134,39 +131,23 @@ describe("vestingActions", () => { }); it("encodes vesting validator join and deposit without caller value", async () => { - const {actions, client, publicClient} = makeHarness(); - client.chain.id = 999; - publicClient.readContract.mockImplementation(async ({address, functionName, args}: any) => { - if (address === VESTING_ADDRESS && functionName === "addressManager") return ADDRESS_MANAGER_ADDRESS; - if (address === ADDRESS_MANAGER_ADDRESS && functionName === "getAddress") { - expect(args).toEqual(["ValidatorWalletFactory"]); - return VALIDATOR_WALLET_FACTORY_ADDRESS; - } - throw new Error(`Unexpected read: ${functionName}`); - }); - const registration = await createOperatorRegistration({ - privateKey: OPERATOR_KEY, - registrar: VALIDATOR_WALLET_FACTORY_ADDRESS, - owner: VESTING_ADDRESS, - chainId: 1n, - }); + const {actions, publicClient} = makeHarness(); const result = await actions.vestingValidatorJoin({ vesting: VESTING_ADDRESS, - registration, + operator: OPERATOR_ADDRESS, amount: "3gen", }); expect(publicClient.call.mock.calls[0][0].to).toBe(VESTING_ADDRESS); - expect(publicClient.getChainId).toHaveBeenCalledTimes(1); expect(publicClient.call.mock.calls[0][0].value).toBeUndefined(); expect(decodedWrite(publicClient)).toEqual({ functionName: "vestingValidatorJoin", - args: [registration.operatorPubKey, registration.possessionProof, parseEther("3")], + args: [OPERATOR_ADDRESS, parseEther("3")], }); expect(result).toMatchObject({ vesting: VESTING_ADDRESS, - operator: registration.operator, + operator: OPERATOR_ADDRESS, beneficiary: ACCOUNT_ADDRESS, amount: "3 GEN", amountRaw: parseEther("3"), @@ -396,7 +377,6 @@ const makeProviderHarness = () => { prepareTransactionRequest: vi.fn().mockImplementation(async (r: any) => r), sendRawTransaction: vi.fn().mockResolvedValue(MOCK_TX_HASH), waitForTransactionReceipt: vi.fn().mockResolvedValue(makeReceipt()), - getChainId: vi.fn().mockResolvedValue(1), readContract: vi.fn(), };