Przeglądaj źródła

Build token migration program

Change-Id: I78b10931fb09f5bca457c1fb36095fdbcea9e65f
Hendrik Hofstadt 4 lat temu
rodzic
commit
88933d13e2

+ 3 - 0
devnet/solana-devnet.yaml

@@ -59,6 +59,9 @@ spec:
             - --bpf-program
             - P2WH424242424242424242424242424242424242424
             - /opt/solana/deps/pyth2wormhole.so
+            - --bpf-program
+            - Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK
+            - /opt/solana/deps/wormhole_migration.so
             - --log
           ports:
             - containerPort: 8001

+ 1 - 0
docs/devnet.md

@@ -14,6 +14,7 @@
 | Bridge Core        | SOL             | Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o                                                           |                                                                                                                                                                |
 | Token Bridge       | SOL             | B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE                                                          |                                                                                                                                                                |
 | NFT Bridge         | SOL             | NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA                                                           |                                                                                                                                                                |
+| Migration Contract | SOL             | Ex9bCdVMSfx7EzB3pgSi2R4UHwJAXvTw18rBQm5YQ8gK                                                          |                                                                                                                                                                |
 | Test Wallet        | Terra           | terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v                                                          | Mnemonic: `notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius` |
 | Example Token      | Terra           | terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh                                                          | Tokens minted to Test Wallet                                                                                                                                   |
 | Bridge Core        | Terra           | terra18eezxhys9jwku67cm4w84xhnzt4xjj77w2qt62                                                          |                                                                                                                                                                |

+ 3 - 0
solana/Dockerfile

@@ -48,13 +48,16 @@ RUN --mount=type=cache,target=bridge/target \
     --mount=type=cache,target=modules/token_bridge/target \
     --mount=type=cache,target=modules/nft_bridge/target \
     --mount=type=cache,target=pyth2wormhole/target \
+    --mount=type=cache,target=migration/target \
     cargo build-bpf --manifest-path "bridge/program/Cargo.toml" && \
     cargo build-bpf --manifest-path "bridge/cpi_poster/Cargo.toml" && \
     cargo build-bpf --manifest-path "modules/token_bridge/program/Cargo.toml" && \
     cargo build-bpf --manifest-path "pyth2wormhole/program/Cargo.toml" && \
     cargo build-bpf --manifest-path "modules/nft_bridge/program/Cargo.toml" && \
+    cargo build-bpf --manifest-path "migration/Cargo.toml" && \
     cp bridge/target/deploy/bridge.so /opt/solana/deps/bridge.so && \
     cp bridge/target/deploy/cpi_poster.so /opt/solana/deps/cpi_poster.so && \
+    cp migration/target/deploy/wormhole_migration.so /opt/solana/deps/wormhole_migration.so && \
     cp modules/token_bridge/target/deploy/token_bridge.so /opt/solana/deps/token_bridge.so && \
     cp modules/nft_bridge/target/deploy/nft_bridge.so /opt/solana/deps/nft_bridge.so && \
     cp modules/token_bridge/token-metadata/spl_token_metadata.so /opt/solana/deps/spl_token_metadata.so && \

+ 7 - 0
solana/Dockerfile.wasm

@@ -16,6 +16,7 @@ ENV BRIDGE_ADDRESS="Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
 COPY bridge bridge
 COPY modules modules
 COPY solitaire solitaire
+COPY migration migration
 
 # Compile Wormhole
 RUN --mount=type=cache,target=/root/.cache \
@@ -35,6 +36,11 @@ RUN --mount=type=cache,target=/root/.cache \
 	--mount=type=cache,target=modules/token_bridge/target \
     cd modules/token_bridge/program && /usr/local/cargo/bin/wasm-pack build --target nodejs -d nodejs -- --features wasm
 
+# Compile Migration
+RUN --mount=type=cache,target=/root/.cache \
+	--mount=type=cache,target=migration/target \
+    cd migration && /usr/local/cargo/bin/wasm-pack build --target bundler -d bundler -- --features wasm
+
 # Compile NFT Bridge
 RUN --mount=type=cache,target=/root/.cache \
 	--mount=type=cache,target=modules/nft_bridge/target \
@@ -48,6 +54,7 @@ FROM scratch AS export
 
 COPY --from=build /usr/src/bridge/bridge/program/bundler sdk/js/src/solana/core
 COPY --from=build /usr/src/bridge/modules/token_bridge/program/bundler sdk/js/src/solana/token
+COPY --from=build /usr/src/bridge/migration/bundler sdk/js/src/solana/migration
 COPY --from=build /usr/src/bridge/modules/nft_bridge/program/bundler sdk/js/src/solana/nft
 
 COPY --from=build /usr/src/bridge/bridge/program/nodejs clients/solana/pkg

+ 41 - 0
solana/migration/Cargo.toml

