Skip to content
Open
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
77 changes: 77 additions & 0 deletions packages/svm/programs/settler/src/instructions/add_axia_sig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use anchor_lang::{
prelude::{instruction::Instruction, sysvar::instructions::get_instruction_relative, *},
solana_program::sysvar::instructions::ID as IX_ID,
};

use crate::{
controller::{accounts::EntityRegistry, types::EntityType},
errors::SettlerError,
state::Proposal,
utils::{check_ed25519_ix, get_args_from_ed25519_ix_data, Ed25519Args},
};

#[derive(Accounts)]
pub struct AddAxiaSig<'info> {
#[account(mut)]
pub solver: Signer<'info>,

#[account(
seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()],
bump = solver_registry.bump,
seeds::program = crate::controller::ID,
)]
pub solver_registry: Box<Account<'info, EntityRegistry>>,

#[account(
seeds = [b"entity-registry", &[EntityType::Axia as u8 + 1], axia_registry.entity_pubkey.as_ref()],
bump = axia_registry.bump,
seeds::program = crate::controller::ID,
)]
pub axia_registry: Box<Account<'info, EntityRegistry>>,

/// CHECK: Any proposal
#[account(
mut,
constraint = proposal.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::ProposalIsExpired,
constraint = proposal.is_final @ SettlerError::ProposalIsNotFinal,
)]
pub proposal: Box<Account<'info, Proposal>>,

/// CHECK: The address check is needed because otherwise
/// the supplied Sysvar could be anything else.
#[account(address = IX_ID)]
pub ix_sysvar: AccountInfo<'info>,
}

pub fn add_axia_sig(ctx: Context<AddAxiaSig>) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;

// NOP if already signed
if proposal.is_signed {
return Ok(());
}

// Get Ed25519 instruction
let ed25519_ix: Instruction = get_instruction_relative(-1, &ctx.accounts.ix_sysvar)?;
let ed25519_ix_args: Ed25519Args = get_args_from_ed25519_ix_data(&ed25519_ix.data)?;

// Verify correct program and accounts
check_ed25519_ix(&ed25519_ix)?;

// Verify correct message was signed
require!(
ed25519_ix_args.msg == proposal.key().as_array(),
SettlerError::SigVerificationFailed
);

// Verify pubkey is whitelisted Axia
require!(
ed25519_ix_args.pubkey == &ctx.accounts.axia_registry.entity_pubkey.to_bytes(),
SettlerError::AxiaNotAllowlisted
);

// Updates proposal as signed
proposal.is_signed = true;

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ pub struct AddInstructionsToProposal<'info> {
realloc::zero = true,
has_one = creator @ SettlerError::IncorrectProposalCreator
)]
// Any proposal

/// Any proposal
#[account(
constraint = proposal.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::ProposalIsExpired,
constraint = !proposal.is_final @ SettlerError::ProposalIsFinal
)]
pub proposal: Box<Account<'info, Proposal>>,

pub system_program: Program<'info, System>,
Expand All @@ -29,12 +34,8 @@ pub fn add_instructions_to_proposal(
more_instructions: Vec<ProposalInstruction>,
finalize: bool,
) -> Result<()> {
let now = Clock::get()?.unix_timestamp as u64;
let proposal = &mut ctx.accounts.proposal;

require!(proposal.deadline > now, SettlerError::ProposalIsExpired);
require!(!proposal.is_final, SettlerError::ProposalIsFinal);

proposal.instructions.extend_from_slice(&more_instructions);

if finalize {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use anchor_lang::{
prelude::{instruction::Instruction, sysvar::instructions::get_instruction_relative, *},
solana_program::sysvar::instructions::ID as IX_ID,
};

use crate::{
controller::{accounts::EntityRegistry, types::EntityType},
errors::SettlerError,
state::Intent,
utils::{check_ed25519_ix, get_args_from_ed25519_ix_data, Ed25519Args},
};

#[derive(Accounts)]
pub struct AddValidatorSig<'info> {
#[account(mut)]
pub solver: Signer<'info>,

#[account(
seeds = [b"entity-registry", &[EntityType::Solver as u8 + 1], solver.key().as_ref()],
bump = solver_registry.bump,
seeds::program = crate::controller::ID,
)]
pub solver_registry: Box<Account<'info, EntityRegistry>>,

// Any Intent
#[account(
mut,
constraint = intent.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::IntentIsExpired,
constraint = intent.is_final @ SettlerError::IntentIsNotFinal
)]
pub intent: Box<Account<'info, Intent>>,

