From 9c4ee3b321544e15b63737a6a4b163baca156720 Mon Sep 17 00:00:00 2001 From: JoelDellamaggiore Date: Wed, 20 May 2026 17:22:00 -0300 Subject: [PATCH] fix: use provider-supplied cost models in script integrity hash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The serializer currently hashes Plutus cost models from a hardcoded default (DEFAULT_V[123]_COST_MODEL_LIST), regardless of the protocol parameters passed in. This produces an incorrect scriptIntegrityHash on any network whose cost model differs from those defaults. Concrete impact: mainnet still uses the default (297 V3 entries), but preprod and preview have expanded V3 to 350 entries ahead of the upcoming PV11 hard fork. Today any Plutus V3 mint submitted to preprod/preview fails with PPViewHashesDontMatch. Once PV11 is enacted, mainnet will be affected too. This change makes the CardanoSDKSerializer prefer cost models supplied through Protocol.costModels (typically derived from the provider's fetchProtocolParameters response) and fall back to the hardcoded defaults when not supplied. Backwards compatible — existing callers that pass a Protocol without costModels keep current behavior. Changes: - Add optional CostModels type and Protocol.costModels field - castProtocol passes the costModels object through - CardanoSDKSerializerCore reads cost models from protocolParams when available, falls back to DEFAULT_V[123]_COST_MODEL_LIST otherwise - Tests for castProtocol covering presence/absence of costModels --- packages/mesh-common/src/types/protocol.ts | 14 +++++++++++++- .../mesh-common/test/types/protocol.test.ts | 14 ++++++++++++++ packages/mesh-core-cst/src/serializer/index.ts | 18 +++++++++--------- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/mesh-common/src/types/protocol.ts b/packages/mesh-common/src/types/protocol.ts index 0a4d380b1..109299b49 100644 --- a/packages/mesh-common/src/types/protocol.ts +++ b/packages/mesh-common/src/types/protocol.ts @@ -1,5 +1,11 @@ import { DEFAULT_PROTOCOL_PARAMETERS } from "../constants"; +export type CostModels = { + PlutusV1?: number[]; + PlutusV2?: number[]; + PlutusV3?: number[]; +}; + export type Protocol = { epoch: number; minFeeA: number; @@ -22,12 +28,14 @@ export type Protocol = { maxCollateralInputs: number; coinsPerUtxoSize: number; minFeeRefScriptCostPerByte: number; + costModels?: CostModels; }; export const castProtocol = ( data: Partial>, ): Protocol => { - const result: Partial> = {}; + const result: Partial> = + {}; for (const rawKey in DEFAULT_PROTOCOL_PARAMETERS) { const key = rawKey as keyof Protocol; @@ -40,5 +48,9 @@ export const castProtocol = ( } } + if (data.costModels && typeof data.costModels === "object") { + result.costModels = data.costModels as CostModels; + } + return result as Protocol; }; diff --git a/packages/mesh-common/test/types/protocol.test.ts b/packages/mesh-common/test/types/protocol.test.ts index b04b34865..ca7e3d1f8 100644 --- a/packages/mesh-common/test/types/protocol.test.ts +++ b/packages/mesh-common/test/types/protocol.test.ts @@ -75,5 +75,19 @@ describe("Protocol", () => { expect(casted.epoch).toBe(correct.epoch); expect(casted.decentralisation).toBe(correct.decentralisation); }); + + it("should preserve costModels when provided", () => { + const v3 = [100788, 420, 1, 1, 1000]; + const casted = castProtocol({ costModels: { PlutusV3: v3 } }); + + expect(casted.costModels).toBeDefined(); + expect(casted.costModels?.PlutusV3).toEqual(v3); + }); + + it("should leave costModels undefined when not provided", () => { + const casted = castProtocol({ minFeeA: 44 }); + + expect(casted.costModels).toBeUndefined(); + }); }); }); diff --git a/packages/mesh-core-cst/src/serializer/index.ts b/packages/mesh-core-cst/src/serializer/index.ts index 613640acc..b7b8b9dce 100644 --- a/packages/mesh-core-cst/src/serializer/index.ts +++ b/packages/mesh-core-cst/src/serializer/index.ts @@ -1528,15 +1528,15 @@ class CardanoSDKSerializerCore { // After building tx witness set, we must hash it with the cost models // and put the hash in the tx body - let costModelV1 = Serialization.CostModel.newPlutusV1( - DEFAULT_V1_COST_MODEL_LIST, - ); - let costModelV2 = Serialization.CostModel.newPlutusV2( - DEFAULT_V2_COST_MODEL_LIST, - ); - let costModelV3 = Serialization.CostModel.newPlutusV3( - DEFAULT_V3_COST_MODEL_LIST, - ); + let v1CostList = + this.protocolParams.costModels?.PlutusV1 ?? DEFAULT_V1_COST_MODEL_LIST; + let v2CostList = + this.protocolParams.costModels?.PlutusV2 ?? DEFAULT_V2_COST_MODEL_LIST; + let v3CostList = + this.protocolParams.costModels?.PlutusV3 ?? DEFAULT_V3_COST_MODEL_LIST; + let costModelV1 = Serialization.CostModel.newPlutusV1(v1CostList); + let costModelV2 = Serialization.CostModel.newPlutusV2(v2CostList); + let costModelV3 = Serialization.CostModel.newPlutusV3(v3CostList); let costModels = new Serialization.Costmdls(); if (this.usedLanguages[PlutusLanguageVersion.V1]) {