@@ -0,0 +1,41 @@
+[package]
+name = "wormhole-migration"
+version = "0.1.0"
+description = "Created with Rocksalt"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "wormhole_migration"
+
+[features]
+no-entrypoint = ["solitaire/no-entrypoint", "rand"]
+trace = ["solitaire/trace"]
+wasm = ["no-entrypoint"]
+client = ["solitaire-client", "solitaire/client", "no-entrypoint"]
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+borsh = "0.8.1"
+byteorder = "1.4.3"
+rocksalt = { path = "../solitaire/rocksalt" }
+solitaire = { path = "../solitaire/program" }
+sha3 = "0.9.1"
+solana-program = "*"
+spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
+solitaire-client = { path = "../solitaire/client", optional = true }
+wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] }
+serde = { version = "1.0", features = ["derive"] }
+rand = { version = "0.7.3", optional = true }
+
+[dev-dependencies]
+hex = "*"
+hex-literal = "0.3.1"
+libsecp256k1 = { version = "0.3.5", features = [] }
+solana-client = "1.7.0"
+solana-sdk = "=1.7.0"
+spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
+
+[patch.crates-io]
+memmap2 = { path = "../bridge/memmap2-rs" }

+ 11 - 0
solana/migration/rustfmt.toml

@@ -0,0 +1,11 @@
+# Merge similar crates together to avoid multiple use statements.
+imports_granularity = "Crate"
+
+# Consistency in formatting makes tool based searching/editing better.
+empty_item_single_line = false
+
+# Easier editing when arbitrary mixed use statements do not collapse.
+imports_layout = "Vertical"
+
+# Default rustfmt formatting of match arms with branches is awful.
+match_arm_leading_pipes = "Preserve"

+ 84 - 0
solana/migration/src/accounts.rs

@@ -0,0 +1,84 @@
+use crate::types::{
+    PoolData,
+    SplAccount,
+    SplMint,
+};
+use solana_program::pubkey::Pubkey;
+use solitaire::{
+    processors::seeded::Seeded,
+    AccountState,
+    Data,
+    Derive,
+    Info,
+};
+
+pub type ShareMint<'a, const STATE: AccountState> = Data<'a, SplMint, { STATE }>;
+
+pub struct ShareMintDerivationData {
+    pub pool: Pubkey,
+}
+
+impl<'b, const STATE: AccountState> Seeded<&ShareMintDerivationData> for ShareMint<'b, { STATE }> {
+    fn seeds(accs: &ShareMintDerivationData) -> Vec<Vec<u8>> {
+        vec![
+            String::from("share_mint").as_bytes().to_vec(),
+            accs.pool.to_bytes().to_vec(),
+        ]
+    }
+}
+
+pub type FromCustodyTokenAccount<'a, const STATE: AccountState> = Data<'a, SplAccount, { STATE }>;
+
+pub struct FromCustodyTokenAccountDerivationData {
+    pub pool: Pubkey,
+}
+
+impl<'b, const STATE: AccountState> Seeded<&FromCustodyTokenAccountDerivationData>
+    for FromCustodyTokenAccount<'b, { STATE }>
+{
+    fn seeds(accs: &FromCustodyTokenAccountDerivationData) -> Vec<Vec<u8>> {
+        vec![
+            String::from("from_custody").as_bytes().to_vec(),
+            accs.pool.to_bytes().to_vec(),
+        ]
+    }
+}
+
+pub type ToCustodyTokenAccount<'a, const STATE: AccountState> = Data<'a, SplAccount, { STATE }>;
+
+pub struct ToCustodyTokenAccountDerivationData {
+    pub pool: Pubkey,
+}
+
+impl<'b, const STATE: AccountState> Seeded<&ToCustodyTokenAccountDerivationData>
+    for ToCustodyTokenAccount<'b, { STATE }>
+{
+    fn seeds(accs: &ToCustodyTokenAccountDerivationData) -> Vec<Vec<u8>> {
+        vec![
+            String::from("to_custody").as_bytes().to_vec(),
+            accs.pool.to_bytes().to_vec(),
+        ]
+    }
+}
+
+pub type MigrationPool<'a, const STATE: AccountState> = Data<'a, PoolData, { STATE }>;
+
+pub struct MigrationPoolDerivationData {
+    pub from: Pubkey,
+    pub to: Pubkey,
+}
+
+impl<'b, const STATE: AccountState> Seeded<&MigrationPoolDerivationData>
+    for MigrationPool<'b, { STATE }>
+{
+    fn seeds(accs: &MigrationPoolDerivationData) -> Vec<Vec<u8>> {
+        vec![
+            String::from("pool").as_bytes().to_vec(),
+            accs.from.to_bytes().to_vec(),
+            accs.to.to_bytes().to_vec(),
+        ]
+    }
+}
+
+pub type CustodySigner<'a> = Derive<Info<'a>, "custody_signer">;
+pub type AuthoritySigner<'a> = Derive<Info<'a>, "authority_signer">;