#[account(
seeds = [b"fulfilled-intent", intent.hash.as_ref()],
bump
)]
/// This PDA must be uninitialized
pub fulfilled_intent: SystemAccount<'info>,

/// CHECK: other checks in ix body
pub validator_registry: Box<Account<'info, EntityRegistry>>,

/// CHECK: The address check is needed because otherwise
/// the supplied Sysvar could be anything else.
#[account(address = IX_ID)]
pub ix_sysvar: AccountInfo<'info>,
}

pub fn add_validator_sig(ctx: Context<AddValidatorSig>) -> Result<()> {
let intent = &mut ctx.accounts.intent;

// Get Ed25519 instruction
let ed25519_ix: Instruction = get_instruction_relative(-1, &ctx.accounts.ix_sysvar)?;
let ed25519_ix_args: Ed25519Args = get_args_from_ed25519_ix_data(&ed25519_ix.data)?;

// Verify correct program and accounts
check_ed25519_ix(&ed25519_ix)?;

// Verify correct message was signed
require!(
ed25519_ix_args.msg == intent.hash,
SettlerError::SigVerificationFailed
);

// Verify pubkey is a whitelisted Validator
require_keys_eq!(
ctx.accounts.validator_registry.key(),
Pubkey::create_program_address(
&[
b"entity-registry",
&[EntityType::Validator as u8 + 1],
ed25519_ix_args.pubkey,
&[ctx.accounts.validator_registry.bump]
],
&crate::controller::ID,
)
.map_err(|_| SettlerError::ValidatorNotAllowlisted)?,
SettlerError::ValidatorNotAllowlisted,
);

// Updates intent PDA if signature not present and min_validations not met

if intent.validators.len() == intent.min_validations as usize {
return Ok(());
}

let ed25519_pubkey = Pubkey::try_from_slice(ed25519_ix_args.pubkey)?;

if intent.validators.contains(&ed25519_pubkey) {
return Ok(());
}

intent.validators.push(ed25519_pubkey);

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct ChangeControllerProgram {}

pub fn change_controller_program(ctx: Context<ChangeControllerProgram>) -> Result<()> {
// TODO: check against crate::controller::ID
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct ClaimStaleIntent<'info> {
mut,
close = creator,
has_one = creator @ SettlerError::IncorrectIntentCreator,
constraint = Clock::get()?.unix_timestamp as u64 > intent.deadline @ SettlerError::IntentNotYetExpired
constraint = intent.deadline < Clock::get()?.unix_timestamp as u64 @ SettlerError::IntentNotYetExpired
)]
pub intent: Box<Account<'info, Intent>>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct ClaimStaleProposal<'info> {
mut,
close = creator,
has_one = creator @ SettlerError::IncorrectProposalCreator,
constraint = Clock::get()?.unix_timestamp as u64 > proposal.deadline @ SettlerError::ProposalNotYetExpired
constraint = proposal.deadline < Clock::get()?.unix_timestamp as u64 @ SettlerError::ProposalNotYetExpired
)]
pub proposal: Box<Account<'info, Proposal>>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct ExecuteProposal<'info> {
has_one = intent @ SettlerError::IncorrectIntentForProposal,
constraint = proposal.creator == proposal_creator.key() @ SettlerError::IncorrectProposalCreator,
constraint = proposal.is_signed @ SettlerError::ProposalIsNotSigned,
constraint = proposal.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::ProposalIsExpired,
close = proposal_creator
)]
pub proposal: Box<Account<'info, Proposal>>,
Expand Down Expand Up @@ -56,12 +57,8 @@ pub struct ExecuteProposal<'info> {
}

