ido: sync IdoParams commitment to 127-byte layout, enforce NFT-pinned min PLP

libriften 0941c88 grew the ORB IdoParams NFT commitment 121->127 bytes,
inserting minPlpAfterDiscount/minPlpShare (4-byte pIntLE numerators over
PERMANENT_LIQUIDITY_SHARE_DENOMINATOR) between maxExpireDuration and
entryExecutionFee, and shrinking paramsUseFee 4->2 bytes.

- Parse the two new fields; shift entryExecutionFee/createExecutionFee offsets.
- Drop the hardcoded MIN_PLP_SHARE / MIN_PLP_AFTER_DISCOUNT statics; enforce
  both minimums from the NFT commitment inside the version-verified block
  (permanentLiquidityShareNumerator >= minPlpShare, and
  maxDiscountRateNumerator + minPlpAfterDiscount <= permanentLiquidityShareNumerator).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hossein Zoda 2026-07-13 11:22:41 +00:00
parent ca7f829591
commit c8450e210b

View file

@ -70,11 +70,12 @@ const OFFERING_ENTRY_FLAG_XWNT: u8 = 0x20; // exchange with native token (legacy
static PERMANENT_LIQUIDITY_SHARE_DENOMINATOR: LazyLock<Integer> = static PERMANENT_LIQUIDITY_SHARE_DENOMINATOR: LazyLock<Integer> =
LazyLock::new(|| Integer::from(100_000_000i64)); LazyLock::new(|| Integer::from(100_000_000i64));
static MIN_PLP_SHARE: LazyLock<Integer> = LazyLock::new(|| Integer::from(20_000_000i64)); // 20%
static MAX_PLP_SHARE: LazyLock<Integer> = LazyLock::new(|| Integer::from(80_000_000i64)); // 80% static MAX_PLP_SHARE: LazyLock<Integer> = LazyLock::new(|| Integer::from(80_000_000i64)); // 80%
static MAX_PLP_ANNUAL_DISCOUNT: LazyLock<Integer> = LazyLock::new(|| Integer::from(10_000_000i64)); // 10% static MAX_PLP_ANNUAL_DISCOUNT: LazyLock<Integer> = LazyLock::new(|| Integer::from(10_000_000i64)); // 10%
static MIN_PLP_ANNUAL_DISCOUNT: LazyLock<Integer> = LazyLock::new(|| Integer::from(0i64)); // 0% static MIN_PLP_ANNUAL_DISCOUNT: LazyLock<Integer> = LazyLock::new(|| Integer::from(0i64)); // 0%
static MIN_PLP_AFTER_DISCOUNT: LazyLock<Integer> = LazyLock::new(|| Integer::from(5_000_000i64)); // 5% // The minimum PLP share and minimum PLP-after-discount are no longer hardcoded:
// they are pinned per-IdoParams NFT (minPlpShare / minPlpAfterDiscount, numerators
// over PERMANENT_LIQUIDITY_SHARE_DENOMINATOR) and enforced against the offering.
// The ORB IdoParams NFT category (display byte order, like a token id). Every // The ORB IdoParams NFT category (display byte order, like a token id). Every
// preinit must include this NFT (input #1, preserved at output #10); its // preinit must include this NFT (input #1, preserved at output #10); its
@ -141,7 +142,7 @@ static REBUILD_IPFS_PLACEHOLDER_BCMR_FOR_GENESIS_WITH_AUTHGUARD_CONTRACT: LazyLo
hex::decode("0902a9147ca97e01877e57897c6b00c08851d100887c635ab2756d51cd016a88674d0301404142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f6b007c8253a26365537f7c76011299527f77816c766b7c7f77517f75785c99527f77013f84816c766b7c7f77517f757e785699527f77013f84816c766b7c7f77517f757e7c527f77013f84816c766b7c7f77517f757e7b7c7e7c82539f666882760087636d677d537c94007c807e76011299527f77816c766b7c7f77517f75785c99527f77013f84816c766b7c7f77517f757e785699527f77013f84816c766b7c7f77517f757e7c527f77013f84816c766b7c7f77517f757e7c51937f757e686c7554893a10303132333435363738396162636465667c006b65517f7c5279785f84817f77517f756c7e6b52797c5499817f77517f756c7e6b82009c666d6c5689097f7b827b7c7f777e7e538978a8886b518ac0ce568a65766c537a538a6b74519c66756c766ba804015512207c7e548a01757c7e6b528a6c7c6b65766c537a538a6b74519c66756c6ca87c7e076a0442434d52207c7e51cd886801206c0f51ce8851d0009d6300cdc0c78868517e7e578a00cd8800ce00d18800d2008800d000d38800c600cca26952d10088c45387").unwrap() hex::decode("0902a9147ca97e01877e57897c6b00c08851d100887c635ab2756d51cd016a88674d0301404142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f6b007c8253a26365537f7c76011299527f77816c766b7c7f77517f75785c99527f77013f84816c766b7c7f77517f757e785699527f77013f84816c766b7c7f77517f757e7c527f77013f84816c766b7c7f77517f757e7b7c7e7c82539f666882760087636d677d537c94007c807e76011299527f77816c766b7c7f77517f75785c99527f77013f84816c766b7c7f77517f757e785699527f77013f84816c766b7c7f77517f757e7c527f77013f84816c766b7c7f77517f757e7c51937f757e686c7554893a10303132333435363738396162636465667c006b65517f7c5279785f84817f77517f756c7e6b52797c5499817f77517f756c7e6b82009c666d6c5689097f7b827b7c7f777e7e538978a8886b518ac0ce568a65766c537a538a6b74519c66756c766ba804015512207c7e548a01757c7e6b528a6c7c6b65766c537a538a6b74519c66756c6ca87c7e076a0442434d52207c7e51cd886801206c0f51ce8851d0009d6300cdc0c78868517e7e578a00cd8800ce00d18800d2008800d000d38800c600cca26952d10088c45387").unwrap()
}); });
/// The 121-byte commitment of the ORB IdoParams NFT (libriften /// The 127-byte commitment of the ORB IdoParams NFT (libriften
/// packages/cauldron/src/orb/v0). Byte layout: /// packages/cauldron/src/orb/v0). Byte layout:
/// [0] version (must equal IDO_PARAMS_VERSION) /// [0] version (must equal IDO_PARAMS_VERSION)
/// [1..33] orbPoolParamsCategory /// [1..33] orbPoolParamsCategory
@ -150,12 +151,14 @@ static REBUILD_IPFS_PLACEHOLDER_BCMR_FOR_GENESIS_WITH_AUTHGUARD_CONTRACT: LazyLo
/// [97..101] platformFee (pIntLE, raw-equal to platformFeeNumerator) /// [97..101] platformFee (pIntLE, raw-equal to platformFeeNumerator)
/// [101..105] minExpireDuration (pIntLE, seconds) /// [101..105] minExpireDuration (pIntLE, seconds)
/// [105..109] maxExpireDuration (pIntLE, seconds) /// [105..109] maxExpireDuration (pIntLE, seconds)
/// [109..113] entryExecutionFee (pIntLE, sats) /// [109..113] minPlpAfterDiscount (pIntLE, numerator / PERMANENT_LIQUIDITY_SHARE_DENOMINATOR)
/// [113..117] createExecutionFee (pIntLE, sats) /// [113..117] minPlpShare (pIntLE, numerator / PERMANENT_LIQUIDITY_SHARE_DENOMINATOR)
/// [117..121] paramsUseFee (pIntLE, sats) /// [117..121] entryExecutionFee (pIntLE, sats)
/// [121..125] createExecutionFee (pIntLE, sats)
/// [125..127] paramsUseFee (pIntLE, sats/100 — not parsed here)
/// Categories and the nfth are stored in the same byte order the announcement /// Categories and the nfth are stored in the same byte order the announcement
/// uses, so equality checks against announced parameters are direct compares. /// uses, so equality checks against announced parameters are direct compares.
const IDO_PARAMS_COMMITMENT_SIZE: usize = 121; const IDO_PARAMS_COMMITMENT_SIZE: usize = 127;
/// The only ORB IdoParams NFT commitment version this indexer understands /// The only ORB IdoParams NFT commitment version this indexer understands
/// (libriften `ORB_IDO_PARAMS_VERSION`). Any other value means the params NFT /// (libriften `ORB_IDO_PARAMS_VERSION`). Any other value means the params NFT
@ -170,6 +173,8 @@ pub struct IdoParamsNftCommitment {
pub platformFee: Integer, pub platformFee: Integer,
pub minExpireDuration: Integer, pub minExpireDuration: Integer,
pub maxExpireDuration: Integer, pub maxExpireDuration: Integer,
pub minPlpAfterDiscount: Integer,
pub minPlpShare: Integer,
pub entryExecutionFee: Integer, pub entryExecutionFee: Integer,
pub createExecutionFee: Integer, pub createExecutionFee: Integer,
} }
@ -190,8 +195,10 @@ fn parse_ido_params_nft_commitment(commitment: &[u8]) -> Result<IdoParamsNftComm
platformFee: vm_number_to_bigint(&commitment[97..101]), platformFee: vm_number_to_bigint(&commitment[97..101]),
minExpireDuration: vm_number_to_bigint(&commitment[101..105]), minExpireDuration: vm_number_to_bigint(&commitment[101..105]),
maxExpireDuration: vm_number_to_bigint(&commitment[105..109]), maxExpireDuration: vm_number_to_bigint(&commitment[105..109]),
entryExecutionFee: vm_number_to_bigint(&commitment[109..113]), minPlpAfterDiscount: vm_number_to_bigint(&commitment[109..113]),
createExecutionFee: vm_number_to_bigint(&commitment[113..117]), minPlpShare: vm_number_to_bigint(&commitment[113..117]),
entryExecutionFee: vm_number_to_bigint(&commitment[117..121]),
createExecutionFee: vm_number_to_bigint(&commitment[121..125]),
}) })
} }
@ -1566,13 +1573,6 @@ fn parse_ido_preinit_tx_params(
} }
} }
if preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE {
invalid_ido_reasons.push(anyhow::anyhow!(
"preinit_parameters.permanentLiquidityShareNumerator < *MIN_PLP_SHARE"
));
is_valid_ido = false;
}
if preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE { if preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE {
invalid_ido_reasons.push(anyhow::anyhow!( invalid_ido_reasons.push(anyhow::anyhow!(
"preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE" "preinit_parameters.permanentLiquidityShareNumerator > *MAX_PLP_SHARE"
@ -1602,13 +1602,6 @@ fn parse_ido_preinit_tx_params(
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.maxDiscountRateNumerator < Integer::from(0)")); invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.maxDiscountRateNumerator < Integer::from(0)"));
is_valid_ido = false; is_valid_ido = false;
} }
if &preinit_parameters.offering.offer.maxDiscountRateNumerator
+ &MIN_PLP_AFTER_DISCOUNT.clone()
> preinit_parameters.permanentLiquidityShareNumerator
{
invalid_ido_reasons.push(anyhow::anyhow!("preinit_parameters.offering.offer.maxDiscountRateNumerator + MIN_PLP_AFTER_DISCOUNT > preinit_parameters.permanentLiquidityShareNumerator"));
is_valid_ido = false;
}
if preinit_parameters.offeredTokenAmount <= 0 { if preinit_parameters.offeredTokenAmount <= 0 {
invalid_ido_reasons.push(anyhow::anyhow!( invalid_ido_reasons.push(anyhow::anyhow!(
"preinit_parameters.offeredTokenAmount <= Integer::from(0)" "preinit_parameters.offeredTokenAmount <= Integer::from(0)"
@ -1698,6 +1691,26 @@ fn parse_ido_preinit_tx_params(
)); ));
is_valid_ido = false; is_valid_ido = false;
} }
// The permanent-liquidity minimums are pinned by the IdoParams
// NFT (numerators over PERMANENT_LIQUIDITY_SHARE_DENOMINATOR,
// directly comparable to permanentLiquidityShareNumerator).
if preinit_parameters.permanentLiquidityShareNumerator
< ido_params.minPlpShare
{
invalid_ido_reasons.push(anyhow::anyhow!(
"preinit_parameters.permanentLiquidityShareNumerator < ido_params.minPlpShare"
));
is_valid_ido = false;
}
if &preinit_parameters.offering.offer.maxDiscountRateNumerator
+ &ido_params.minPlpAfterDiscount
> preinit_parameters.permanentLiquidityShareNumerator
{
invalid_ido_reasons.push(anyhow::anyhow!(
"preinit_parameters.offering.offer.maxDiscountRateNumerator + ido_params.minPlpAfterDiscount > preinit_parameters.permanentLiquidityShareNumerator"
));
is_valid_ido = false;
}
} }
} }