+ 4 - 0
solana/migration/src/api.rs

@@ -0,0 +1,4 @@
+pub mod add_liquidity;
+pub mod claim_shares;
+pub mod create_pool;
+pub mod migrate_tokens;

+ 112 - 0
solana/migration/src/api/add_liquidity.rs

@@ -0,0 +1,112 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        CustodySigner,
+        MigrationPool,
+        ShareMint,
+        ShareMintDerivationData,
+        ToCustodyTokenAccount,
+        ToCustodyTokenAccountDerivationData,
+    },
+    types::{
+        SplAccount,
+        SplMint,
+    },
+    MigrationError::WrongMint,
+};
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+
+use crate::accounts::MigrationPoolDerivationData;
+use solitaire::*;
+
+#[derive(FromAccounts)]
+pub struct AddLiquidity<'b> {
+    pub pool: Mut<MigrationPool<'b, { AccountState::Initialized }>>,
+    pub from_mint: Data<'b, SplMint, { AccountState::Initialized }>,
+    pub to_mint: Data<'b, SplMint, { AccountState::Initialized }>,
+    pub to_token_custody: Mut<ToCustodyTokenAccount<'b, { AccountState::Initialized }>>,
+    pub share_mint: Mut<ShareMint<'b, { AccountState::Initialized }>>,
+
+    pub to_lp_acc: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+    pub lp_share_acc: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+    pub custody_signer: CustodySigner<'b>,
+    pub authority_signer: AuthoritySigner<'b>,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct AddLiquidityData {
+    pub amount: u64,
+}
+
+pub fn add_liquidity(
+    ctx: &ExecutionContext,
+    accs: &mut AddLiquidity,
+    data: AddLiquidityData,
+) -> Result<()> {
+    if *accs.from_mint.info().key != accs.pool.from {
+        return Err(WrongMint.into());
+    }
+    if *accs.to_mint.info().key != accs.pool.to {
+        return Err(WrongMint.into());
+    }
+    if accs.lp_share_acc.mint != *accs.share_mint.info().key {
+        return Err(WrongMint.into());
+    }
+    accs.to_token_custody.verify_derivation(
+        ctx.program_id,
+        &ToCustodyTokenAccountDerivationData {
+            pool: *accs.pool.info().key,
+        },
+    )?;
+    accs.share_mint.verify_derivation(
+        ctx.program_id,
+        &ShareMintDerivationData {
+            pool: *accs.pool.info().key,
+        },
+    )?;
+    accs.pool.verify_derivation(
+        ctx.program_id,
+        &MigrationPoolDerivationData {
+            from: accs.pool.from,
+            to: accs.pool.to,
+        },
+    )?;
+
+    // Transfer out-tokens in
+    let transfer_ix = spl_token::instruction::transfer(
+        &spl_token::id(),
+        accs.to_lp_acc.info().key,
+        accs.to_token_custody.info().key,
+        accs.authority_signer.key,
+        &[],
+        data.amount,
+    )?;
+    invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?;
+
+    // The share amount should be equal to the amount of from tokens an lp would be getting
+    let share_amount = if accs.from_mint.decimals > accs.to_mint.decimals {
+        data.amount
+            .checked_mul(10u64.pow((accs.from_mint.decimals - accs.to_mint.decimals) as u32))
+            .unwrap()
+    } else {
+        data.amount
+            .checked_div(10u64.pow((accs.to_mint.decimals - accs.from_mint.decimals) as u32))
+            .unwrap()
+    };
+
+    // Mint LP shares
+    let mint_ix = spl_token::instruction::mint_to(
+        &spl_token::id(),
+        accs.from_mint.info().key,
+        accs.lp_share_acc.info().key,
+        accs.custody_signer.key,
+        &[],
+        share_amount,
+    )?;
+    invoke_seeded(&mint_ix, ctx, &accs.custody_signer, None)?;
+
+    Ok(())
+}

+ 103 - 0
solana/migration/src/api/claim_shares.rs

