From 2df20cceaf3584bf8e8f48e6ea1fb58faebf3e81 Mon Sep 17 00:00:00 2001 From: ceun Date: Fri, 20 May 2022 15:45:06 +0000 Subject: [PATCH] added solana readmd --- solana/README.md | 45 +++++++++++++++++++++++++++ solana/src/api/complete_transfer.rs | 47 ++++++++++++++++++++++++----- solana/test/index.js | 4 ++- 3 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 solana/README.md diff --git a/solana/README.md b/solana/README.md new file mode 100644 index 0000000..b5166e3 --- /dev/null +++ b/solana/README.md @@ -0,0 +1,45 @@ +Scenario: User from Chain A wants to swap Token A for Token B on Chain B. + +Step 1: User transfers Token A to Native Swap through the token bridge +Step 2: Native Swap redeems the transfer from the token bridge and takes custody of the wrapped-Token A +Step 3a: for "NoSwap", Native Swap sends wrapped-Token A to recipient wallet on Chain B. +Step 3b: for "WithSwap", Native Swap swaps wrapped-Token A for Token B and sends Token B to recipient wallet on Chain B + +Instruction::CompleteTransfer +Description: redeems wrapped tokens from the token bridge ATA, transfers them to the NativeSwap ATA. +Extremely similar to CompleteWrappedWithPayload struct in the token bridge sdk except, instead of the user redeeming the transfer, NativeSwap redeems it. + +Instruction::CompleteNoSwap +Description: transfers wrapped tokens from NativeSwap ATA to user's ATA + +### Running +Set you id.json to your private key +``` +~/.config/solana/id.json +``` +Build the cargo +``` +EMITTER_ADDRESS=EMITTER_ADDRESS BRIDGE_ADDRESS=BRIDGE_ADDRESS TOKEN_BRIDGE_ADDRESS=TOKEN_BRIDGE_ADDRESS cargo build-bpf +``` +Write the program byte code into a buffer address +``` +solana program write-buffer target/deploy/wormhole_nativeswap.so -u d +``` +Take the buffer address & Deploy the contract +``` +solana program deploy --program-id PROGRAM_ID --buffer BUFFER -u d +>in testnet (solana devnet), PROGRAM_ID=92XVWWdN47dL38HLZ277rdRJh7RUG2ikmiBRoUGrKXif +``` +Compile the wasm bindings +``` +EMITTER_ADDRESS="11111111111111111111111111111115" BRIDGE_ADDRESS="3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5" TOKEN_BRIDGE_ADDRESS="DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe" wasm-pack build --target nodejs -d nodejs -- --features wasm +``` +Now the wasm node package is ready to use. +``` +cd test +MNEMONIC=PRIVATE_KEY node index.js +``` +transfer_ix: the transfer instruction to redeem the tokens from the token bridge and transfer them to NativeSwap. you have to submit a tokenTransfer VAA where the recipient is the custody address + +no_swap_ix: the transfer instruction to move the tokens from NativeSwap to the recipients wallet. + diff --git a/solana/src/api/complete_transfer.rs b/solana/src/api/complete_transfer.rs index c447dd0..ef29139 100644 --- a/solana/src/api/complete_transfer.rs +++ b/solana/src/api/complete_transfer.rs @@ -32,6 +32,9 @@ use solitaire::{ *, }; +// have to create custom config account for the token bridge b/c the default ConfigAccount in solana/modules/token_bridge/program/src/accounts.rs +// is a derived account and assumes the program id is the ExecutionContext, +// which is normally the token bridge... but in this case would be NativeSwap. pub type TokenBridgeConfigAccount<'b, const STATE: AccountState> = Data<'b, Config, { STATE }>; pub struct TokenBridgeConfigAccountDerivationData { @@ -49,7 +52,8 @@ impl<'b, const STATE: AccountState> Seeded<&TokenBridgeConfigAccountDerivationDa } } -pub type TokenBridgeMintSigner<'b> = Info<'b>; +//Info is short alias for AccountInfo from solana programs +pub type TokenBridgeMintSigner<'b> = Info<'b>; pub struct TokenBridgeMintSignerDerivationData { pub token_bridge: Pubkey, @@ -66,6 +70,27 @@ impl<'b> Seeded<&TokenBridgeMintSignerDerivationData> } } +/* +#[derive(FromAccounts)] +pub struct CompleteWrappedWithPayload<'b> { + pub payer: Mut>>, + pub config: ConfigAccount<'b, { AccountState::Initialized }>, + + // Signed message for the transfer + pub vaa: ClaimableVAA<'b, PayloadTransferWithPayload>, + + pub chain_registration: Endpoint<'b, { AccountState::Initialized }>, + + pub to: Mut>, + pub to_owner: MaybeMut>>, + pub to_fees: Mut>, + pub mint: Mut>, + pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>, + + pub mint_authority: MintSigner<'b>, +} +*/ + #[derive(FromAccounts)] pub struct CompleteTransfer<'b> { pub payer: Mut>>, @@ -76,15 +101,16 @@ pub struct CompleteTransfer<'b> { // Above includes claim account pub chain_registration: Endpoint<'b, { AccountState::Initialized }>, - + // custody == Native Swap ATA pub custody: Mut>, + // custody signer == PDA of Native Swap Program ID pub custody_signer: CustodySigner<'b>, pub to_fees: Mut>, pub mint: Mut>, pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>, pub mint_authority: TokenBridgeMintSigner<'b>, - pub token_bridge: Info<'b>, + pub token_bridge: Info<'b>, //<-- added for derived config, endpoint, & wrapped metadata } impl<'a> From<&CompleteTransfer<'a>> for EndpointDerivationData { @@ -105,6 +131,8 @@ impl<'a> From<&CompleteTransfer<'a>> for WrappedDerivationData { } } +// have to define implementations for the derived data similar to CompleteWrappedWithPayload + impl<'a> From<&CompleteTransfer<'a>> for CustodyAccountDerivationData { fn from(accs: &CompleteTransfer<'a>) -> Self { CustodyAccountDerivationData { @@ -125,15 +153,19 @@ pub fn complete_transfer( _data: CompleteTransferData, ) -> Result<()> { + msg!("program id: {:?}", ctx.program_id); + msg!("accounts: {:?}", ctx.accounts); + // core bridge let bridge_id = ctx.accounts[14].info().key; + //where the payload/postedVaa is being stored let message_key = ctx.accounts[2].info().key; - msg!("bridge_id: {:?}", bridge_id); - msg!("message_key: {:?}", message_key); - // Verify that the custody account is derived correctly + + // Verify that the custody account is a ATA of custody owner aka native swap let derivation_data: CustodyAccountDerivationData = (&*accs).into(); accs.custody .verify_derivation(ctx.program_id, &derivation_data)?; + // if the ATA of the wrapped token belonging to NativeSwap is not created, create it if !accs.custody.is_initialized() { accs.custody .create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?; @@ -149,7 +181,8 @@ pub fn complete_transfer( // see https://github.com/certusone/wormhole/blob/2e24f11fa045ac8460347d9796a4ecdb7931a154/solana/modules/token_bridge/program/src/instructions.rs#L312-L338 // TODO: maybe there's a better way to rebuild this off our list of accounts which should be nearly compatible - + + // transfer the wrapped token from the token bridge ATA to the NativeSwap ATA let transfer_ix = Instruction { program_id: *accs.token_bridge.info().key, accounts: vec![ diff --git a/solana/test/index.js b/solana/test/index.js index 8a432eb..8d4d40e 100644 --- a/solana/test/index.js +++ b/solana/test/index.js @@ -1,3 +1,5 @@ +const { uint8ArrayToNative } = require("@certusone/wormhole-sdk"); + (async () => { const PAYLOAD_1_VAA = "01000000000100339c0d030b927eda9cb7ee53d266cbdc6d8f2a70a2a8031952a3a19ee3963d77030dfa8d70c134ef577f9db119cd606bf82ad593f6bb5addfc57f33e741e7e6201624b367c636d0000000b000000000000000000000000d11de1f930ea1f7dd0290fe3a2e35b9c91aefb37000000000000000c010100000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000337610d27c682e347c9cd60bd4b3b107c9d34ddd000400000000000000000000000012345756e90eba0c357d6ea5d537a179f9d6d0b000040000000000000000000000000000000000000000000000000000000000000000"; @@ -53,6 +55,7 @@ sdk.hexToUint8Array(PAYLOAD_3_VAA_TO_SOLANA_WITH_CUSTODY_SIGNER2) ); console.log(transfer_ix_json); + console.log("Program Id:", sdk.uint8ArrayToNative(transfer_ix_json.program_id, 1)) console.log( transfer_ix_json.accounts.map(({ pubkey, is_signer, is_writable }) => [ sdk.hexToNativeString( @@ -84,7 +87,6 @@ ]) ); - const transfer_ix = sdk.ixFromRust(transfer_ix_json); const no_swap_ix = sdk.ixFromRust(no_swap_ix_json);