Skip to content

Conversation

@javerett
Copy link

No description provided.

function pullGuardianSets() public {
// For each guardian set after the current one
uint32 coreGuardianSetIndex = _coreV1.getCurrentGuardianSetIndex();
for (uint32 i = uint32(_guardianSetExpirationTime.length); i <= coreGuardianSetIndex; ++i) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (uint32 i = uint32(_guardianSetExpirationTime.length); i <= coreGuardianSetIndex; ++i) {
for (uint i = _guardianSetExpirationTime.length; i <= coreGuardianSetIndex; ++i) {

Loop counters should always be uint(256). Using a different datatype only increases gas cost and you'll never overflow the boundary anyway.

Comment on lines 155 to 159
function eagerOr(bool lhs, bool rhs) private pure returns (bool ret) {
assembly ("memory-safe") {
ret := or(lhs, rhs)
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is available in the utils of the Solidity SDK and should be taken from there.

}

// Write the guardian set to the ExtStore and verify the index
uint32 extIndex = uint32(_extWrite(data));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for casts once the loop counter is simply a uint.

result_entry = abi.encodePacked(expirationTime, guardianSetAddrs);
}

result = abi.encodePacked(result, result_entry);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing it only once here, this should be copy pasted to each individual branch and combined with the abi.encodePacked of that branch. solc is sadly not smart enough to do this sort of thing so this will incur an additional, unnecessary, looping memcopy (looping because we likely can't rely on he MEMCOPY opcode to exist everywhere we deploy this contract).

Comment on lines 110 to 112
uint8 version;
(version, offset) = data.asUint8CdUnchecked(offset);
if (version != RAW_DISPATCH_PROTOCOL_VERSION) revert InvalidDispatchVersion(version);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likewise no need for a version.

if (version != RAW_DISPATCH_PROTOCOL_VERSION) revert InvalidDispatchVersion(version);

bytes memory result;
uint length = data.length;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

Comment on lines 79 to 83
uint32 dataLength;
(dataLength, offset) = data.asUint32CdUnchecked(offset);

bytes calldata encodedVaa = data[offset:offset + dataLength];
VaaBody memory vaaBody = decodeAndVerifyVaa(encodedVaa);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BytesParsing.sliceUint16PrefixedCdUnchecked

using VaaLib for bytes;
using UncheckedIndexing for address[];

ICoreBridge private _coreV1;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ICoreBridge private _coreV1;
ICoreBridge private immutable _coreBridge;

V1 suggests that there is a v2 when really there is only a v2 for verification.

uint32[] private _guardianSetExpirationTime;

// Get the guardian addresses for a given guardian set index using the ExtStore
function getGuardianSetInfo(uint32 index) public view returns (uint32 expirationTime, address[] memory guardianAddrs) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properly functioning code should never create a Panic, not even on invalid external input.
from the Solidity documentation

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File should use spaces, not tabs (and my preference is 2 space indentation, but whatever) and a max line length of 100 (except for urls that can't be line-broken)

uint upper = (limit == 0 || currentGuardianSetLength - oldGuardianSetLength < limit) ? currentGuardianSetLength : oldGuardianSetLength + limit;

// Pull and append the guardian sets
for (uint i = oldGuardianSetLength; i < upper; i++) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (uint i = oldGuardianSetLength; i < upper; i++) {
for (uint i = oldGuardianSetLength; i < upper; ++i) {

nit for consistency

// address (160 bits)
uint256[] private _pastThresholdInfo;

bytes32[][] private _shards;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This connection is currently communicated

Oh... this should have said "is currently NOT communicated" ...

Comment on lines 99 to 107
(
,
,
,
,
,
,
bytes calldata payload
) = _decodeAndVerifyVaa(encodedVaa);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤨

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could go on one line if you like? I found this a bit easier to read, but it's still not pretty haha

bytes calldata encodedVaa;
(encodedVaa, offset) = data.sliceUint16PrefixedCdUnchecked(offset);

// Decode the VAA
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Decode the VAA
// Decode and verify the VAA

and arguably the comments aren't really adding anything in this function

uint8 shardsLength;
(shardsLength, offset) = payload.asUint8MemUnchecked(offset);
shards = new bytes32[](shardsLength);
for (uint i = 0; i < shardsLength; i++) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (uint i = 0; i < shardsLength; i++) {
for (uint i = 0; i < shardsLength; ++i) {

Comment on lines 119 to 120
if (module != MODULE_VERIFICATION_V2) revert InvalidModule(module);
if (action != ACTION_APPEND_THRESHOLD_KEY) revert InvalidAction(action);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 checks should be done in the decode function imho. exec should only be doing parsing and dispatching work, imho.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors can be standalone (don't have to be declared inside a contract) and I'm not sure if you can't just declare them twice (presumably you moved it here because you can't?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it complains about redefinition

pub struct VAA {
// Header
pub version: u8,
pub guardian_set_index: u32,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be tss_index or something similar

Comment on lines 28 to 29
pub fn get_seed_bytes(encoded_vaa: &[u8]) -> &[u8] {
// Extract the index from the encoded VAA
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn get_seed_bytes(encoded_vaa: &[u8]) -> &[u8] {
// Extract the index from the encoded VAA
pub fn get_tss_index(encoded_vaa: &[u8]) -> &[u8] {

#[derive(InitSpace)]
pub struct ThresholdKey {
pub bump: u8,
pub index: u32, // TODO: Is this needed?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, keep this and drop the bump. One can then verify that the account is right by just checking that the owner is the oracle, that the discriminator matches (both done automatically by anchor) and the tss_index can be used to verify that it's the right address too.

Ok(())
}

pub fn verify_vaa(ctx: Context<VerifyVaa>, raw_vaa: Vec<u8>) -> Result<VAA> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can have this as an instruction, but the functionality itself should come from a library. There's no reason to force users to do a precious CPI for something that they can just as easily just verify themselves.

let mut payload = Vec::new();
cursor.read_to_end(&mut payload)?;

let double_hash = hash(&hash(&data[body_start..]).to_bytes()).to_bytes();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced you should be returning any hash here, and if you do, it should definitely be hashed only once. Double hashing was a mistake in the old EVM core bridge, but everywhere else, the hash of a VAA is always considered the single hash (e.g. Solana core bridge impl).

This is also documented in the TS-SDK, the Solidity SDK, and the docs.

Comment on lines 163 to 176
if vaa.version != 2 {
return Err(VAAError::InvalidVersion.into());
}

// Check if the threshold key index matches the VAA index
let threshold_key = &mut ctx.accounts.threshold_key;
if threshold_key.index != vaa.guardian_set_index {
return Err(VAAError::InvalidIndex.into());
}

// Check if the threshold key has expired
if threshold_key.expiration != 0 && threshold_key.expiration < Clock::get().unwrap().unix_timestamp as u64 {
return Err(VAAError::GuardianSetExpired.into());
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move checks to accounts struct if possible.

Comment on lines 113 to 119
if vaa.meta.emitter_chain != CHAIN_ID_SOLANA {
return Err(AppendThresholdKeyError::InvalidGovernanceChainId.into());
}

if vaa.meta.emitter_address != GOVERNANCE_ADDRESS {
return Err(AppendThresholdKeyError::InvalidGovernanceAddress.into());
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move checks to accounts struct.

const GOVERNANCE_ADDRESS: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4];

#[derive(Accounts)]
#[instruction(new_index: u32, vaa_hash: [u8; 32])]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the purpose of the vaa_hash value here? The derivation doesn't guarantee anything, does it? Seems superfluous.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, again, you should be able to take inspiration from TBRv3. It defines a type PostedRelayerMessage which it uses like so.

So you in turn can just use the analogous type from the core bridge for your governance message.

const GOVERNANCE_ADDRESS: [u8; 32] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4];

#[derive(Accounts)]
#[instruction(new_index: u32, vaa_hash: [u8; 32])]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, there should be no need to pass in the new_index either. If the optional account does not exist, the index must be 0, otherwise you can take it from the account and add +1.

Comment on lines 130 to 142
if new_index == 0 {
if ctx.accounts.old_threshold_key.is_some() {
return Err(AppendThresholdKeyError::InvalidOldThresholdKey.into());
}
} else {
if let Some(old_threshold_key) = &ctx.accounts.old_threshold_key {
if old_threshold_key.index != new_index - 1 {
return Err(AppendThresholdKeyError::InvalidOldThresholdKey.into());
}
} else {
return Err(AppendThresholdKeyError::InvalidOldThresholdKey.into());
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eliminate as mentioned above.

JoshuaAverett and others added 29 commits May 12, 2025 15:27
This makes the semantics of the append threshold key VAA identical to
the Solana implementation.
@scnale scnale deleted the combined-contract branch June 5, 2025 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants