Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,503 changes: 1,383 additions & 120 deletions packages/svm/Cargo.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct ResizeSettings<'info> {
}

fn check_settings_data(data: &[u8], expected_admin: Pubkey) -> Result<()> {
if !data.starts_with(&ControllerSettings::DISCRIMINATOR) {
if !data.starts_with(ControllerSettings::DISCRIMINATOR) {
return Err(ProgramError::InvalidAccountData.into());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub struct EntityRegistry {
}

impl EntityRegistry {
pub fn size(entity_address: &Vec<u8>) -> usize {
pub fn size(entity_address: &[u8]) -> usize {
EntityType::INIT_SPACE + VEC_LEN_SIZE + entity_address.len() + 1
}
}
3 changes: 2 additions & 1 deletion packages/svm/programs/settler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
idl-build = ["anchor-lang/idl-build"]
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
anchor-debug = []
custom-heap = []
custom-panic = []

[dependencies]
anchor-lang = { version = "0.32.1", features = [ "init-if-needed" ] }
anchor-spl = { version = "0.32.1" }
alloy-sol-types = "1.5.4"
alloy-primitives = "1.5.4"

Expand Down
43 changes: 41 additions & 2 deletions packages/svm/programs/settler/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ pub enum SettlerError {
#[msg("Validator is not allowlisted")]
ValidatorNotAllowlisted,

#[msg("Signer must be intent creator")]
#[msg("Incorrect intent creator")]
IncorrectIntentCreator,

#[msg("Signer must be proposal creator")]
#[msg("Incorrect proposal creator")]
IncorrectProposalCreator,

#[msg("Intent is already final")]
Expand Down Expand Up @@ -59,6 +59,9 @@ pub enum SettlerError {
#[msg("Proposal deadline can't be after the Intent's deadline")]
ProposalDeadlineExceedsIntentDeadline,

#[msg("Incorrect proposal data")]
IncorrectProposalData,

#[msg("Intent has insufficient validations")]
InsufficientIntentValidations,

Expand Down Expand Up @@ -86,6 +89,42 @@ pub enum SettlerError {
#[msg("Fee amount exceeds max fee")]
FeeAmountExceedsMaxFee,

#[msg("Unsupported intent op")]
UnsupportedIntentOp,

#[msg("Incorrect intent chain id")]
IncorrectChainId,

#[msg("Invalid transfer recipient: malformed pubkey")]
InvalidTransferRecipient,

#[msg("Incorrect transfer recipient account")]
IncorrectTransferRecipient,

#[msg("Invalid transfer token: malformed pubkey")]
InvalidTransferToken,

#[msg("Incorrect transfer token mint account")]
IncorrectTransferToken,

#[msg("Incorrect fee token mint account")]
IncorrectFeeToken,

#[msg("Account not owned by TokenKeg or Token2022 programs")]
AccountNotOwnedByTokenProgram,

#[msg("Incorrect recipient token account: mint or authority do not match expected")]
IncorrectRecipientTokenAccount,

#[msg("Incorrect user token account: mint or authority do not match expected")]
IncorrectUserTokenAccount,

#[msg("Incorrect solver token account: mint or authority do not match expected")]
IncorrectSolverTokenAccount,

#[msg("Incorrect token program account provided")]
IncorrectTokenProgram,

#[msg("Math Error")]
MathError,
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn create_intent(
) -> Result<()> {
let now = Clock::get()?.unix_timestamp as u64;
require!(deadline > now, SettlerError::DeadlineIsInThePast);
require!(max_fees.len() > 0, SettlerError::NoMaxFees);
require!(!max_fees.is_empty(), SettlerError::NoMaxFees);

// TODO: check hash

Expand Down
49 changes: 42 additions & 7 deletions packages/svm/programs/settler/src/instructions/execute_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
errors::SettlerError,
state::{FulfilledIntent, Intent, Proposal},
types::IntentEvent,
utils::{handle_intent_execution, pay_solver_fees},
};

#[derive(Accounts)]
Expand Down Expand Up @@ -53,24 +54,58 @@ pub struct ExecuteProposal<'info> {
)]
pub fulfilled_intent: Box<Account<'info, FulfilledIntent>>,

#[account(seeds = [b"delegate", intent.user.key().as_ref()], bump)]
pub delegate: SystemAccount<'info>,

pub system_program: Program<'info, System>,
}

pub fn execute_proposal(ctx: Context<ExecuteProposal>) -> Result<()> {
pub fn execute_proposal<'info>(
ctx: Context<'_, '_, '_, 'info, ExecuteProposal<'info>>,
) -> Result<()> {
let intent = &ctx.accounts.intent;
let proposal = &ctx.accounts.proposal;

let mut remaining_accounts_iter = ctx.remaining_accounts.iter();
let token_program = next_account_info(&mut remaining_accounts_iter)?;
let token_2022_program = next_account_info(&mut remaining_accounts_iter)?;

require_keys_eq!(
token_program.key(),
anchor_spl::token::ID,
SettlerError::IncorrectTokenProgram
);
require_keys_eq!(
token_2022_program.key(),
anchor_spl::token_2022::ID,
SettlerError::IncorrectTokenProgram
);

handle_intent_execution(
intent,
proposal,
&ctx.accounts.delegate.clone(),
&mut remaining_accounts_iter,
token_program,
token_2022_program,
ctx.bumps.delegate,
)?;

// TODO: Execute proposal

// TODO: Validate execution

// TODO: Emit events
intent.events.iter().for_each(|event| {
emit!(IntentEventEvent {
event: event.clone()
})
});

// TODO: Pay fees to Solver
pay_solver_fees(
&mut remaining_accounts_iter,
intent,
proposal,
token_program,
token_2022_program,
&ctx.accounts.delegate.clone(),
ctx.bumps.delegate,
)?;

Ok(())
}
Expand Down
4 changes: 3 additions & 1 deletion packages/svm/programs/settler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ pub mod settler {
instructions::create_proposal(ctx, instructions, fees, deadline, is_final)
}

pub fn execute_proposal(ctx: Context<ExecuteProposal>) -> Result<()> {
pub fn execute_proposal<'info>(
ctx: Context<'_, '_, '_, 'info, ExecuteProposal<'info>>,
) -> Result<()> {
instructions::execute_proposal(ctx)
}

Expand Down
9 changes: 3 additions & 6 deletions packages/svm/programs/settler/src/state/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ impl Proposal {
1 // bump
;

pub fn total_size(instructions: &Vec<ProposalInstruction>, fees_len: usize) -> Result<usize> {
pub fn total_size(instructions: &[ProposalInstruction], fees_len: usize) -> Result<usize> {
let size = add(8, Proposal::BASE_LEN)?;
let size = add(size, Proposal::instructions_size(instructions)?)?;
let size = add(size, Proposal::fees_size(fees_len)?)?;
Ok(size)
}

pub fn instructions_size(instructions: &Vec<ProposalInstruction>) -> Result<usize> {
pub fn instructions_size(instructions: &[ProposalInstruction]) -> Result<usize> {
let sum = instructions
.iter()
.try_fold(0usize, |acc, ix| add(acc, ix.size()))?;
Expand All @@ -43,10 +43,7 @@ impl Proposal {
add(4, mul(8, len)?)
}

pub fn extended_size(
size: usize,
more_instructions: &Vec<ProposalInstruction>,
) -> Result<usize> {
pub fn extended_size(size: usize, more_instructions: &[ProposalInstruction]) -> Result<usize> {
sub(
add(size, Proposal::instructions_size(more_instructions)?)?,
4,
Expand Down
3 changes: 3 additions & 0 deletions packages/svm/programs/settler/src/types/intent_data/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod transfer;

pub use transfer::*;
14 changes: 14 additions & 0 deletions packages/svm/programs/settler/src/types/intent_data/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use anchor_lang::prelude::{borsh::BorshDeserialize, *};

#[derive(BorshDeserialize)]
pub struct SvmTransfer {
pub token: Vec<u8>,
pub amount: u64,
pub recipient: Vec<u8>,
}

#[derive(BorshDeserialize)]
pub struct SvmTransferIntentData {
pub chain_id: u32,
pub transfers: Vec<SvmTransfer>,
}
2 changes: 2 additions & 0 deletions packages/svm/programs/settler/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub mod eip712_domain;
pub mod intent_data;
pub mod intent_event;
pub mod op_type;
pub mod token_fee;

pub use eip712_domain::*;
pub use intent_data::*;
pub use intent_event::*;
pub use op_type::*;
pub use token_fee::*;
131 changes: 131 additions & 0 deletions packages/svm/programs/settler/src/utils/execution/misc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use anchor_lang::prelude::*;
use anchor_spl::{
token,
token_2022::{self, TransferChecked},
token_interface::{self, Mint as IMint, TokenAccount as ITokenAccount},
};

use core::slice::Iter;

use crate::{
errors::SettlerError,
state::{Intent, Proposal},
types::OpType,
utils::handle_transfer,
};

pub fn handle_intent_execution<'info>(
intent: &Intent,
proposal: &Proposal,
delegate: &AccountInfo<'info>,
remaining_accounts_iter: &mut Iter<'_, AccountInfo<'info>>,
token_program: &AccountInfo<'info>,
token_2022_program: &AccountInfo<'info>,
delegate_bump: u8,
) -> Result<()> {
match intent.op {
OpType::Swap => err!(SettlerError::UnsupportedIntentOp),
OpType::Transfer => handle_transfer(
intent,
proposal,
delegate,
remaining_accounts_iter,
token_program,
token_2022_program,
delegate_bump,
),
OpType::EvmCall => err!(SettlerError::UnsupportedIntentOp),
OpType::SvmCall => err!(SettlerError::UnsupportedIntentOp),
}
}

/// Deserializes and checks the following remaining_accounts:
///
/// For each fee_token:
///
/// pub fee_token: Account<'info, IMint>,
///
/// #[account(
/// mut,
/// token::owner = solver,
/// token::mint = fee_token
/// )]
/// pub solver_ta: Account<'info, ITokenAccount>,
///
/// #[account(
/// mut,
/// token::owner = user,
/// token::mint = fee_token,
/// )]
/// pub user_ta: Account<'info, ITokenAccount>,
///
pub fn pay_solver_fees<'info>(
remaining_accounts_iter: &mut Iter<'_, AccountInfo<'info>>,
intent: &Intent,
proposal: &Proposal,
token_program: &AccountInfo<'info>,
token_2022_program: &AccountInfo<'info>,
delegate: &AccountInfo<'info>,
delegate_bump: u8,
) -> Result<()> {
let delegate_seeds: &[&[u8]] = &[b"delegate", intent.user.as_ref(), &[delegate_bump]];
let signer_seeds = [delegate_seeds];

for (fee, max_fee) in proposal.fees.iter().zip(&intent.max_fees) {
let token_account_info = next_account_info(remaining_accounts_iter)?;
let solver_ta_account_info = next_account_info(remaining_accounts_iter)?;
let user_ta_account_info = next_account_info(remaining_accounts_iter)?;

check_owner_is_token_program(token_account_info)?;
check_owner_is_token_program(user_ta_account_info)?;
check_owner_is_token_program(solver_ta_account_info)?;

let token_mint = {
let mut token_mint_data: &[u8] = &token_account_info.try_borrow_data()?;
IMint::try_deserialize(&mut token_mint_data)?
};

let user_ta = {
let mut user_ta_data: &[u8] = &user_ta_account_info.try_borrow_data()?;
ITokenAccount::try_deserialize(&mut user_ta_data)?
};

let solver_ta = {
let mut solver_ta_data: &[u8] = &solver_ta_account_info.try_borrow_data()?;
ITokenAccount::try_deserialize(&mut solver_ta_data)?
};

require_keys_eq!(token_account_info.key(), max_fee.token, SettlerError::IncorrectFeeToken);
require_keys_eq!(user_ta.owner, intent.user, SettlerError::IncorrectUserTokenAccount);
require_keys_eq!(user_ta.mint, max_fee.token, SettlerError::IncorrectUserTokenAccount);
require_keys_eq!(solver_ta.owner, proposal.creator, SettlerError::IncorrectSolverTokenAccount);
require_keys_eq!(solver_ta.mint, max_fee.token, SettlerError::IncorrectSolverTokenAccount);

// Construct transfer_checked CPI
let cpi_accounts = TransferChecked {
authority: delegate.clone(),
from: user_ta_account_info.clone(),
mint: token_account_info.clone(),
to: solver_ta_account_info.clone(),
};

let cpi_program = match *token_account_info.owner {
anchor_spl::token::ID => token_program.clone(),
anchor_spl::token_2022::ID => token_2022_program.clone(),
_ => err!(SettlerError::AccountNotOwnedByTokenProgram)?,
};

let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, &signer_seeds);
token_interface::transfer_checked(cpi_ctx, *fee, token_mint.decimals)?;
}

Ok(())
}

pub fn check_owner_is_token_program<'info>(account_info: &AccountInfo<'info>) -> Result<()> {
if *account_info.owner != token::ID && *account_info.owner != token_2022::ID {
err!(SettlerError::AccountNotOwnedByTokenProgram)?;
}

Ok(())
}
5 changes: 5 additions & 0 deletions packages/svm/programs/settler/src/utils/execution/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod misc;
pub mod transfer;

pub use misc::*;
pub use transfer::*;
Loading
Loading