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

Transfer hook offchain and onchain helpers bugfix #6065

Closed
wants to merge 7 commits into from

Conversation

buffalojoec
Copy link
Contributor

@buffalojoec buffalojoec commented Jan 5, 2024

This PR introduces some modifications to Token2022's off-chain and on-chain helpers that simply convert a transfer instruction into an Execute instruction before resolving the extra account metas.

Since anyone storing extra account meta configs should be considering the list of accounts as it will arrive in their Transfer Hook program's Execute instruction, it makes sense to resolve for Execute.

After all, the main problem we have here is that we're asking TLV Account Resolution to resolve the extra account metas for an Execute instruction, but we're not actually feeding it a valid Execute instruction. Instead, we're feeding it a transfer instruction, typically TransferChecked.

I've also added some tests!

Closes #6064


A Note on Token2022 Release

A release is necessary to publish this bugfix. However, since this change is specific to Token2022's off-chain and on-chain helpers, the on-chain program does not need to be modified. Developers will simply need to use the newer version of the crate, as well as the newer versions of the Token Client and CLI if they'd like to use the repaired helpers.

We can communicate the bug in question by describing the exact use cases one may encounter issues:

  • Defining extra account metas that have seed configurations pointing to instruction data by index
  • Defining extra account metas that have seed configurations pointing to other extra account metas by index (not instruction accounts; these will work just fine)

This means the following extra account meta configurations will still work as expected:

  • Defining extra account metas that have fixed addresses
  • Defining extra account metas that have seed configurations with literal bytes

Also, while we're at it, I think we need to publish some extremely detailed documentation on how to use Transfer Hook extra metas.

@buffalojoec buffalojoec marked this pull request as draft January 6, 2024 00:27
@oasisMystre

This comment was marked as off-topic.

@buffalojoec buffalojoec changed the title Transfer hook offchain bugfix Transfer hook offchain and onchain helpers bugfix Jan 8, 2024
@buffalojoec
Copy link
Contributor Author

Hey @joncinque I'm going to break this up into 3 PRs for easier review, but I'd like for you to have a look at the linked issue and the general proposed solution, as well as my notes on release.

@joncinque
Copy link
Contributor

Great catch! The issue is definitely valid, it's definitely best to split this up, the release plan makes sense, and we definitely need more docs 😅

It makes me realize that seed configurations won't work in all cases. For example, if you're writing a swap program, and a token derives a PDA based on instruction data, then there's no way to get the right address, since the amount will be variable based on the swap calculation.

I'll make some comments on the interface

Comment on lines 46 to 51
pub async fn resolve_extra_transfer_account_metas<F, Fut>(
instruction: &mut Instruction,
fetch_account_data_fn: F,
mint_address: &Pubkey,
amount: u64,
) -> Result<(), AccountFetchError>
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is specific to transfers, let's deprecate this function, and create a new one which requires passing in all the transfer parameters, and that way we don't have to assume that a proper transfer instruction is provided.

Comment on lines +36 to +42
pub fn resolve_extra_transfer_account_metas_for_cpi<'a>(
cpi_instruction: &mut Instruction,
cpi_account_infos: &mut Vec<AccountInfo<'a>>,
mint_info: &AccountInfo<'a>,
additional_accounts: &[AccountInfo<'a>],
amount: u64,
) -> Result<(), ProgramError> {
Copy link
Contributor

@joncinque joncinque Jan 8, 2024

Choose a reason for hiding this comment

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

Same here, let's pass the transfer parameters by name, similar to invoke_transfer_checked, rather than implicitly in the instruction

Comment on lines 703 to +704
offchain::resolve_extra_transfer_account_metas(
&mut instruction,
&mut transfer_1_instruction,
Copy link
Contributor

Choose a reason for hiding this comment

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

By updating the params on this function, this test should look much clearer!

@buffalojoec
Copy link
Contributor Author

It makes me realize that seed configurations won't work in all cases. For example, if you're writing a swap program, and a token derives a PDA based on instruction data, then there's no way to get the right address, since the amount will be variable based on the swap calculation.

Actually I'm glad you brought this up. I have been mulling on an idea for a more dynamic approach to some account keys, but I haven't thought through all of the implementation yet.

The use case I had in mind was "what if I want to require an extra account meta that's not a PDA but can be different every time someone invokes my program?". For example, let's say I require some program ID to CPI to, and maybe that program just has to implement some interface. Right now you'd have to make that a static fixed address, but it could be set to [0u8; 32] and that just means "wildcard". That way it can be whatever address!

Note if we did add more seed support we still wouldn't have to change Token2022's on-chain program. 🙏🏼

As far as the instruction data issue you presented, I think that's probably okay and not our concern at the moment. Technically no matter what program you're writing, you have to implement the Execute instruction. That means your instruction data is always going to be <discriminator (8)> + <amount (8)>. If you extend inputs through account state, you're covered with Seed::AccountData and if you are doing calculations within the program, that's a really far-out edge case in my opinion that's hard to use as a seed even natively.

Curious your thoughts on either of these points.

@buffalojoec
Copy link
Contributor Author

FYI I've added the comprehensive docs to my list, and I'll produce something next week.

@joncinque
Copy link
Contributor

That way it can be whatever address!

Seems like that should be a different ExtraAccountMeta type entirely. It should be fine to implement that as a non-breaking change and use the newer library in token-2022 for the next deployment if you really want to support it. Has anyone asked for it? I'm not even sure how the resolver would work for that. Given a huge number of accounts, how does it pick which one is the "bonus" one?

if you are doing calculations within the program, that's a really far-out edge case in my opinion that's hard to use as a seed even natively

We need to always consider supporting DeFi or trading apps, so doing calculations shouldn't be considered an edge case. We'll just have to hope that people won't use instruction data for deriving their transfer hook accounts 😬

@buffalojoec
Copy link
Contributor Author

That way it can be whatever address!

Seems like that should be a different ExtraAccountMeta type entirely. It should be fine to implement that as a non-breaking change and use the newer library in token-2022 for the next deployment if you really want to support it. Has anyone asked for it? I'm not even sure how the resolver would work for that. Given a huge number of accounts, how does it pick which one is the "bonus" one?

Agreed, it would be another ExtraAccountMeta type. And yeah, this was where I hit the snag, lol.

if you are doing calculations within the program, that's a really far-out edge case in my opinion that's hard to use as a seed even natively

We need to always consider supporting DeFi or trading apps, so doing calculations shouldn't be considered an edge case. We'll just have to hope that people won't use instruction data for deriving their transfer hook accounts 😬

But isn't the instruction data always Execute, ie <discriminator (8)> + <amount (8)>? That's the Transfer Hook interface. You couldn't use another instruction.

@joncinque
Copy link
Contributor

But isn't the instruction data always Execute, ie <discriminator (8)> + <amount (8)>? That's the Transfer Hook interface. You couldn't use another instruction.

Correct. I was focusing on the amount part, which will be impossible to figure out off-chain for trading applications.

@buffalojoec
Copy link
Contributor Author

Closing in favor of the segmented PRs going in for this.

@buffalojoec buffalojoec closed this Jan 9, 2024
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.

Transfer Hook: Off-chain and on-chain helpers are resolving keys incorrectly
3 participants