pub fn execute_proposal(ctx: Context<ExecuteProposal>) -> Result<()> {
let now = Clock::get()?.unix_timestamp as u64;
let proposal = &ctx.accounts.proposal;
let intent = &ctx.accounts.intent;

require!(proposal.deadline > now, SettlerError::ProposalIsExpired);

// TODO: Execute proposal

// TODO: Validate execution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct ExtendIntent<'info> {
mut,
has_one = creator @ SettlerError::IncorrectIntentCreator,
constraint = !intent.is_final @ SettlerError::IntentIsFinal,
constraint = intent.deadline > Clock::get()?.unix_timestamp as u64 @ SettlerError::IntentIsExpired,
realloc =
Intent::extended_size(intent.to_account_info().data_len(), &more_data, &more_max_fees, &more_events)?,
realloc::payer = creator,
Expand Down
6 changes: 6 additions & 0 deletions packages/svm/programs/settler/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub mod add_axia_sig;
pub mod add_instructions_to_proposal;
pub mod add_validator_sig;
pub mod change_controller_program;
pub mod claim_stale_intent;
pub mod claim_stale_proposal;
pub mod create_intent;
Expand All @@ -7,7 +10,10 @@ pub mod execute_proposal;
pub mod extend_intent;
pub mod initialize;

pub use add_axia_sig::*;
pub use add_instructions_to_proposal::*;
pub use add_validator_sig::*;
pub use change_controller_program::*;
pub use claim_stale_intent::*;
pub use claim_stale_proposal::*;
pub use create_intent::*;
Expand Down
12 changes: 12 additions & 0 deletions packages/svm/programs/settler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ use crate::{instructions::*, state::*, types::*};
pub mod settler {
use super::*;

pub fn add_axia_sig(ctx: Context<AddAxiaSig>) -> Result<()> {
instructions::add_axia_sig(ctx)
}

pub fn add_instructions_to_proposal(
ctx: Context<AddInstructionsToProposal>,
more_instructions: Vec<ProposalInstruction>,
Expand All @@ -24,6 +28,14 @@ pub mod settler {
instructions::add_instructions_to_proposal(ctx, more_instructions, finalize)
}

pub fn add_validator_sig(ctx: Context<AddValidatorSig>) -> Result<()> {
instructions::add_validator_sig(ctx)
}

pub fn change_controller_program(ctx: Context<ChangeControllerProgram>) -> Result<()> {
instructions::change_controller_program(ctx)
}

pub fn claim_stale_intent(ctx: Context<ClaimStaleIntent>) -> Result<()> {
instructions::claim_stale_intent(ctx)
}
Expand Down
2 changes: 2 additions & 0 deletions packages/svm/programs/settler/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod math;
pub mod sigs;

pub use math::*;
pub use sigs::*;
75 changes: 75 additions & 0 deletions packages/svm/programs/settler/src/utils/sigs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use anchor_lang::prelude::{instruction::Instruction, *};

use crate::errors::SettlerError;

pub fn check_ed25519_ix(ix: &Instruction) -> Result<()> {
if ix.program_id.to_string() != "Ed25519SigVerify111111111111111111111111111"
|| ix.accounts.len() != 0
{
return err!(SettlerError::SigVerificationFailed);
}

Ok(())
}

pub struct Ed25519Args<'a> {
pub pubkey: &'a [u8; 32],
pub sig: &'a [u8; 64],
pub msg: &'a [u8],
}

pub fn get_args_from_ed25519_ix_data(data: &[u8]) -> Result<Ed25519Args<'_>> {
if data.len() < 112 {
return err!(SettlerError::SigVerificationFailed);
}

// Header
let num_signatures = &[data[0]];
let padding = &[data[1]];
let signature_offset = &data[2..=3];
let signature_instruction_index = &data[4..=5];
let public_key_offset = &data[6..=7];
let public_key_instruction_index = &data[8..=9];
let message_data_offset = &data[10..=11];
let message_data_size = &data[12..=13];
let message_instruction_index = &data[14..=15];

// Data
let pubkey = &data[16..16 + 32];
let sig = &data[48..48 + 64];
let msg = &data[112..];

// Expected values
let exp_public_key_offset: u16 = 16; // 2*u8 + 7*u16
let exp_signature_offset: u16 = exp_public_key_offset + 32_u16;
let exp_message_data_offset: u16 = exp_signature_offset + 64_u16;
let exp_num_signatures: u8 = 1;
let exp_message_data_size: u16 = msg
.len()
.try_into()
.map_err(|_| SettlerError::SigVerificationFailed)?;

// Header
if num_signatures != &exp_num_signatures.to_le_bytes()
|| padding != &[0]
|| signature_offset != &exp_signature_offset.to_le_bytes()
|| signature_instruction_index != &u16::MAX.to_le_bytes()
|| public_key_offset != &exp_public_key_offset.to_le_bytes()
|| public_key_instruction_index != &u16::MAX.to_le_bytes()
|| message_data_offset != &exp_message_data_offset.to_le_bytes()
|| message_data_size != &exp_message_data_size.to_le_bytes()
|| message_instruction_index != &u16::MAX.to_le_bytes()
{
return err!(SettlerError::SigVerificationFailed);
}

Ok(Ed25519Args {
pubkey: pubkey
.try_into()
.map_err(|_| SettlerError::SigVerificationFailed)?,
sig: sig
.try_into()
.map_err(|_| SettlerError::SigVerificationFailed)?,
msg,
})
}
Loading