@@ -0,0 +1,103 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        CustodySigner,
+        FromCustodyTokenAccountDerivationData,
+        MigrationPool,
+        ShareMint,
+        ShareMintDerivationData,
+        ToCustodyTokenAccount,
+    },
+    types::{
+        SplAccount,
+        SplMint,
+    },
+    MigrationError::WrongMint,
+};
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+
+use crate::accounts::MigrationPoolDerivationData;
+use solitaire::{
+    processors::seeded::{
+        invoke_seeded,
+        Seeded,
+    },
+    *,
+};
+
+#[derive(FromAccounts)]
+pub struct ClaimShares<'b> {
+    pub pool: Mut<MigrationPool<'b, { AccountState::Initialized }>>,
+    pub to_mint: Data<'b, SplMint, { AccountState::Initialized }>,
+    pub from_token_custody: Mut<ToCustodyTokenAccount<'b, { AccountState::Initialized }>>,
+    pub share_mint: Mut<ShareMint<'b, { AccountState::Initialized }>>,
+
+    pub from_lp_acc: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+    pub lp_share_acc: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+    pub custody_signer: CustodySigner<'b>,
+    pub authority_signer: AuthoritySigner<'b>,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct ClaimSharesData {
+    pub amount: u64,
+}
+
+pub fn claim_shares(
+    ctx: &ExecutionContext,
+    accs: &mut ClaimShares,
+    data: ClaimSharesData,
+) -> Result<()> {
+    if *accs.to_mint.info().key != accs.pool.to {
+        return Err(WrongMint.into());
+    }
+    if accs.lp_share_acc.mint != *accs.share_mint.info().key {
+        return Err(WrongMint.into());
+    }
+    accs.from_token_custody.verify_derivation(
+        ctx.program_id,
+        &FromCustodyTokenAccountDerivationData {
+            pool: *accs.pool.info().key,
+        },
+    )?;
+    accs.share_mint.verify_derivation(
+        ctx.program_id,
+        &ShareMintDerivationData {
+            pool: *accs.pool.info().key,
+        },
+    )?;
+    accs.pool.verify_derivation(
+        ctx.program_id,
+        &MigrationPoolDerivationData {
+            from: accs.pool.from,
+            to: accs.pool.to,
+        },
+    )?;
+
+    // Transfer claimed tokens to LP
+    let transfer_ix = spl_token::instruction::transfer(
+        &spl_token::id(),
+        accs.from_token_custody.info().key,
+        accs.from_lp_acc.info().key,
+        accs.custody_signer.key,
+        &[],
+        data.amount,
+    )?;
+    invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
+
+    // Burn LP shares
+    let mint_ix = spl_token::instruction::burn(
+        &spl_token::id(),
+        accs.lp_share_acc.info().key,
+        accs.share_mint.info().key,
+        accs.authority_signer.key,
+        &[],
+        data.amount,
+    )?;
+    invoke_seeded(&mint_ix, ctx, &accs.authority_signer, None)?;
+
+    Ok(())
+}

+ 119 - 0
solana/migration/src/api/create_pool.rs

@@ -0,0 +1,119 @@
+use crate::{
+    accounts::{
+        CustodySigner,
+        FromCustodyTokenAccount,
+        FromCustodyTokenAccountDerivationData,
+        MigrationPool,
+        MigrationPoolDerivationData,
+        ShareMint,
+        ShareMintDerivationData,
+        ToCustodyTokenAccount,
+        ToCustodyTokenAccountDerivationData,
+    },
+    types::SplMint,
+};
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+use solana_program::program::invoke_signed;
+use solitaire::{
+    processors::seeded::Seeded,
+    CreationLamports::Exempt,
+    *,
+};
+
+#[derive(FromAccounts)]
+pub struct CreatePool<'b> {
+    pub payer: Mut<Signer<Info<'b>>>,
+
+    pub pool: Mut<MigrationPool<'b, { AccountState::Uninitialized }>>,
+    pub from_mint: Data<'b, SplMint, { AccountState::Initialized }>,
+    pub to_mint: Data<'b, SplMint, { AccountState::Initialized }>,
+    pub from_token_custody: Mut<FromCustodyTokenAccount<'b, { AccountState::Uninitialized }>>,
+    pub to_token_custody: Mut<ToCustodyTokenAccount<'b, { AccountState::Uninitialized }>>,
+    pub pool_mint: Mut<ShareMint<'b, { AccountState::Uninitialized }>>,
+
+    pub custody_signer: CustodySigner<'b>,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct CreatePoolData {}
+
+pub fn create_pool(
+    ctx: &ExecutionContext,
+    accs: &mut CreatePool,
+    _data: CreatePoolData,
+) -> Result<()> {
+    // Create from custody account
+    accs.from_token_custody.create(
+        &FromCustodyTokenAccountDerivationData {
+            pool: *accs.pool.info().key,
+        },
+        ctx,
+        accs.payer.key,
+        Exempt,
+    )?;
+
+    let init_ix = spl_token::instruction::initialize_account(
+        &spl_token::id(),
+        accs.from_token_custody.info().key,
+        accs.from_mint.info().key,
+        accs.custody_signer.info().key,
+    )?;
+    invoke_signed(&init_ix, ctx.accounts, &[])?;
+
+    // Create to custody account
+    accs.to_token_custody.create(
+        &ToCustodyTokenAccountDerivationData {
+            pool: *accs.pool.info().key,
+        },
+        ctx,
+        accs.payer.key,
+        Exempt,
+    )?;
+
+    let init_ix = spl_token::instruction::initialize_account(
+        &spl_token::id(),
+        accs.to_token_custody.info().key,
+        accs.to_mint.info().key,
+        accs.custody_signer.info().key,
+    )?;
+    invoke_signed(&init_ix, ctx.accounts, &[])?;
+
+    // Create to pool mint
+    accs.pool_mint.create(
+        &ShareMintDerivationData {
+            pool: *accs.pool.info().key,
+        },
+        ctx,
+        accs.payer.key,
+        Exempt,
+    )?;
+
+    let init_ix = spl_token::instruction::initialize_mint(
+        &spl_token::id(),
+        accs.pool_mint.info().key,
+        accs.custody_signer.info().key,
+        None,
+        accs.from_mint.decimals,
+    )?;
+    invoke_signed(&init_ix, ctx.accounts, &[])?;
+
+    // Set fields on pool
+    accs.pool.from = *accs.from_mint.info().key;
+    accs.pool.to = *accs.to_mint.info().key;
+
+    // Create pool
+    accs.pool.create(
+        &MigrationPoolDerivationData {
+            from: *accs.from_mint.info().key,
+            to: *accs.to_mint.info().key,
+        },
+        ctx,
+        accs.payer.key,
+        Exempt,
+    )?;
+
+    Ok(())
+}

