diff --git a/lib/deposit.ts b/lib/deposit.ts new file mode 100644 index 000000000..f57b0763c --- /dev/null +++ b/lib/deposit.ts @@ -0,0 +1,29 @@ +import { sha256 } from "ethers"; +import { ONE_GWEI } from "./constants"; +import { bigintToHex } from "bigint-conversion"; +import { intToHex } from "ethereumjs-util"; + +export function computeDepositDataRoot(creds: string, pubkey: string, signature: string, amount: bigint) { + // strip everything of the 0x prefix to make 0x explicit when slicing + creds = creds.slice(2); + pubkey = pubkey.slice(2); + signature = signature.slice(2); + + const pubkeyRoot = sha256("0x" + pubkey + "00".repeat(16)).slice(2); + + const sigSlice1root = sha256("0x" + signature.slice(0, 128)).slice(2); + const sigSlice2root = sha256("0x" + signature.slice(128, signature.length) + "00".repeat(32)).slice(2); + const sigRoot = sha256("0x" + sigSlice1root + sigSlice2root).slice(2); + + const sizeInGweiLE64 = formatAmount(amount); + + const pubkeyCredsRoot = sha256("0x" + pubkeyRoot + creds).slice(2); + const sizeSigRoot = sha256("0x" + sizeInGweiLE64 + "00".repeat(24) + sigRoot).slice(2); + return sha256("0x" + pubkeyCredsRoot + sizeSigRoot); +} + +export function formatAmount(amount: bigint) { + const gweiAmount = amount / ONE_GWEI; + let bytes = bigintToHex(gweiAmount, false, 8); + return Buffer.from(bytes, "hex").reverse().toString("hex"); +} diff --git a/lib/index.ts b/lib/index.ts index f1df50e7f..6c2aa00a1 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -24,3 +24,4 @@ export * from "./time"; export * from "./transaction"; export * from "./type"; export * from "./units"; +export * from "./deposit"; diff --git a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts index 23aa5e7e9..86ddcfa6a 100644 --- a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts +++ b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts @@ -14,7 +14,7 @@ import { VaultHub__MockForStakingVault, } from "typechain-types"; -import { de0x, ether, findEvents, impersonate, streccak } from "lib"; +import { computeDepositDataRoot, de0x, ether, findEvents, impersonate, streccak } from "lib"; import { Snapshot } from "test/suite"; @@ -329,7 +329,7 @@ describe("StakingVault", () => { const signature = "0x" + "ef".repeat(96); const amount = ether("32"); const withdrawalCredentials = await stakingVault.withdrawalCredentials(); - const depositDataRoot = getRoot(withdrawalCredentials, pubkey, signature, amount); + const depositDataRoot = computeDepositDataRoot(withdrawalCredentials, pubkey, signature, amount); await expect( stakingVault.connect(operator).depositToBeaconChain([{ pubkey, signature, amount, depositDataRoot }]), @@ -499,27 +499,3 @@ describe("StakingVault", () => { return [stakingVault_, vaultHub_, vaultFactory_, stakingVaultImplementation_, depositContract_]; } }); - -function getRoot(creds: string, pubkey: string, signature: string, size: bigint) { - // strip everything of the 0x prefix to make 0x explicit when slicing - creds = creds.slice(2); - pubkey = pubkey.slice(2); - signature = signature.slice(2); - const sizeHex = size.toString(16); - - const pubkeyRoot = keccak256("0x" + pubkey + "00".repeat(16)).slice(2); - const sigSlice1root = keccak256("0x" + signature.slice(0, 128)).slice(2); - const sigSlice2root = keccak256("0x" + signature.slice(128, signature.length) + "00".repeat(32)).slice(2); - const sigRoot = keccak256("0x" + sigSlice1root + sigSlice2root).slice(2); - const sizeInGweiLE64 = toLittleEndian(sizeHex); - - const pubkeyCredsRoot = keccak256("0x" + pubkeyRoot + creds).slice(2); - const sizeSigRoot = keccak256("0x" + sizeInGweiLE64 + "00".repeat(24) + sigRoot).slice(2); - - return keccak256("0x" + pubkeyCredsRoot + sizeSigRoot); -} - -function toLittleEndian(value: string) { - const bytes = Buffer.from(value, "hex"); - return bytes.reverse().toString("hex"); -} diff --git a/test/integration/vaults-happy-path.integration.ts b/test/integration/vaults-happy-path.integration.ts index 673eba5af..f0de1997e 100644 --- a/test/integration/vaults-happy-path.integration.ts +++ b/test/integration/vaults-happy-path.integration.ts @@ -6,7 +6,7 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { Delegation, StakingVault } from "typechain-types"; -import { impersonate, log, streccak, trace, updateBalance } from "lib"; +import { computeDepositDataRoot, impersonate, log, streccak, trace, updateBalance } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; import { getReportTimeElapsed, @@ -258,7 +258,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { pubkey: pubkey, signature: signature, amount: VALIDATOR_DEPOSIT_SIZE, - depositDataRoot: getRoot(withdrawalCredentials, pubkey, signature, VALIDATOR_DEPOSIT_SIZE), + depositDataRoot: computeDepositDataRoot(withdrawalCredentials, pubkey, signature, VALIDATOR_DEPOSIT_SIZE), }); } @@ -475,27 +475,3 @@ describe("Scenario: Staking Vaults Happy Path", () => { expect(await stakingVault.locked()).to.equal(0); }); }); - -function getRoot(creds: string, pubkey: string, signature: string, size: bigint) { - // strip everything of the 0x prefix to make 0x explicit when slicing - creds = creds.slice(2); - pubkey = pubkey.slice(2); - signature = signature.slice(2); - const sizeHex = size.toString(16); - - const pubkeyRoot = keccak256("0x" + pubkey + "00".repeat(16)).slice(2); - const sigSlice1root = keccak256("0x" + signature.slice(0, 128)).slice(2); - const sigSlice2root = keccak256("0x" + signature.slice(128, signature.length) + "00".repeat(32)).slice(2); - const sigRoot = keccak256("0x" + sigSlice1root + sigSlice2root).slice(2); - const sizeInGweiLE64 = toLittleEndian(sizeHex); - - const pubkeyCredsRoot = keccak256("0x" + pubkeyRoot + creds).slice(2); - const sizeSigRoot = keccak256("0x" + sizeInGweiLE64 + "00".repeat(24) + sigRoot).slice(2); - - return keccak256("0x" + pubkeyCredsRoot + sizeSigRoot); -} - -function toLittleEndian(value: string) { - const bytes = Buffer.from(value, "hex"); - return bytes.reverse().toString("hex"); -}