diff --git a/solana/modules/icco_contributor/program/src/accounts.rs b/solana/modules/icco_contributor/program/src/accounts.rs index d5aac889..ac804355 100644 --- a/solana/modules/icco_contributor/program/src/accounts.rs +++ b/solana/modules/icco_contributor/program/src/accounts.rs @@ -74,6 +74,7 @@ impl<'b, const STATE: AccountState> Seeded<&ContributionStateAccountDerivationDa pub type CustodyAccount<'b, const STATE: AccountState> = Data<'b, SplAccount, { STATE }>; pub struct CustodyAccountDerivationData { + pub sale_id: u128, pub mint: Pubkey, } diff --git a/solana/modules/icco_contributor/program/src/api/contribute.rs b/solana/modules/icco_contributor/program/src/api/contribute.rs index 7f133718..717deb8f 100644 --- a/solana/modules/icco_contributor/program/src/api/contribute.rs +++ b/solana/modules/icco_contributor/program/src/api/contribute.rs @@ -14,7 +14,9 @@ use crate::{ CustodyAccountDerivationData, ContributionStateAccount, ContributionStateAccountDerivationData, + AuthoritySigner, }, + errors::Error::*, types::*, }; @@ -62,6 +64,7 @@ pub struct ContributeIccoSale<'b> { pub from: Mut>, // From account pub mint: Mut>, // From token + pub authority_signer: AuthoritySigner<'b>, pub custody_signer: CustodySigner<'b>, pub custody: Mut>, // TBD Move custody Account init to separate call. By Sale creator before init sale. In case sale creator needs to pay for it. @@ -80,6 +83,7 @@ impl<'a> From<&ContributeIccoSale<'a>> for SaleStateDerivationData { impl<'a> From<&ContributeIccoSale<'a>> for CustodyAccountDerivationData { fn from(accs: &ContributeIccoSale<'a>) -> Self { CustodyAccountDerivationData { + sale_id: accs.init_sale_vaa.sale_id, mint: *accs.mint.info().key, } } @@ -98,7 +102,8 @@ impl<'a> From<&ContributeIccoSale<'a>> for ContributionStateAccountDerivationDat #[derive(BorshDeserialize, BorshSerialize, Default)] pub struct ContributeIccoSaleData { - amount: u128, + amount: u64, + token_idx: u8, } impl<'b> InstructionContext<'b> for ContributeIccoSale<'b> { @@ -111,6 +116,34 @@ pub fn contribute_icco_sale( ) -> Result<()> { msg!("bbrp in contribute_icco_sale!"); + // Check sale status. + if accs.sale_state.is_sealed || accs.sale_state.is_aborted { + return Err(SaleSealedOrAborted.into()); + } + + // Check if sale started. + // require(block.timestamp >= start, "sale not yet started"); + // require(block.timestamp <= end, "sale has ended"); + let now_time = accs.clock.unix_timestamp as u128; // i64 ->u128 + if now_time < accs.init_sale_vaa.get_sale_start(&accs.init_sale_vaa.meta().payload[..]).0 { + return Err(SaleHasNotStarted.into()); + } + if now_time > accs.init_sale_vaa.get_sale_end(&accs.init_sale_vaa.meta().payload[..]).0 { + return Err(SaleHasEnded.into()); + } + + // Make sure token Idx matches passed in token mint addr. + let token_idx = data.token_idx; + if token_idx >= accs.init_sale_vaa.token_cnt { + return Err(InvalidTokenIndex.into()); + } + let token_idx = usize::from(token_idx); + let &token_addr = &accs.init_sale_vaa.get_accepted_token_address(token_idx, &accs.init_sale_vaa.meta().payload[..]); + let token_chain = accs.init_sale_vaa.get_accepted_token_chain(token_idx, &accs.init_sale_vaa.meta().payload[..]); + if &token_addr != accs.mint.info().key || token_chain != CHAIN_ID_SOLANA { + return Err(InvalidTokenAddress.into()); + } + // Create and init custody account as needed. // https://github.com/certusone/wormhole/blob/1792141307c3979b1f267af3e20cfc2f011d7051/solana/modules/token_bridge/program/src/api/transfer.rs#L159 if !accs.custody.is_initialized() { @@ -130,7 +163,16 @@ pub fn contribute_icco_sale( accs.contribution_state.create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?; } - // TBD Do the from->custody non-WH transfer. + // TBD Transfer tokens from->custody non-WH transfer. + let transfer_ix = spl_token::instruction::transfer( + &spl_token::id(), + accs.from.info().key, + accs.custody.info().key, + accs.authority_signer.key, + &[], + data.amount, + )?; + invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?; // store new amount. accs.contribution_state.amount = accs.contribution_state.amount + data.amount; diff --git a/solana/modules/icco_contributor/program/src/api/init_sale.rs b/solana/modules/icco_contributor/program/src/api/init_sale.rs index 8402b12b..b8422a7f 100644 --- a/solana/modules/icco_contributor/program/src/api/init_sale.rs +++ b/solana/modules/icco_contributor/program/src/api/init_sale.rs @@ -29,15 +29,11 @@ use crate::{ SaleStateAccount, SaleStateDerivationData, }, -}; - -use crate:: { errors::Error::{ VAAInvalidEmitterChain, - } + }, }; - use solana_program::msg; use solana_program::{ diff --git a/solana/modules/icco_contributor/program/src/errors.rs b/solana/modules/icco_contributor/program/src/errors.rs index 5ad578d8..9001ffae 100644 --- a/solana/modules/icco_contributor/program/src/errors.rs +++ b/solana/modules/icco_contributor/program/src/errors.rs @@ -9,6 +9,11 @@ pub enum Error { VAAAlreadyExecuted, VAAInvalidEmitterChain, VAAInvalid, + InvalidTokenAddress, + InvalidTokenIndex, + SaleSealedOrAborted, + SaleHasNotStarted, + SaleHasEnded, } /// Errors thrown by the program will bubble up to the solitaire wrapper, which needs a way to diff --git a/solana/modules/icco_contributor/program/src/messages.rs b/solana/modules/icco_contributor/program/src/messages.rs index 3a3bfd2b..846380b4 100644 --- a/solana/modules/icco_contributor/program/src/messages.rs +++ b/solana/modules/icco_contributor/program/src/messages.rs @@ -41,8 +41,8 @@ fn read_u256(buf: &[u8]) -> (u128, u128) { #[derive(PartialEq, Debug)] #[allow(non_snake_case)] pub struct SaleInit { - payload_id: u8, // Sale ID - token_cnt: u8, + payload_id: u8, // 1 + pub token_cnt: u8, pub sale_id: u128, } diff --git a/solana/modules/icco_contributor/program/src/types.rs b/solana/modules/icco_contributor/program/src/types.rs index 7aa1a4dc..a0fb6341 100644 --- a/solana/modules/icco_contributor/program/src/types.rs +++ b/solana/modules/icco_contributor/program/src/types.rs @@ -27,8 +27,8 @@ impl Owned for Config { /// icco sale state. Writeable in init, seal, abort. #[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)] pub struct SaleState { - pub is_sealed: u8, - pub is_aborted: u8, + pub is_sealed: bool, + pub is_aborted: bool, } impl Owned for SaleState { @@ -41,7 +41,7 @@ impl Owned for SaleState { /// icco contribution state. Writeable in contribute, redeem, refund. #[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)] pub struct ContributionState { - pub amount: u128, + pub amount: u64, pub is_redeemed_or_refunded: u8, }