Skip to content

Commit

Permalink
Sealevel IGP program (#2574)
Browse files Browse the repository at this point in the history
### Description

* Sealevel IGP program
* Includes tests
* Minor changes to some other Sealevel program tests to use some common
test utils

### Drive-by changes

n/a

### Related issues

#2217 

### Backward compatibility

Yes

### Testing

Unit / functional
  • Loading branch information
tkporter authored Jul 31, 2023
1 parent 07d3acc commit 21accca
Show file tree
Hide file tree
Showing 16 changed files with 3,003 additions and 113 deletions.
34 changes: 21 additions & 13 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ members = [
"sealevel/programs/hyperlane-sealevel-token",
"sealevel/programs/hyperlane-sealevel-token-collateral",
"sealevel/programs/hyperlane-sealevel-token-native",
"sealevel/programs/interchain-gas-paymaster",
"sealevel/programs/ism/multisig-ism-message-id",
"sealevel/programs/ism/test-ism",
"sealevel/programs/mailbox",
Expand Down
138 changes: 138 additions & 0 deletions rust/sealevel/libraries/account-utils/src/discriminator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::program_error::ProgramError;
use spl_type_length_value::discriminator::Discriminator;
use std::ops::Deref;

use crate::SizedData;

pub const PROGRAM_INSTRUCTION_DISCRIMINATOR: [u8; Discriminator::LENGTH] = [1, 1, 1, 1, 1, 1, 1, 1];

/// A wrapper type that prefixes data with a discriminator when Borsh (de)serialized.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct DiscriminatorPrefixed<T> {
pub data: T,
}

impl<T> DiscriminatorPrefixed<T> {
pub fn new(data: T) -> Self {
Self { data }
}
}

impl<T> BorshSerialize for DiscriminatorPrefixed<T>
where
T: DiscriminatorData + borsh::BorshSerialize,
{
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
PROGRAM_INSTRUCTION_DISCRIMINATOR.serialize(writer)?;
self.data.serialize(writer)
}
}

impl<T> BorshDeserialize for DiscriminatorPrefixed<T>
where
T: DiscriminatorData + borsh::BorshDeserialize,
{
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
let (discriminator, rest) = buf.split_at(Discriminator::LENGTH);
if discriminator != PROGRAM_INSTRUCTION_DISCRIMINATOR {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid discriminator",
));
}
Ok(Self {
data: T::deserialize(&mut rest.to_vec().as_slice())?,
})
}
}

impl<T> SizedData for DiscriminatorPrefixed<T>
where
T: SizedData,
{
fn size(&self) -> usize {
// Discriminator prefix + data
Discriminator::LENGTH + self.data.size()
}
}

impl<T> Deref for DiscriminatorPrefixed<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.data
}
}

impl<T> From<T> for DiscriminatorPrefixed<T> {
fn from(data: T) -> Self {
Self::new(data)
}
}

pub trait DiscriminatorData: Sized {
const DISCRIMINATOR_LENGTH: usize = Discriminator::LENGTH;

const DISCRIMINATOR: [u8; Discriminator::LENGTH];
const DISCRIMINATOR_SLICE: &'static [u8] = &Self::DISCRIMINATOR;
}

/// Encodes the given data with a discriminator prefix.
pub trait DiscriminatorEncode: DiscriminatorData + borsh::BorshSerialize {
fn encode(self) -> Result<Vec<u8>, ProgramError> {
let mut buf = vec![];
buf.extend_from_slice(Self::DISCRIMINATOR_SLICE);
buf.extend_from_slice(
&self
.try_to_vec()
.map_err(|err| ProgramError::BorshIoError(err.to_string()))?[..],
);
Ok(buf)
}
}

// Auto-implement
impl<T> DiscriminatorEncode for T where T: DiscriminatorData + borsh::BorshSerialize {}

/// Decodes the given data with a discriminator prefix.
pub trait DiscriminatorDecode: DiscriminatorData + borsh::BorshDeserialize {
fn decode(data: &[u8]) -> Result<Self, ProgramError> {
let (discriminator, rest) = data.split_at(Discriminator::LENGTH);
if discriminator != Self::DISCRIMINATOR_SLICE {
return Err(ProgramError::InvalidInstructionData);
}
Self::try_from_slice(rest).map_err(|_| ProgramError::InvalidInstructionData)
}
}

// Auto-implement
impl<T> DiscriminatorDecode for T where T: DiscriminatorData + borsh::BorshDeserialize {}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_discriminator_prefixed_size() {
#[derive(BorshSerialize, BorshDeserialize)]
struct Foo {
a: u64,
}

impl DiscriminatorData for Foo {
const DISCRIMINATOR: [u8; 8] = [2, 2, 2, 2, 2, 2, 2, 2];
}

impl SizedData for Foo {
fn size(&self) -> usize {
8
}
}

let prefixed_foo = DiscriminatorPrefixed::new(Foo { a: 1 });
let serialized_prefixed_foo = prefixed_foo.try_to_vec().unwrap();

assert_eq!(serialized_prefixed_foo.len(), prefixed_foo.size());
}
}
90 changes: 49 additions & 41 deletions rust/sealevel/libraries/account-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use solana_program::{
rent::Rent,
system_instruction, system_program,
};
use spl_type_length_value::discriminator::Discriminator;

