Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1e30ef3
Define official master agreement naming and Step 3 dashboard
Jaymyong66 Apr 26, 2026
99b093d
Store validated master agreement names on creation
Jaymyong66 Apr 26, 2026
2c730a0
Allow leader and operator to rename master agreements
Jaymyong66 Apr 26, 2026
6d28561
Sync the exported TS IDL with master agreement rename metadata
Jaymyong66 Apr 26, 2026
33b18e2
Keep master agreement names consistent across create and rename paths
Jaymyong66 Apr 26, 2026
fe9732b
Surface master agreement names from on-chain accounts
Jaymyong66 Apr 26, 2026
b7fb783
Add official master agreement naming to the workbench
Jaymyong66 Apr 26, 2026
7243706
Restore operator role flow for master agreement selection
Jaymyong66 Apr 26, 2026
4f5189b
Keep official names visible before backend refresh catches up
Jaymyong66 Apr 26, 2026
a766320
Prefer optimistic names across review and reconciliation UI
Jaymyong66 Apr 26, 2026
f1d04ee
Return explicit refresh results for master agreement reconciliation
Jaymyong66 Apr 26, 2026
8405218
Turn Step 3 into a master activation dashboard
Jaymyong66 Apr 26, 2026
9d1518c
Route leader pool label through agreement naming state
Jaymyong66 Apr 26, 2026
26db9d0
Restore Step 3 activation control and guard unresolved money snapshots
Jaymyong66 Apr 26, 2026
15fe4bb
Keep Step 3 actionable when live balances are unresolved
Jaymyong66 Apr 26, 2026
0976399
Prevent Step 3 from treating missing live data as authoritative deficits
Jaymyong66 Apr 26, 2026
c102940
Keep Step 3 as the only master activation surface
Jaymyong66 Apr 26, 2026
f03e2fa
Keep activation guidance and CTA readiness aligned with the Step 3 flow
Jaymyong66 Apr 26, 2026
e8a3559
Prevent duplicate master-agreement subscriptions on activation dashboard
Jaymyong66 Apr 26, 2026
42cedc8
Preserve the Step 2 to Step 3 activation boundary
Jaymyong66 Apr 26, 2026
0056142
Gate on-chain activation by the configured operator
Jaymyong66 Apr 26, 2026
3a9a069
Let authoritative master names replace stale optimistic labels
Jaymyong66 Apr 26, 2026
656222e
Apply rustfmt to master agreement rename tests
Jaymyong66 Apr 27, 2026
cfc0f07
Keep legacy master agreements readable in backend
Jaymyong66 Apr 27, 2026
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 change: 1 addition & 0 deletions backend/src/api/service/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ fn master_agreement(
MasterAgreementInfo {
pubkey: pubkey.to_string(),
master_id: 1,
name: "test master agreement".to_string(),
leader: leader.to_string(),
operator: leader.to_string(),
currency_mint: Pubkey::new_unique().to_string(),
Expand Down
1 change: 1 addition & 0 deletions backend/src/db/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ fn master_agreement(pubkey: &str, premium_per_policy: u64) -> MasterAgreementInf
MasterAgreementInfo {
pubkey: pubkey.to_string(),
master_id: 1,
name: "test master agreement".to_string(),
leader: "leader-1".to_string(),
operator: "leader-1".to_string(),
currency_mint: Pubkey::new_unique().to_string(),
Expand Down
1 change: 1 addition & 0 deletions backend/src/events/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn master_agreement(pubkey: &str, status: u8) -> MasterAgreementInfo {
MasterAgreementInfo {
pubkey: pubkey.to_string(),
master_id: 1,
name: "test master agreement".to_string(),
leader: "leader-1".to_string(),
operator: "leader-1".to_string(),
currency_mint: Pubkey::new_unique().to_string(),
Expand Down
38 changes: 38 additions & 0 deletions backend/src/oracle/program_accounts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub struct MasterAgreementParticipantInfo {
pub struct MasterAgreementInfo {
pub pubkey: String,
pub master_id: u64,
pub name: String,
pub leader: String,
pub operator: String,
pub currency_mint: String,
Expand Down Expand Up @@ -132,9 +133,45 @@ fn scan_accounts<T>(
}

fn parse_master_agreement(pubkey: &Pubkey, data: &[u8]) -> Result<MasterAgreementInfo> {
match parse_master_agreement_with_name(pubkey, data) {
Ok(info) => Ok(info),
Err(name_layout_err) => {
let legacy = parse_master_agreement_legacy(pubkey, data);
match legacy {
Ok(info) => {
tracing::info!(
"[program_accounts] legacy MasterAgreement 레이아웃으로 파싱 성공 {pubkey}"
);
Ok(info)
}
Err(legacy_err) => Err(name_layout_err)
.with_context(|| format!("legacy 레이아웃 파싱도 실패: {legacy_err}")),
}
}
}
}

fn parse_master_agreement_with_name(pubkey: &Pubkey, data: &[u8]) -> Result<MasterAgreementInfo> {
let mut offset = 8usize;

let master_id = read_u64(data, &mut offset)?;
let name = read_string(data, &mut offset)?;
parse_master_agreement_from_offset(pubkey, data, offset, master_id, name)
}

fn parse_master_agreement_legacy(pubkey: &Pubkey, data: &[u8]) -> Result<MasterAgreementInfo> {
let mut offset = 8usize;
let master_id = read_u64(data, &mut offset)?;
parse_master_agreement_from_offset(pubkey, data, offset, master_id, String::new())
}

fn parse_master_agreement_from_offset(
pubkey: &Pubkey,
data: &[u8],
mut offset: usize,
master_id: u64,
name: String,
) -> Result<MasterAgreementInfo> {
let leader = read_pubkey(data, &mut offset)?;
let operator = read_pubkey(data, &mut offset)?;
let currency_mint = read_pubkey(data, &mut offset)?;
Expand Down Expand Up @@ -164,6 +201,7 @@ fn parse_master_agreement(pubkey: &Pubkey, data: &[u8]) -> Result<MasterAgreemen
Ok(MasterAgreementInfo {
pubkey: pubkey.to_string(),
master_id,
name,
leader: leader.to_string(),
operator: operator.to_string(),
currency_mint: currency_mint.to_string(),
Expand Down
98 changes: 97 additions & 1 deletion backend/src/oracle/program_accounts/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn build_master_agreement_bytes() -> (Pubkey, Vec<u8>, Vec<Pubkey>) {

let mut data = anchor_account_discriminator("MasterAgreement").to_vec();
data.extend_from_slice(&7u64.to_le_bytes());
push_string(&mut data, "대한-뉴욕 2026 리더 공동계약");
push_pubkey(&mut data, &leader);
push_pubkey(&mut data, &operator);
push_pubkey(&mut data, &currency_mint);
Expand Down Expand Up @@ -81,6 +82,77 @@ fn build_master_agreement_bytes() -> (Pubkey, Vec<u8>, Vec<Pubkey>) {
)
}

fn build_legacy_master_agreement_bytes() -> (Pubkey, Vec<u8>, Vec<Pubkey>) {
let pubkey = Pubkey::new_unique();
let leader = Pubkey::new_unique();
let operator = Pubkey::new_unique();
let currency_mint = Pubkey::new_unique();
let reinsurer = Pubkey::new_unique();
let reinsurer_pool_wallet = Pubkey::new_unique();
let reinsurer_deposit_wallet = Pubkey::new_unique();
let leader_pool_wallet = Pubkey::new_unique();
let leader_deposit_wallet = Pubkey::new_unique();
let participant_insurer = Pubkey::new_unique();
let participant_pool_wallet = Pubkey::new_unique();
let participant_deposit_wallet = Pubkey::new_unique();
let oracle_feed = Pubkey::new_unique();

let mut data = anchor_account_discriminator("MasterAgreement").to_vec();
data.extend_from_slice(&7u64.to_le_bytes());
push_pubkey(&mut data, &leader);
push_pubkey(&mut data, &operator);
push_pubkey(&mut data, &currency_mint);
data.extend_from_slice(&100i64.to_le_bytes());
data.extend_from_slice(&200i64.to_le_bytes());
data.extend_from_slice(&1_000u64.to_le_bytes());
data.extend_from_slice(&100u64.to_le_bytes());
data.extend_from_slice(&200u64.to_le_bytes());
data.extend_from_slice(&300u64.to_le_bytes());
data.extend_from_slice(&400u64.to_le_bytes());
data.extend_from_slice(&5_000u16.to_le_bytes());
data.extend_from_slice(&1_100u16.to_le_bytes());
data.extend_from_slice(&220u16.to_le_bytes());
data.extend_from_slice(&880u16.to_le_bytes());
data.push(1);
push_pubkey(&mut data, &reinsurer);
data.push(1);
data.push(1);
push_pubkey(&mut data, &reinsurer_pool_wallet);
data.push(1);
push_pubkey(&mut data, &reinsurer_deposit_wallet);
push_pubkey(&mut data, &leader_pool_wallet);
push_pubkey(&mut data, &leader_deposit_wallet);
data.extend_from_slice(&1u32.to_le_bytes());
push_pubkey(&mut data, &participant_insurer);
data.extend_from_slice(&5_000u16.to_le_bytes());
data.push(1);
push_pubkey(&mut data, &participant_pool_wallet);
push_pubkey(&mut data, &participant_deposit_wallet);
push_pubkey(&mut data, &oracle_feed);
data.push(2);
data.extend_from_slice(&777i64.to_le_bytes());
data.push(254);

(
pubkey,
data,
vec![
leader,
operator,
currency_mint,
reinsurer,
reinsurer_pool_wallet,
reinsurer_deposit_wallet,
leader_pool_wallet,
leader_deposit_wallet,
participant_insurer,
participant_pool_wallet,
participant_deposit_wallet,
oracle_feed,
],
)
}

fn build_flight_policy_bytes(status: u8) -> (Pubkey, Vec<u8>, Pubkey, Pubkey) {
let pubkey = Pubkey::new_unique();
let master = Pubkey::new_unique();
Expand Down Expand Up @@ -115,6 +187,7 @@ fn parse_master_agreement_parses_full_account_data() {

assert_eq!(agreement.pubkey, pubkey.to_string());
assert_eq!(agreement.master_id, 7);
assert_eq!(agreement.name, "대한-뉴욕 2026 리더 공동계약");
assert_eq!(agreement.leader, keys[0].to_string());
assert_eq!(agreement.operator, keys[1].to_string());
assert_eq!(agreement.currency_mint, keys[2].to_string());
Expand Down Expand Up @@ -146,6 +219,23 @@ fn parse_master_agreement_parses_full_account_data() {
assert_eq!(agreement.created_at, 777);
}

#[test]
fn parse_master_agreement_parses_legacy_account_data_without_name() {
let (pubkey, data, keys) = build_legacy_master_agreement_bytes();

let agreement = parse_master_agreement(&pubkey, &data).unwrap();

assert_eq!(agreement.pubkey, pubkey.to_string());
assert_eq!(agreement.master_id, 7);
assert_eq!(agreement.name, "");
assert_eq!(agreement.leader, keys[0].to_string());
assert_eq!(agreement.operator, keys[1].to_string());
assert_eq!(agreement.currency_mint, keys[2].to_string());
assert_eq!(agreement.participants.len(), 1);
assert_eq!(agreement.oracle_feed, keys[11].to_string());
assert_eq!(agreement.status, 2);
}

#[test]
fn parse_flight_policy_parses_full_account_data() {
let (pubkey, data, master, creator) = build_flight_policy_bytes(4);
Expand Down Expand Up @@ -188,5 +278,11 @@ fn parse_master_agreement_fails_on_truncated_data() {

let error = parse_master_agreement(&pubkey, &data).unwrap_err();

assert!(error.to_string().contains("읽기 실패") || error.to_string().contains("범위 초과"));
let message = error.to_string();
assert!(
message.contains("읽기 실패")
|| message.contains("범위 초과")
|| message.contains("legacy 레이아웃 파싱도 실패"),
"unexpected error: {message}"
);
}
4 changes: 2 additions & 2 deletions contract/programs/open_parametric/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pub const ORACLE_MAX_STALENESS_SLOTS: u64 = 150; // approx 60-90s depending on c
pub const MAX_ROUTE_LEN: usize = 16;
pub const MAX_FLIGHT_NO_LEN: usize = 16;
pub const MAX_MASTER_PARTICIPANTS: usize = 5;
pub const MASTER_AGREEMENT_NAME_MAX_LEN: usize = 40;
pub const MAX_SUBSCRIBER_REF_LEN: usize = 64;

// oracle_feed(32 bytes) 추가됐지만 4096 버퍼로 충분.
pub const MASTER_POLICY_SPACE: usize = 4096;
pub const MASTER_POLICY_SPACE: usize = 8 + 1024 + 4 + MASTER_AGREEMENT_NAME_MAX_LEN * 4;
pub const FLIGHT_POLICY_SPACE: usize = 1024;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use crate::errors::OpenParamError;
use crate::math::effective_reinsurer_bps;
use crate::state::*;

use super::master_agreement_name::normalize_master_agreement_name;

#[derive(Accounts)]
#[instruction(params: CreateMasterAgreementParams)]
pub struct CreateMasterAgreement<'info> {
Expand Down Expand Up @@ -37,6 +39,7 @@ pub fn handler(
ctx: Context<CreateMasterAgreement>,
params: CreateMasterAgreementParams,
) -> Result<()> {
let normalized_name = normalize_master_agreement_name(&params.name)?;
let master = &mut ctx.accounts.master_agreement;
let has_reinsurer = params.ceded_ratio_bps > 0;

Expand Down Expand Up @@ -73,6 +76,7 @@ pub fn handler(
effective_reinsurer_bps(params.ceded_ratio_bps, params.reins_commission_bps)?;

master.master_id = params.master_id;
master.name = normalized_name;
master.leader = ctx.accounts.leader.key();
master.operator = ctx.accounts.operator.key();
master.currency_mint = ctx.accounts.currency_mint.key();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use crate::constants::MAX_MASTER_PARTICIPANTS;
use crate::errors::OpenParamError;
use crate::state::MasterParticipantInit;

use super::create_master_agreement::{validate_create_master_inputs, validate_master_participants};
use super::{
create_master_agreement::{validate_create_master_inputs, validate_master_participants},
master_agreement_name::normalize_master_agreement_name,
};

#[test]
fn master_participants_require_10000_bps_with_separate_leader_share() {
Expand Down Expand Up @@ -179,3 +182,22 @@ fn accepts_collateral_claim_count_between_1_and_100() {

assert!(result.is_ok());
}

#[test]
fn accepts_trimmed_name_within_40_chars() {
let normalized = normalize_master_agreement_name(" 2026 인천-뉴욕 공동계약 ").unwrap();
assert_eq!(normalized, "2026 인천-뉴욕 공동계약");
}

#[test]
fn rejects_blank_master_agreement_name() {
let error = normalize_master_agreement_name(" ").unwrap_err();
assert!(matches!(error, OpenParamError::InvalidInput));
}

#[test]
fn rejects_master_agreement_name_longer_than_40_chars() {
let error =
normalize_master_agreement_name("12345678901234567890123456789012345678901").unwrap_err();
assert!(matches!(error, OpenParamError::InvalidInput));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::constants::MASTER_AGREEMENT_NAME_MAX_LEN;
use crate::errors::OpenParamError;

pub(crate) fn normalize_master_agreement_name(
name: &str,
) -> std::result::Result<String, OpenParamError> {
let normalized = name.trim();
if normalized.is_empty() || normalized.chars().count() > MASTER_AGREEMENT_NAME_MAX_LEN {
return Err(OpenParamError::InvalidInput);
}
Ok(normalized.to_string())
}
6 changes: 6 additions & 0 deletions contract/programs/open_parametric/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ pub mod confirm_master;
pub mod create_flight_policy_from_master;
pub mod create_master_agreement;
pub mod fund_pool;
pub(crate) mod master_agreement_name;
pub mod register_participant_wallets;
pub mod resolve_flight_delay;
pub mod settle_flight_claim;
pub mod settle_flight_no_claim;
pub mod update_master_agreement_name;

// 인스트럭션별 단위 테스트 모듈
#[cfg(test)]
Expand All @@ -31,6 +33,8 @@ mod resolve_flight_delay_test;
mod settle_flight_claim_test;
#[cfg(test)]
mod settle_flight_no_claim_test;
#[cfg(test)]
mod update_master_agreement_name_test;

#[allow(ambiguous_glob_reexports)]
pub use activate_master::*;
Expand All @@ -52,3 +56,5 @@ pub use resolve_flight_delay::*;
pub use settle_flight_claim::*;
#[allow(ambiguous_glob_reexports)]
pub use settle_flight_no_claim::*;
#[allow(ambiguous_glob_reexports)]
pub use update_master_agreement_name::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use anchor_lang::prelude::*;

use crate::errors::OpenParamError;
use crate::state::*;

use super::master_agreement_name::normalize_master_agreement_name;

#[derive(Accounts)]
pub struct UpdateMasterAgreementName<'info> {
pub signer: Signer<'info>,
#[account(mut)]
pub master_agreement: Account<'info, MasterAgreement>,
}

pub(crate) fn assert_can_rename_master_agreement(
leader: Pubkey,
operator: Pubkey,
signer: Pubkey,
) -> std::result::Result<(), OpenParamError> {
if signer != leader && signer != operator {
return Err(OpenParamError::Unauthorized);
}

Ok(())
}

pub(crate) fn apply_master_agreement_name_update(
master: &mut MasterAgreement,
signer: Pubkey,
name: &str,
) -> std::result::Result<(), OpenParamError> {
assert_can_rename_master_agreement(master.leader, master.operator, signer)?;
master.name = normalize_master_agreement_name(name)?;

Ok(())
}

pub fn handler(ctx: Context<UpdateMasterAgreementName>, name: String) -> Result<()> {
let master = &mut ctx.accounts.master_agreement;
apply_master_agreement_name_update(master, ctx.accounts.signer.key(), &name)?;
Ok(())
}
Loading
Loading