+ 121 - 0
solana/migration/src/api/migrate_tokens.rs

@@ -0,0 +1,121 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        CustodySigner,
+        FromCustodyTokenAccount,
+        FromCustodyTokenAccountDerivationData,
+        MigrationPool,
+        ToCustodyTokenAccount,
+        ToCustodyTokenAccountDerivationData,
+    },
+    types::{
+        SplAccount,
+        SplMint,
+    },
+    MigrationError::WrongMint,
+};
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+
+use crate::accounts::MigrationPoolDerivationData;
+use solitaire::{
+    processors::seeded::{
+        invoke_seeded,
+        Seeded,
+    },
+    *,
+};
+
+#[derive(FromAccounts)]
+pub struct MigrateTokens<'b> {
+    pub pool: Mut<MigrationPool<'b, { AccountState::Initialized }>>,
+    pub from_mint: Data<'b, SplMint, { AccountState::Initialized }>,
+    pub to_mint: Data<'b, SplMint, { AccountState::Initialized }>,
+    pub to_token_custody: Mut<ToCustodyTokenAccount<'b, { AccountState::Initialized }>>,
+    pub from_token_custody: Mut<FromCustodyTokenAccount<'b, { AccountState::Initialized }>>,
+
+    pub user_from_acc: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+    pub user_to_acc: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
+    pub custody_signer: CustodySigner<'b>,
+    pub authority_signer: AuthoritySigner<'b>,
+}
+
+#[derive(BorshDeserialize, BorshSerialize, Default)]
+pub struct MigrateTokensData {
+    pub amount: u64,
+}
+
+pub fn migrate_tokens(
+    ctx: &ExecutionContext,
+    accs: &mut MigrateTokens,
+    data: MigrateTokensData,
+) -> Result<()> {
+    if *accs.from_mint.info().key != accs.pool.from {
+        return Err(WrongMint.into());
+    }
+    if *accs.to_mint.info().key != accs.pool.to {
+        return Err(WrongMint.into());
+    }
+    if accs.user_from_acc.mint != accs.pool.from {
+        return Err(WrongMint.into());
+    }
+    if accs.user_to_acc.mint != accs.pool.to {
+        return Err(WrongMint.into());
+    }
+    accs.to_token_custody.verify_derivation(
+        ctx.program_id,
+        &ToCustodyTokenAccountDerivationData {
+            pool: *accs.pool.info().key,
+        },
+    )?;
+    accs.from_token_custody.verify_derivation(
+        ctx.program_id,
+        &FromCustodyTokenAccountDerivationData {
+            pool: *accs.pool.info().key,
+        },
+    )?;
+    accs.pool.verify_derivation(
+        ctx.program_id,
+        &MigrationPoolDerivationData {
+            from: accs.pool.from,
+            to: accs.pool.to,
+        },
+    )?;
+
+    // Transfer in-tokens in
+    let transfer_ix = spl_token::instruction::transfer(
+        &spl_token::id(),
+        accs.user_from_acc.info().key,
+        accs.from_token_custody.info().key,
+        accs.authority_signer.key,
+        &[],
+        data.amount,
+    )?;
+    invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?;
+
+    // The share amount should be equal to the amount of from tokens an lp would be getting
+    let out_amount = if accs.from_mint.decimals > accs.to_mint.decimals {
+        data.amount
+            .checked_div(10u64.pow((accs.from_mint.decimals - accs.to_mint.decimals) as u32))
+            .unwrap()
+    } else {
+        data.amount
+            .checked_mul(10u64.pow((accs.to_mint.decimals - accs.from_mint.decimals) as u32))
+            .unwrap()
+    };
+
+    // Transfer out-tokens to user
+    let transfer_ix = spl_token::instruction::transfer(
+        &spl_token::id(),
+        accs.to_token_custody.info().key,
+        accs.user_to_acc.info().key,
+        accs.custody_signer.key,
+        &[],
+        out_amount,
+    )?;
+    invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
+
+    Ok(())
+}

