-
Notifications
You must be signed in to change notification settings - Fork 30
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
Added Optimized Spl-token program #31
Conversation
This is great! I left a few comments, but overall looks very good. The main one is about UB when you get a pointer from a Also, could you please run |
Writing here a new comment addressing some of the changes:
Not sure if you were referring to something else but happy to change it if that's the case!
If this is satisfying I'll change the instructions and run additional tests |
I think what is tricky of UB is that it might work, but "break" in a different setting (arch or compiler version). From the docs, when a raw pointer is dereferenced (using the A workaround would be to define the pub struct Mint {
pub mint_authority_present: [u8; 4],
pub mint_authority: Pubkey,
pub supply: [u8; 8],
pub decimals: u8,
pub is_initialized: bool,
pub freeze_authority_present: [u8; 4],
pub freeze_authority: Pubkey,
}
impl Mint {
pub fn supply(&self) -> u64 {
u64::from_le_bytes(self.supply)
}
} You can then cast the raw pointer into this type to access its fields. The "penalty" from the
Great! 👍
I think this is still UB: https://doc.rust-lang.org/beta/std/mem/union.MaybeUninit.html#method.assume_init_mut
I think the compiler will do a good job in optimizing this even with a "normal" zeroed static array since the array gets populated immediately after. |
Update on the final PR: - General nit fixes: - Alignment Issues -
|
Amazing! 🙌
Great!
I think we might have an issue here with the fact that you are taking a raw pointer and the borrow going out of scope. I will have a closer look at the code to confirm this.
Read my mind. 😀 I think we can move it to an "utility" crate (e.g., I will review the PR shortly - thanks for addressing the comments! |
programs/token/src/state/mint.rs
Outdated
#[inline(always)] | ||
pub fn from_account_info_unchecked(account_info: &AccountInfo) -> Result<Self, ProgramError> { | ||
let data = account_info.try_borrow_data()?; | ||
Ok(Self(data.as_ptr())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, this does not "keep" the borrow. The Ref
returned by try_borrow_data()
will drop once the method returns, but the type will keep the raw pointer. So it will look like the data is not borrowed while there is still a "live" raw pointer. Perhaps the solution is to hold the Ref<[u8]>
instead of *const u8
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, maybe all we need is to map
the Ref<[u8]>
to a Ref<Mint>
:
Ref::map(account_info.try_borrow_data()?, |data| Self(data.as_ptr()))
The return type of the method then becomes Result<Ref<Mint>, ProgramError>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, maybe all we need is to
map
theRef<[u8]>
to aRef<Mint>
:Ref::map(account_info.try_borrow_data()?, |data| Self(data.as_ptr()))The return type of the method then becomes
Result<Ref<Mint>, ProgramError>
.
I think it's better too, more convenient for the user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this is still correct, but I needed to add an unsafe cast because the compiler was yelling at me so I went with this for the code solution at the end:
pub fn from_account_info(account_info: &AccountInfo) -> Result<Ref<Mint>, ProgramError> {
if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData) }
if account_info.owner() != &ID { return Err(ProgramError::InvalidAccountData) }
Ok(Ref::map(account_info.try_borrow_data()?, |data| {
unsafe { &*(data.as_ptr() as *const Mint) }
}))
}
programs/token/src/state/token.rs
Outdated
#[inline(always)] | ||
pub fn from_account_info_unchecked(account_info: &AccountInfo) -> Result<Self, ProgramError> { | ||
let data = account_info.try_borrow_data()?; | ||
Ok(Self(data.as_ptr())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here about the drop of the Ref<[u8]>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is super close, just a couple of things.
@L0STE I had a second look at the Wonder if the following makes sense:
|
I think this totally make sense, this is my interpretation of this, let me know If I'm close: /// Performs owner and length validation on `AccountInfo` and returns a `Ref<T>` for safe borrowing.
pub fn from_account_info(account_info: &AccountInfo) -> Result<Ref<Mint>, ProgramError> {
if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData) }
if account_info.owner() != &ID { return Err(ProgramError::InvalidAccountData) }
Ok(Ref::map(account_info.try_borrow_data()?, |data| {
unsafe { &*(data.as_ptr() as *const Mint) }
}))
}
/// # Safety
/// Performs owner and length validation on `AccountInfo` but performs unchecked borrowing and
/// returns a `T` directly.
#[inline(always)]
pub unsafe fn from_account_info_unchecked(account_info: &AccountInfo) -> Result<Mint, ProgramError> {
if account_info.data_len() != Self::LEN { return Err(ProgramError::InvalidAccountData) }
if account_info.owner() != &ID { return Err(ProgramError::InvalidAccountData) }
Ok(Self::from_bytes(account_info.borrow_data_unchecked().as_ref()))
}
/// # Safety
/// Constructs a `T` directly from a byte slice. The caller must ensure that `bytes` contains a
/// valid representation of `T`.
pub unsafe fn from_bytes(bytes: &[u8]) -> Self {
core::ptr::read(bytes.as_ptr() as *const Mint)
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
let instruction = Instruction { | ||
program_id: &crate::ID, | ||
accounts: &account_metas, | ||
data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just slice here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is an array of [MaybeUninit<u8>]
so we need to cast it to [u8]
.
|
||
#[inline(always)] | ||
fn write_bytes(destination: &mut [MaybeUninit<u8>], source: &[u8]) { | ||
for (d, s) in destination.iter_mut().zip(source.iter()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you check that this actually desugars to memcpy and not to a bunch of assignments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we probably can replace this by a memcpy
. It is using an iteration to call write
since each position is a MaybeUninit<u8>
but since we know the size in advance, we might be able to use ptr::copy_nonoverlapping
instead.
Added most of the instruction from spl-token program using unsafe rust for optimization w/ @deanmlittle
Some example on how the instruction are constructed:
To test it out and as an example on how to integrate, I created a repo that have all the CPI to this program and all the tests that actually look if the data is saved correctly at this link: https://github.com/L0STE/pinocchio-spl-examples