pub mod discriminator;
pub use discriminator::*;

/// Data that has a predictable size when serialized.
pub trait SizedData {
Expand All @@ -33,14 +35,20 @@ pub struct AccountData<T> {
data: Box<T>,
}

impl<T> From<T> for AccountData<T> {
fn from(data: T) -> Self {
impl<T> AccountData<T> {
pub fn new(data: T) -> Self {
Self {
data: Box::new(data),
}
}
}

impl<T> From<T> for AccountData<T> {
fn from(data: T) -> Self {
Self::new(data)
}
}

impl<T> From<Box<T>> for AccountData<T> {
fn from(data: Box<T>) -> Self {
Self { data }
Expand Down Expand Up @@ -140,6 +148,44 @@ where
}
}

impl<T> AccountData<T>
where
T: Data + SizedData,
{
/// Stores the account data in the given account, reallocing the account
/// if necessary, and ensuring it is rent exempt.
pub fn store_with_rent_exempt_realloc<'a, 'b>(
&self,
account_info: &'a AccountInfo<'b>,
rent: &Rent,
payer_info: &'a AccountInfo<'b>,
) -> Result<(), ProgramError> {
let required_size = self.size();

let account_data_len = account_info.data_len();
let required_account_data_len = required_size.max(account_data_len);

let required_rent = rent.minimum_balance(required_account_data_len);
let lamports = account_info.lamports();
if lamports < required_rent {
invoke(
&system_instruction::transfer(
payer_info.key,
account_info.key,
required_rent - lamports,
),
&[payer_info.clone(), account_info.clone()],
)?;
}

if account_data_len < required_account_data_len {
account_info.realloc(required_account_data_len, false)?;
}

self.store(account_info, false)
}
}

/// Creates associated token account using Program Derived Address for the given seeds.
/// Required to allow PDAs to be created even if they already have a lamport balance.
///
Expand Down Expand Up @@ -216,41 +262,3 @@ pub fn verify_account_uninitialized(account: &AccountInfo) -> Result<(), Program
}
Err(ProgramError::AccountAlreadyInitialized)
}

pub const PROGRAM_INSTRUCTION_DISCRIMINATOR: [u8; Discriminator::LENGTH] = [1, 1, 1, 1, 1, 1, 1, 1];

pub trait DiscriminatorData: Sized {
const DISCRIMINATOR_LENGTH: usize = Discriminator::LENGTH;

const DISCRIMINATOR: [u8; Discriminator::LENGTH];
const DISCRIMINATOR_SLICE: &'static [u8] = &Self::DISCRIMINATOR;
}

pub trait DiscriminatorEncode: DiscriminatorData + borsh::BorshSerialize {
fn encode(self) -> Result<Vec<u8>, ProgramError> {
let mut buf = vec![];
buf.extend_from_slice(Self::DISCRIMINATOR_SLICE);
buf.extend_from_slice(
&self
.try_to_vec()
.map_err(|err| ProgramError::BorshIoError(err.to_string()))?[..],
);
Ok(buf)
}
}

// Auto-implement
impl<T> DiscriminatorEncode for T where T: DiscriminatorData + borsh::BorshSerialize {}

pub trait DiscriminatorDecode: DiscriminatorData + borsh::BorshDeserialize {
fn decode(data: &[u8]) -> Result<Self, ProgramError> {
let (discriminator, rest) = data.split_at(Discriminator::LENGTH);
if discriminator != Self::DISCRIMINATOR_SLICE {
return Err(ProgramError::InvalidInstructionData);
}
Self::try_from_slice(rest).map_err(|_| ProgramError::InvalidInstructionData)
}
}

// Auto-implement
impl<T> DiscriminatorDecode for T where T: DiscriminatorData + borsh::BorshDeserialize {}
30 changes: 30 additions & 0 deletions rust/sealevel/programs/interchain-gas-paymaster/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
cargo-features = ["workspace-inheritance"]

[package]
name = "hyperlane-sealevel-igp"
version = "0.1.0"
edition = "2021"

[features]
no-entrypoint = []
no-spl-noop = []

[dependencies]
hyperlane-core = { path = "../../../hyperlane-core" }
access-control = { path = "../../libraries/access-control" }
account-utils = { path = "../../libraries/account-utils" }
serializable-account-meta = { path = "../../libraries/serializable-account-meta" }
borsh.workspace = true
solana-program.workspace = true
num-derive.workspace = true
num-traits.workspace = true
thiserror.workspace = true
getrandom.workspace = true

[dev-dependencies]
solana-program-test.workspace = true
solana-sdk.workspace = true
hyperlane-test-utils ={ path = "../../libraries/test-utils" }

[lib]
crate-type = ["cdylib", "lib"]
Loading

0 comments on commit 21accca

Please sign in to comment.