+ 242 - 0
solana/migration/src/instructions.rs

@@ -0,0 +1,242 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        CustodySigner,
+        FromCustodyTokenAccount,
+        FromCustodyTokenAccountDerivationData,
+        MigrationPool,
+        MigrationPoolDerivationData,
+        ShareMint,
+        ShareMintDerivationData,
+        ToCustodyTokenAccount,
+        ToCustodyTokenAccountDerivationData,
+    },
+    api::{
+        add_liquidity::AddLiquidityData,
+        claim_shares::ClaimSharesData,
+        create_pool::CreatePoolData,
+        migrate_tokens::MigrateTokensData,
+    },
+};
+use borsh::BorshSerialize;
+use solana_program::{
+    instruction::{
+        AccountMeta,
+        Instruction,
+    },
+    pubkey::Pubkey,
+};
+use solitaire::{
+    processors::seeded::Seeded,
+    AccountState,
+};
+
+pub fn add_liquidity(
+    program_id: Pubkey,
+    from_mint: Pubkey,
+    to_mint: Pubkey,
+    liquidity_token_account: Pubkey,
+    lp_share_token_account: Pubkey,
+    amount: u64,
+) -> solitaire::Result<Instruction> {
+    let pool = MigrationPool::<'_, { AccountState::Initialized }>::key(
+        &MigrationPoolDerivationData {
+            from: from_mint,
+            to: to_mint,
+        },
+        &program_id,
+    );
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(pool, false),
+            AccountMeta::new_readonly(from_mint, false),
+            AccountMeta::new_readonly(to_mint, false),
+            AccountMeta::new(
+                ToCustodyTokenAccount::<'_, { AccountState::Uninitialized }>::key(
+                    &ToCustodyTokenAccountDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(
+                ShareMint::<'_, { AccountState::Uninitialized }>::key(
+                    &ShareMintDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(liquidity_token_account, false),
+            AccountMeta::new(lp_share_token_account, false),
+            AccountMeta::new_readonly(CustodySigner::key(None, &program_id), false),
+            AccountMeta::new_readonly(AuthoritySigner::key(None, &program_id), false),
+            // Dependencies
+            AccountMeta::new(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new(solana_program::system_program::id(), false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+        ],
+        data: (
+            crate::instruction::Instruction::AddLiquidity,
+            AddLiquidityData { amount },
+        )
+            .try_to_vec()?,
+    })
+}
+
+pub fn claim_shares(
+    program_id: Pubkey,
+    from_mint: Pubkey,
+    to_mint: Pubkey,
+    output_token_account: Pubkey,
+    lp_share_token_account: Pubkey,
+    amount: u64,
+) -> solitaire::Result<Instruction> {
+    let pool = MigrationPool::<'_, { AccountState::Initialized }>::key(
+        &MigrationPoolDerivationData {
+            from: from_mint,
+            to: to_mint,
+        },
+        &program_id,
+    );
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(pool, false),
+            AccountMeta::new_readonly(to_mint, false),
+            AccountMeta::new(
+                FromCustodyTokenAccount::<'_, { AccountState::Uninitialized }>::key(
+                    &FromCustodyTokenAccountDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(
+                ShareMint::<'_, { AccountState::Uninitialized }>::key(
+                    &ShareMintDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(output_token_account, false),
+            AccountMeta::new(lp_share_token_account, false),
+            AccountMeta::new_readonly(CustodySigner::key(None, &program_id), false),
+            AccountMeta::new_readonly(AuthoritySigner::key(None, &program_id), false),
+            // Dependencies
+            AccountMeta::new(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new(solana_program::system_program::id(), false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+        ],
+        data: (
+            crate::instruction::Instruction::ClaimShares,
+            ClaimSharesData { amount },
+        )
+            .try_to_vec()?,
+    })
+}
+
+pub fn create_pool(
+    program_id: Pubkey,
+    payer: Pubkey,
+    from_mint: Pubkey,
+    to_mint: Pubkey,
+) -> solitaire::Result<Instruction> {
+    let pool = MigrationPool::<'_, { AccountState::Initialized }>::key(
+        &MigrationPoolDerivationData {
+            from: from_mint,
+            to: to_mint,
+        },
+        &program_id,
+    );
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(payer, true),
+            AccountMeta::new(pool, false),
+            AccountMeta::new_readonly(from_mint, false),
+            AccountMeta::new_readonly(to_mint, false),
+            AccountMeta::new(
+                FromCustodyTokenAccount::<'_, { AccountState::Uninitialized }>::key(
+                    &FromCustodyTokenAccountDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(
+                ToCustodyTokenAccount::<'_, { AccountState::Uninitialized }>::key(
+                    &ToCustodyTokenAccountDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(
+                ShareMint::<'_, { AccountState::Uninitialized }>::key(
+                    &ShareMintDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new_readonly(CustodySigner::key(None, &program_id), false),
+            // Dependencies
+            AccountMeta::new(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new(solana_program::system_program::id(), false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+        ],
+        data: (
+            crate::instruction::Instruction::CreatePool,
+            CreatePoolData {},
+        )
+            .try_to_vec()?,
+    })
+}
+
+pub fn migrate_tokens(
+    program_id: Pubkey,
+    from_mint: Pubkey,
+    to_mint: Pubkey,
+    input_token_account: Pubkey,
+    output_token_account: Pubkey,
+    amount: u64,
+) -> solitaire::Result<Instruction> {
+    let pool = MigrationPool::<'_, { AccountState::Initialized }>::key(
+        &MigrationPoolDerivationData {
+            from: from_mint,
+            to: to_mint,
+        },
+        &program_id,
+    );
+    Ok(Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(pool, false),
+            AccountMeta::new_readonly(from_mint, false),
+            AccountMeta::new_readonly(to_mint, false),
+            AccountMeta::new(
+                ToCustodyTokenAccount::<'_, { AccountState::Uninitialized }>::key(
+                    &ToCustodyTokenAccountDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(
+                FromCustodyTokenAccount::<'_, { AccountState::Uninitialized }>::key(
+                    &FromCustodyTokenAccountDerivationData { pool },
+                    &program_id,
+                ),
+                false,
+            ),
+            AccountMeta::new(input_token_account, false),
+            AccountMeta::new(output_token_account, false),
+            AccountMeta::new_readonly(CustodySigner::key(None, &program_id), false),
+            AccountMeta::new_readonly(AuthoritySigner::key(None, &program_id), false),
+            // Dependencies
+            AccountMeta::new(solana_program::sysvar::rent::id(), false),
+            AccountMeta::new(solana_program::system_program::id(), false),
+            AccountMeta::new_readonly(spl_token::id(), false),
+        ],
+        data: (
+            crate::instruction::Instruction::MigrateTokens,
+            MigrateTokensData { amount },
+        )
+            .try_to_vec()?,
+    })
+}

