Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sealevel IGP program #2574

Merged
merged 6 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
110 changes: 110 additions & 0 deletions rust/sealevel/libraries/account-utils/src/discriminator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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 {
// 8 byte discriminator prefix
8 + self.data.size()
tkporter marked this conversation as resolved.
Show resolved Hide resolved
}
}

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 {}
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 = "1.14.13"
tkporter marked this conversation as resolved.
Show resolved Hide resolved
solana-sdk.workspace = true
hyperlane-test-utils ={ path = "../../libraries/test-utils" }

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