diff --git a/Anchor.toml b/Anchor.toml index 5d4cf2ad..85a9e917 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -19,7 +19,7 @@ cluster = "localnet" wallet = "test/svm/keys/localnet-wallet.json" [scripts] -test = "anchor run generateExternalTypes && yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/**/*.ts" +test = "anchor run generateExternalTypes && yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/svm/**/*.ts" initialize = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/initialize.ts" queryState = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/queryState.ts" enableRoute = "NODE_NO_WARNINGS=1 yarn run ts-node ./scripts/svm/enableRoute.ts" diff --git a/programs/svm-spoke/src/utils.rs b/programs/svm-spoke/src/utils.rs new file mode 100644 index 00000000..bd394270 --- /dev/null +++ b/programs/svm-spoke/src/utils.rs @@ -0,0 +1,153 @@ +use crate::error::CustomError; +use anchor_lang::prelude::*; +use anchor_lang::solana_program::keccak; +use std::mem::size_of_val; + +use crate::{ + constants::DISCRIMINATOR_SIZE, error::CalldataError, instructions::V3RelayData, + program::SvmSpoke, +}; + +pub trait EncodeInstructionData { + fn encode_instruction_data(&self, discriminator_str: &str) -> Result>; +} + +impl EncodeInstructionData for T { + fn encode_instruction_data(&self, discriminator_str: &str) -> Result> { + let mut data = Vec::with_capacity(DISCRIMINATOR_SIZE + size_of_val(self)); + data.extend_from_slice( + &anchor_lang::solana_program::hash::hash(discriminator_str.as_bytes()).to_bytes() + [..DISCRIMINATOR_SIZE], + ); + data.extend_from_slice(&self.try_to_vec()?); + + Ok(data) + } +} + +pub fn encode_solidity_selector(signature: &str) -> [u8; 4] { + let hash = anchor_lang::solana_program::keccak::hash(signature.as_bytes()); + let mut selector = [0u8; 4]; + selector.copy_from_slice(&hash.to_bytes()[..4]); + selector +} + +pub fn get_solidity_selector(data: &Vec) -> Result<[u8; 4]> { + let slice = data.get(..4).ok_or(CalldataError::InvalidSelector)?; + let array = <[u8; 4]>::try_from(slice).unwrap(); + Ok(array) +} + +pub fn get_solidity_arg(data: &Vec, index: usize) -> Result<[u8; 32]> { + let offset = 4 + 32 * index; + let slice = data + .get(offset..offset + 32) + .ok_or(CalldataError::InvalidArgument)?; + let array = <[u8; 32]>::try_from(slice).unwrap(); + Ok(array) +} + +pub fn decode_solidity_bool(data: &[u8; 32]) -> Result { + let h_value = u128::from_be_bytes(data[..16].try_into().unwrap()); + let l_value = u128::from_be_bytes(data[16..].try_into().unwrap()); + match h_value { + 0 => match l_value { + 0 => Ok(false), + 1 => Ok(true), + _ => return Err(CalldataError::InvalidBool.into()), + }, + _ => return Err(CalldataError::InvalidBool.into()), + } +} + +pub fn get_self_authority_pda() -> Pubkey { + let (pda_address, _bump) = Pubkey::find_program_address(&[b"self_authority"], &SvmSpoke::id()); + pda_address +} + +pub fn decode_solidity_uint64(data: &[u8; 32]) -> Result { + let h_value = u128::from_be_bytes(data[..16].try_into().unwrap()); + let l_value = u128::from_be_bytes(data[16..].try_into().unwrap()); + if h_value > 0 || l_value > u64::MAX as u128 { + return Err(CalldataError::InvalidUint64.into()); + } + Ok(l_value as u64) +} + +pub fn decode_solidity_address(data: &[u8; 32]) -> Result { + for i in 0..12 { + if data[i] != 0 { + return Err(CalldataError::InvalidAddress.into()); + } + } + Ok(Pubkey::new_from_array(*data)) +} + +// Across specific utilities. +pub fn get_v3_relay_hash(relay_data: &V3RelayData, chain_id: u64) -> [u8; 32] { + let mut input = relay_data.try_to_vec().unwrap(); + input.extend_from_slice(&chain_id.to_le_bytes()); + // Log the input that will be hashed + msg!("Input to be hashed: {:?}", input); + keccak::hash(&input).0 +} + +pub fn verify_merkle_proof(root: [u8; 32], leaf: [u8; 32], proof: Vec<[u8; 32]>) -> Result<()> { + msg!("Verifying merkle proof"); + let computed_root = process_proof(&proof, &leaf); + if computed_root != root { + msg!("Invalid proof: computed root does not match provided root"); + return Err(CustomError::InvalidProof.into()); + } + msg!("Merkle proof verified successfully"); + Ok(()) +} + +// The following is the rust implementation of the merkle proof verification from OpenZeppelin that can be found here: +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol +pub fn process_proof(proof: &[[u8; 32]], leaf: &[u8; 32]) -> [u8; 32] { + let mut computed_hash = *leaf; + for proof_element in proof.iter() { + computed_hash = commutative_keccak256(&computed_hash, proof_element); + } + computed_hash +} + +// See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/Hashes.sol +fn commutative_keccak256(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] { + if a < b { + efficient_keccak256(a, b) + } else { + efficient_keccak256(b, a) + } +} + +fn efficient_keccak256(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] { + let mut input = [0u8; 64]; + input[..32].copy_from_slice(a); + input[32..].copy_from_slice(b); + keccak::hash(&input).0 +} + +//TODO: we might want to split this utils up into different files. we have a) CCTP b) Merkle proof c) Bitmap sections. At minimum we should have more comments splitting these up. + +pub fn is_claimed(claimed_bitmap: &Vec, index: u32) -> bool { + let byte_index = (index / 8) as usize; // Index of the byte in the array + if byte_index >= claimed_bitmap.len() { + return false; // Out of bounds, treat as not claimed + } + let bit_in_byte_index = (index % 8) as usize; // Index of the bit within the byte + let claimed_byte = claimed_bitmap[byte_index]; + let mask = 1 << bit_in_byte_index; + claimed_byte & mask == mask +} + +pub fn set_claimed(claimed_bitmap: &mut Vec, index: u32) { + let byte_index = (index / 8) as usize; // Index of the byte in the array + if byte_index >= claimed_bitmap.len() { + let new_size = byte_index + 1; + claimed_bitmap.resize(new_size, 0); // Resize the Vec if necessary + } + let bit_in_byte_index = (index % 8) as usize; // Index of the bit within the byte + claimed_bitmap[byte_index] |= 1 << bit_in_byte_index; +} diff --git a/scripts/svm/closeRelayerPdas.ts b/scripts/svm/closeRelayerPdas.ts index 61cb887a..0f8be9f7 100644 --- a/scripts/svm/closeRelayerPdas.ts +++ b/scripts/svm/closeRelayerPdas.ts @@ -11,7 +11,7 @@ import { calculateRelayHashUint8Array } from "../../src/SvmUtils"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; // Use programId from the provider diff --git a/scripts/svm/enableRoute.ts b/scripts/svm/enableRoute.ts index b16082ff..daeb9a8d 100644 --- a/scripts/svm/enableRoute.ts +++ b/scripts/svm/enableRoute.ts @@ -9,7 +9,7 @@ import { hideBin } from "yargs/helpers"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; diff --git a/scripts/svm/initialize.ts b/scripts/svm/initialize.ts index dbbfcec0..78184410 100644 --- a/scripts/svm/initialize.ts +++ b/scripts/svm/initialize.ts @@ -9,7 +9,7 @@ import { evmAddressToPublicKey } from "../../src/SvmUtils"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; diff --git a/scripts/svm/queryDeposits.ts b/scripts/svm/queryDeposits.ts index 01d63b94..774a1426 100644 --- a/scripts/svm/queryDeposits.ts +++ b/scripts/svm/queryDeposits.ts @@ -9,7 +9,7 @@ import { readProgramEvents } from "../../src/SvmUtils"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; diff --git a/scripts/svm/queryFills.ts b/scripts/svm/queryFills.ts index d51bc24a..09a022bd 100644 --- a/scripts/svm/queryFills.ts +++ b/scripts/svm/queryFills.ts @@ -9,7 +9,7 @@ import { readProgramEvents } from "../../src/SvmUtils"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; diff --git a/scripts/svm/queryRoute.ts b/scripts/svm/queryRoute.ts index d5519650..4dabb492 100644 --- a/scripts/svm/queryRoute.ts +++ b/scripts/svm/queryRoute.ts @@ -14,7 +14,7 @@ import { hideBin } from "yargs/helpers"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; diff --git a/scripts/svm/queryState.ts b/scripts/svm/queryState.ts index 6843c43d..ed786ded 100644 --- a/scripts/svm/queryState.ts +++ b/scripts/svm/queryState.ts @@ -8,7 +8,7 @@ import { hideBin } from "yargs/helpers"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; diff --git a/scripts/svm/remotePauseDeposits.ts b/scripts/svm/remotePauseDeposits.ts index abbc76d2..98cc6bcb 100644 --- a/scripts/svm/remotePauseDeposits.ts +++ b/scripts/svm/remotePauseDeposits.ts @@ -11,7 +11,6 @@ import { hideBin } from "yargs/helpers"; import { ethers } from "ethers"; import { MessageTransmitter } from "../../target/types/message_transmitter"; import { decodeMessageHeader, getMessages } from "../../test/svm/cctpHelpers"; -import { AnyCnameRecord } from "dns"; // Set up Solana provider. const provider = anchor.AnchorProvider.env(); @@ -51,13 +50,21 @@ const remoteDomain = 0; // Ethereum const localDomain = 5; // Solana // Get Solana programs and accounts. +<<<<<<< HEAD const svmSpokeIdl = require("../target/idl/svm_spoke.json"); +======= +const svmSpokeIdl = require("../../target/idl/svm_spoke.json"); +>>>>>>> master const svmSpokeProgram = new anchor.Program(svmSpokeIdl, provider); const [statePda, _] = PublicKey.findProgramAddressSync( [Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], svmSpokeProgram.programId ); +<<<<<<< HEAD const messageTransmitterIdl = require("../target/idl/message_transmitter.json"); +======= +const messageTransmitterIdl = require("../../target/idl/message_transmitter.json"); +>>>>>>> master const messageTransmitterProgram = new anchor.Program(messageTransmitterIdl, provider); const [messageTransmitterState] = PublicKey.findProgramAddressSync( [Buffer.from("message_transmitter")], diff --git a/scripts/svm/simpleDeposit.ts b/scripts/svm/simpleDeposit.ts index 5276a736..ace3a42a 100644 --- a/scripts/svm/simpleDeposit.ts +++ b/scripts/svm/simpleDeposit.ts @@ -9,7 +9,7 @@ import { hideBin } from "yargs/helpers"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId; diff --git a/scripts/svm/simpleFill.ts b/scripts/svm/simpleFill.ts index b39c5da9..96b07edc 100644 --- a/scripts/svm/simpleFill.ts +++ b/scripts/svm/simpleFill.ts @@ -10,7 +10,7 @@ import { calculateRelayHashUint8Array } from "../../src/SvmUtils"; // Set up the provider const provider = AnchorProvider.env(); anchor.setProvider(provider); -const idl = require("../target/idl/svm_spoke.json"); +const idl = require("../../target/idl/svm_spoke.json"); const program = new Program(idl, provider); const programId = program.programId;