+ 45 - 0
solana/migration/src/lib.rs

@@ -0,0 +1,45 @@
+#![allow(incomplete_features)]
+#![feature(const_generics)]
+
+use api::{
+    add_liquidity::*,
+    claim_shares::*,
+    create_pool::*,
+    migrate_tokens::*,
+};
+use solitaire::{
+    solitaire,
+    SolitaireError,
+};
+
+pub mod accounts;
+pub mod api;
+pub mod types;
+
+#[cfg(feature = "no-entrypoint")]
+pub mod instructions;
+
+#[cfg(feature = "wasm")]
+#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+extern crate wasm_bindgen;
+
+#[cfg(feature = "wasm")]
+#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
+pub mod wasm;
+
+pub enum MigrationError {
+    WrongMint,
+}
+
+impl From<MigrationError> for SolitaireError {
+    fn from(t: MigrationError) -> SolitaireError {
+        SolitaireError::Custom(t as u64)
+    }
+}
+
+solitaire! {
+    AddLiquidity(AddLiquidityData) => add_liquidity,
+    ClaimShares(ClaimSharesData) => claim_shares,
+    CreatePool(CreatePoolData) => create_pool,
+    MigrateTokens(MigrateTokensData) => migrate_tokens,
+}

+ 35 - 0
solana/migration/src/types.rs

@@ -0,0 +1,35 @@
+use borsh::{
+    BorshDeserialize,
+    BorshSerialize,
+};
+use serde::{
+    Deserialize,
+    Serialize,
+};
+use solana_program::pubkey::Pubkey;
+use solitaire::{
+    pack_type,
+    processors::seeded::{
+        AccountOwner,
+        Owned,
+    },
+};
+use spl_token::state::{
+    Account,
+    Mint,
+};
+
+#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
+pub struct PoolData {
+    pub from: Pubkey,
+    pub to: Pubkey,
+}
+
+impl Owned for PoolData {
+    fn owner(&self) -> AccountOwner {
+        AccountOwner::This
+    }
+}
+
+pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
+pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));

+ 194 - 0
solana/migration/src/wasm.rs

@@ -0,0 +1,194 @@
+use crate::{
+    accounts::{
+        AuthoritySigner,
+        FromCustodyTokenAccount,
+        FromCustodyTokenAccountDerivationData,
+        MigrationPool,
+        MigrationPoolDerivationData,
+        ShareMint,
+        ShareMintDerivationData,
+        ToCustodyTokenAccount,
+        ToCustodyTokenAccountDerivationData,
+    },
+    instructions,
+    types::PoolData,
+};
+use borsh::BorshDeserialize;
+use solana_program::pubkey::Pubkey;
+use solitaire::{
+    processors::seeded::Seeded,
+    AccountState,
+};
+use std::str::FromStr;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+pub fn add_liquidity(
+    program_id: String,
+    from_mint: String,
+    to_mint: String,
+    liquidity_token_account: String,
+    lp_share_token_account: String,
+    amount: u64,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let from_mint = Pubkey::from_str(from_mint.as_str()).unwrap();
+    let to_mint = Pubkey::from_str(to_mint.as_str()).unwrap();
+    let liquidity_token_account = Pubkey::from_str(liquidity_token_account.as_str()).unwrap();
+    let lp_share_token_account = Pubkey::from_str(lp_share_token_account.as_str()).unwrap();
+
+    let ix = instructions::add_liquidity(
+        program_id,
+        from_mint,
+        to_mint,
+        liquidity_token_account,
+        lp_share_token_account,
+        amount,
+    )
+    .unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn claim_shares(
+    program_id: String,
+    from_mint: String,
+    to_mint: String,
+    output_token_account: String,
+    lp_share_token_account: String,
+    amount: u64,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let from_mint = Pubkey::from_str(from_mint.as_str()).unwrap();
+    let to_mint = Pubkey::from_str(to_mint.as_str()).unwrap();
+    let output_token_account = Pubkey::from_str(output_token_account.as_str()).unwrap();
+    let lp_share_token_account = Pubkey::from_str(lp_share_token_account.as_str()).unwrap();
+
+    let ix = instructions::claim_shares(
+        program_id,
+        from_mint,
+        to_mint,
+        output_token_account,
+        lp_share_token_account,
+        amount,
+    )
+    .unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn create_pool(
+    program_id: String,
+    payer: String,
+    from_mint: String,
+    to_mint: String,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let payer = Pubkey::from_str(payer.as_str()).unwrap();
+    let from_mint = Pubkey::from_str(from_mint.as_str()).unwrap();
+    let to_mint = Pubkey::from_str(to_mint.as_str()).unwrap();
+
+    let ix = instructions::create_pool(program_id, payer, from_mint, to_mint).unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn migrate_tokens(
+    program_id: String,
+    from_mint: String,
+    to_mint: String,
+    input_token_account: String,
+    output_token_account: String,
+    amount: u64,
+) -> JsValue {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let from_mint = Pubkey::from_str(from_mint.as_str()).unwrap();
+    let to_mint = Pubkey::from_str(to_mint.as_str()).unwrap();
+    let input_token_account = Pubkey::from_str(input_token_account.as_str()).unwrap();
+    let output_token_account = Pubkey::from_str(output_token_account.as_str()).unwrap();
+
+    let ix = instructions::migrate_tokens(
+        program_id,
+        from_mint,
+        to_mint,
+        input_token_account,
+        output_token_account,
+        amount,
+    )
+    .unwrap();
+
+    JsValue::from_serde(&ix).unwrap()
+}
+
+#[wasm_bindgen]
+pub fn pool_address(program_id: String, from_mint: String, to_mint: String) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let from_mint_key = Pubkey::from_str(from_mint.as_str()).unwrap();
+    let to_mint_key = Pubkey::from_str(to_mint.as_str()).unwrap();
+
+    let pool_addr = MigrationPool::<'_, { AccountState::Initialized }>::key(
+        &MigrationPoolDerivationData {
+            from: from_mint_key,
+            to: to_mint_key,
+        },
+        &program_id,
+    );
+
+    pool_addr.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn authority_address(program_id: String) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+
+    let authority_addr = AuthoritySigner::key(None, &program_id);
+
+    authority_addr.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn share_mint_address(program_id: String, pool: String) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let pool_key = Pubkey::from_str(pool.as_str()).unwrap();
+
+    let share_mint_addr = ShareMint::<'_, { AccountState::Initialized }>::key(
+        &ShareMintDerivationData { pool: pool_key },
+        &program_id,
+    );
+
+    share_mint_addr.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn from_custody_address(program_id: String, pool: String) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let pool_key = Pubkey::from_str(pool.as_str()).unwrap();
+
+    let from_custody_addr = FromCustodyTokenAccount::<'_, { AccountState::Initialized }>::key(
+        &FromCustodyTokenAccountDerivationData { pool: pool_key },
+        &program_id,
+    );
+
+    from_custody_addr.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn to_custody_address(program_id: String, pool: String) -> Vec<u8> {
+    let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
+    let pool_key = Pubkey::from_str(pool.as_str()).unwrap();
+
+    let to_custody_addr = ToCustodyTokenAccount::<'_, { AccountState::Initialized }>::key(
+        &ToCustodyTokenAccountDerivationData { pool: pool_key },
+        &program_id,
+    );
+
+    to_custody_addr.to_bytes().to_vec()
+}
+
+#[wasm_bindgen]
+pub fn parse_pool(data: Vec<u8>) -> JsValue {
+    JsValue::from_serde(&PoolData::try_from_slice(data.as_slice()).unwrap()).unwrap()
+}