diff --git a/package.json b/package.json index c56083f7..1276c8b0 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@harmoniclabs/cardano-costmodels-ts": "^1.1.0", "@harmoniclabs/plutus-machine": "^1.1.0", "@harmoniclabs/uplc": "^1.2.3", - "@harmoniclabs/cardano-ledger-ts": "^0.2.0-dev0", + "@harmoniclabs/cardano-ledger-ts": "^0.2.0-dev4", "@harmoniclabs/plu-ts-offchain": "^0.1.13", "@harmoniclabs/plu-ts-onchain": "^0.2.8" }, diff --git a/packages/offchain/package-lock.json b/packages/offchain/package-lock.json index 15b819f1..904f8d81 100644 --- a/packages/offchain/package-lock.json +++ b/packages/offchain/package-lock.json @@ -32,7 +32,7 @@ "peerDependencies": { "@harmoniclabs/bytestring": "^1.0.0", "@harmoniclabs/cardano-costmodels-ts": "^1.1.0", - "@harmoniclabs/cardano-ledger-ts": "^0.2.0-dev3", + "@harmoniclabs/cardano-ledger-ts": "^0.2.0-dev5", "@harmoniclabs/cbor": "^1.3.0", "@harmoniclabs/pair": "^1.0.0", "@harmoniclabs/plutus-data": "^1.2.4", @@ -1766,9 +1766,9 @@ } }, "node_modules/@harmoniclabs/cardano-ledger-ts": { - "version": "0.2.0-dev3", - "resolved": "https://registry.npmjs.org/@harmoniclabs/cardano-ledger-ts/-/cardano-ledger-ts-0.2.0-dev3.tgz", - "integrity": "sha512-t7NXzrQOBwSjf/36k8nftNX1Ltz8YwX82xNyDHxSW/gjmanOpFkztk5E0qrnu31tlWBKQ9rSeKd1g1GFAOsCAQ==", + "version": "0.2.0-dev5", + "resolved": "https://registry.npmjs.org/@harmoniclabs/cardano-ledger-ts/-/cardano-ledger-ts-0.2.0-dev5.tgz", + "integrity": "sha512-tRm/300a7fki2ZYkiLlUiQunLoThZQcYuEcxB3B+XFh/GCDa5pAjToU21WDRI1a/HScujWi/TrqeY/KtgpZaQg==", "peer": true, "dependencies": { "@harmoniclabs/bigint-utils": "^1.0.0", @@ -1784,7 +1784,7 @@ "@harmoniclabs/cardano-costmodels-ts": "^1.1.0", "@harmoniclabs/cbor": "^1.2.0", "@harmoniclabs/pair": "^1.0.0", - "@harmoniclabs/plutus-data": "^1.0.0", + "@harmoniclabs/plutus-data": "^1.2.4", "@harmoniclabs/plutus-machine": "^1.0.2" } }, diff --git a/packages/offchain/package.json b/packages/offchain/package.json index f9cc707d..46a400b1 100644 --- a/packages/offchain/package.json +++ b/packages/offchain/package.json @@ -66,7 +66,7 @@ "@harmoniclabs/cardano-costmodels-ts": "^1.1.0", "@harmoniclabs/plutus-machine": "^1.1.2", "@harmoniclabs/uplc": "^1.2.3", - "@harmoniclabs/cardano-ledger-ts": "^0.2.0-dev3" + "@harmoniclabs/cardano-ledger-ts": "^0.2.0-dev5" }, "devDependencies": { "@babel/preset-env": "^7.18.6", diff --git a/packages/offchain/src/TxBuilder/TxBuilder.ts b/packages/offchain/src/TxBuilder/TxBuilder.ts index 017c7611..5b811467 100644 --- a/packages/offchain/src/TxBuilder/TxBuilder.ts +++ b/packages/offchain/src/TxBuilder/TxBuilder.ts @@ -2,7 +2,7 @@ import { fromHex, fromUtf8, isUint8Array, lexCompare, toHex } from "@harmoniclab import { keepRelevant } from "./keepRelevant"; import { GenesisInfos, isGenesisInfos } from "./GenesisInfos"; import { isCostModelsV2, isCostModelsV1, defaultV2Costs, defaultV1Costs, costModelsToLanguageViewCbor, isCostModelsV3, defaultV3Costs } from "@harmoniclabs/cardano-costmodels-ts"; -import { NetworkT, ProtocolParameters, isPartialProtocolParameters, Tx, Value, ValueUnits, TxOut, TxRedeemerTag, txRdmrTagToString, ScriptType, UTxO, VKeyWitness, Script, BootstrapWitness, TxRedeemer, Hash32, TxIn, Hash28, AuxiliaryData, TxWitnessSet, getNSignersNeeded, txRedeemerTagToString, ScriptDataHash, Address, AddressStr, TxBody, CredentialType, canBeHash32 } from "@harmoniclabs/cardano-ledger-ts"; +import { NetworkT, ProtocolParameters, isPartialProtocolParameters, Tx, Value, ValueUnits, TxOut, TxRedeemerTag, txRdmrTagToString, ScriptType, UTxO, VKeyWitness, Script, BootstrapWitness, TxRedeemer, Hash32, TxIn, Hash28, AuxiliaryData, TxWitnessSet, getNSignersNeeded, txRedeemerTagToString, ScriptDataHash, Address, AddressStr, TxBody, CredentialType, canBeHash32, VotingProcedures, ProposalProcedure } from "@harmoniclabs/cardano-ledger-ts"; import { CborString, CborPositiveRational, Cbor, CborArray, CanBeCborString } from "@harmoniclabs/cbor"; import { byte, blake2b_256 } from "@harmoniclabs/crypto"; import { Data, dataToCborObj, DataConstr, dataToCbor } from "@harmoniclabs/plutus-data"; @@ -19,6 +19,7 @@ import { CanBeData, canBeData, forceData } from "../utils/CanBeData"; import { getSpendingPurposeData } from "../toOnChain/getSpendingPurposeData"; import { TxBuilderProtocolParams, ValidatedTxBuilderProtocolParams, completeTxBuilderProtocolParams } from "./TxBuilderProtocolParams"; import { ChangeInfos } from "../txBuild/ChangeInfos/ChangeInfos"; +import { scriptTypeToDataVersion } from "./utils"; type ScriptLike = { hash: string, @@ -312,10 +313,13 @@ export class TxBuilder const executionUnitPrices = this.protocolParamters.executionUnitPrices; const [ memRational, cpuRational ] = executionUnitPrices; + // group by purpose so we can use the redeemer index to find the script const spendScriptsToExec = scriptsToExec.filter( elem => elem.rdmrTag === TxRedeemerTag.Spend ); const mintScriptsToExec = scriptsToExec.filter( elem => elem.rdmrTag === TxRedeemerTag.Mint ); const certScriptsToExec = scriptsToExec.filter( elem => elem.rdmrTag === TxRedeemerTag.Cert ); const withdrawScriptsToExec = scriptsToExec.filter( elem => elem.rdmrTag === TxRedeemerTag.Withdraw ); + const voteScriptsToExec = scriptsToExec.filter( elem => elem.rdmrTag === TxRedeemerTag.Voting ); + const proposeScriptsToExec = scriptsToExec.filter( elem => elem.rdmrTag === TxRedeemerTag.Proposing ); const maxRound = 3; @@ -327,7 +331,7 @@ export class TxBuilder { prevFee = fee; - const { v1: txInfosV1, v2: txInfosV2 } = getTxInfos( tx, this.genesisInfos ); + const { v1: txInfosV1, v2: txInfosV2, v3: txInfosV3 } = getTxInfos( tx, this.genesisInfos ); let totExBudget = new ExBudget({ mem: 0, cpu: 0 }); @@ -337,7 +341,7 @@ export class TxBuilder const { tag, data: rdmrData, index: rdmr_idx } = rdmr; // "+ 1" because we keep track of lovelaces even if in mint values these are 0 const index = rdmr_idx + (tag === TxRedeemerTag.Mint ? 1 : 0); - const spendingPurpose = getSpendingPurposeData( rdmr, tx.body ); + // const spendingPurpose = getSpendingPurposeData( rdmr, tx.body ); const onlyRedeemerArg = ( purposeScriptsToExec: ScriptToExecEntry[] ) => { @@ -345,14 +349,20 @@ export class TxBuilder if( script === undefined ) throw new Error( - "missing script for " + txRdmrTagToString(tag) + " redeemer " + (index - 1) + "missing script for " + txRedeemerTagToString(tag) + " redeemer " + (index - 1) ); + const expectedVersion = scriptTypeToDataVersion( script.type ); + + if( typeof expectedVersion !== "string" ) + throw new Error("unexpected redeemer for native script"); + const ctxData = getCtx( script.type, - spendingPurpose, + getSpendingPurposeData( rdmr, tx.body, expectedVersion ), txInfosV1, - txInfosV2 + txInfosV2, + txInfosV3, ); const { result, budgetSpent, logs } = cek.eval( @@ -400,11 +410,17 @@ export class TxBuilder "missing datum for spend redeemer " + index ); + const expectedVersion = scriptTypeToDataVersion( script.type ); + + if( typeof expectedVersion !== "string" ) + throw new Error("unexpected redeemer for native script"); + const ctxData = getCtx( script.type, - spendingPurpose, + getSpendingPurposeData( rdmr, tx.body, expectedVersion ), txInfosV1, - txInfosV2 + txInfosV2, + txInfosV3 ); const { result, budgetSpent, logs } = cek.eval( @@ -442,6 +458,8 @@ export class TxBuilder else if( tag === TxRedeemerTag.Mint ) onlyRedeemerArg( mintScriptsToExec ) else if( tag === TxRedeemerTag.Cert ) onlyRedeemerArg( certScriptsToExec ) else if( tag === TxRedeemerTag.Withdraw ) onlyRedeemerArg( withdrawScriptsToExec ) + else if( tag === TxRedeemerTag.Voting ) onlyRedeemerArg( voteScriptsToExec ) + else if( tag === TxRedeemerTag.Proposing ) onlyRedeemerArg( proposeScriptsToExec ) else throw new Error( "unrecoignized redeemer tag " + tag ) @@ -555,6 +573,10 @@ export class TxBuilder certificates, withdrawals, metadata, + votingProcedures, + proposalProcedures, + currentTreasuryValue, + paymentToTreasury, ...args } = normalizeITxBuildArgs( buildArgs ); @@ -570,9 +592,9 @@ export class TxBuilder if( change ) changeAddress = change.address; if( !changeAddress ) - { - throw new Error("missing changAddress and change entry while constructing a transaciton; unable to balance inputs and outpus"); - } + throw new Error( + "missing changAddress and change entry while constructing a transaciton; unable to balance inputs and outpus" + ); if( !change ) change = { address: changeAddress }; @@ -605,6 +627,7 @@ export class TxBuilder const plutusV1ScriptsWitnesses: Script[] = []; const datums: Data[] = [] const plutusV2ScriptsWitnesses: Script[] = []; + const plutusV3ScriptsWitnesses: Script[] = []; const dummyExecBudget = ExBudget.maxCborSize; @@ -612,6 +635,8 @@ export class TxBuilder const mintRedeemers: TxRedeemer[] = []; const certRedeemers: TxRedeemer[] = []; const withdrawRedeemers: TxRedeemer[] = []; + const voteRedeemers: TxRedeemer[] = []; + const proposeRedeemers: TxRedeemer[] = []; const scriptsToExec: ScriptToExecEntry[] = []; @@ -623,33 +648,39 @@ export class TxBuilder * needed in `getScriptDataHash` to understand whoich cost model to transform in language view */ let _hasV2Scripts = false; + /** + * needed in `getScriptDataHash` to understand whoich cost model to transform in language view + */ + let _hasV3Scripts = false; function pushScriptToExec( idx: number, tag: TxRedeemerTag, script: Script, datum?: Data ) { - if( script.type !== ScriptType.NativeScript ) - { - - // keep track of exsisting csript versions - if( !_hasV1Scripts && script.type === "PlutusScriptV1" ) - { - _hasV1Scripts = true; - } - if( !_hasV2Scripts && script.type === "PlutusScriptV2" ) - { - _hasV2Scripts = true; - } + if( script.type == ScriptType.NativeScript ) return; - scriptsToExec.push({ - index: idx, - rdmrTag: tag, - script: { - type: script.type as any, - bytes: script.bytes.slice(), - hash: script.hash.toString() - }, - datum - }) + // keep track of exsisting csript versions + if( !_hasV1Scripts && script.type === "PlutusScriptV1" ) + { + _hasV1Scripts = true; + } + else if( !_hasV2Scripts && script.type === "PlutusScriptV2" ) + { + _hasV2Scripts = true; } + else if( !_hasV3Scripts && script.type === "PlutusScriptV3" ) + { + _hasV3Scripts = true; + } + + scriptsToExec.push({ + index: idx, + rdmrTag: tag, + script: { + type: script.type as any, + bytes: script.bytes, + hash: script.hash.toString() + }, + datum + }); } function pushWitScript( script : Script ): void { @@ -658,6 +689,7 @@ export class TxBuilder if( t === "NativeScript" ) pushUniqueScript( nativeScriptsWitnesses , script as any ); else if( t === "PlutusScriptV1" ) pushUniqueScript( plutusV1ScriptsWitnesses, script as any ); else if( t === "PlutusScriptV2" ) pushUniqueScript( plutusV2ScriptsWitnesses, script as any ); + else if( t === "PlutusScriptV3" ) pushUniqueScript( plutusV3ScriptsWitnesses, script as any ); } /** @@ -683,7 +715,7 @@ export class TxBuilder "multiple scripts specified" ); - const refScript = (script.ref as UTxO).resolved.refScript; + const refScript = (script.ref as UTxO)?.resolved?.refScript; if( refScript === (void 0) ) throw new Error( @@ -691,15 +723,14 @@ export class TxBuilder "but the provided utxo is missing any attached script" ); - const sameRefPresent = refIns.find( u => eqUTxOByRef( u, script.ref )); - if( sameRefPresent === undef ) + if( !refIns.some( u => eqUTxOByRef( u, script.ref )) ) { refIns.push( script.ref ); } return refScript; } - throw "unexpected execution flow 'checkScriptAndPushIfInline' in TxBuilder" + throw new Error("unexpected execution flow 'checkScriptAndPushIfInline' in TxBuilder"); } /** @@ -751,7 +782,7 @@ export class TxBuilder { const { utxo, - referenceScriptV2, + referenceScript, inputScript } = input; @@ -761,14 +792,14 @@ export class TxBuilder if( addr.paymentCreds.type === CredentialType.Script && - referenceScriptV2 === undef && + referenceScript === undef && inputScript === undef ) throw new Error( "spending script utxo \"" + utxo.utxoRef.toString() + "\" without script source" ); - if( referenceScriptV2 !== undef ) + if( referenceScript !== undef ) { if( inputScript !== undef ) throw new Error( @@ -779,7 +810,7 @@ export class TxBuilder datum, redeemer, refUtxo - } = referenceScriptV2; + } = referenceScript; const refScript = refUtxo.resolved.refScript; @@ -810,7 +841,7 @@ export class TxBuilder } if( inputScript !== undefined ) { - if( referenceScriptV2 !== undefined ) + if( referenceScript !== undefined ) throw new Error( "invalid input; multiple scripts specified" ); @@ -976,7 +1007,54 @@ export class TxBuilder } return withdrawal; - }) + }); + + const _votingProcedures = Array.isArray( votingProcedures ) ? + new VotingProcedures( + votingProcedures?.map(({ votingProcedure, script }, i) => { + + if( script !== undef ) + { + voteRedeemers.push( + new TxRedeemer({ + data: forceData( script.redeemer ), + index: i, + execUnits: dummyExecBudget.clone(), + tag: TxRedeemerTag.Voting + }) + ); + + const toExec = checkScriptAndPushIfInline( script ); + + pushScriptToExec( i, TxRedeemerTag.Voting, toExec ); + } + + return votingProcedure; + }) + ) : undef; + + // TODO + const _proposalProcedures = Array.isArray( proposalProcedures ) ? + proposalProcedures.map(({ proposalProcedure, script }, i) => { + + if( script !== undef ) + { + voteRedeemers.push( + new TxRedeemer({ + data: forceData( script.redeemer ), + index: i, + execUnits: dummyExecBudget.clone(), + tag: TxRedeemerTag.Proposing + }) + ); + + const toExec = checkScriptAndPushIfInline( script ); + + pushScriptToExec( i, TxRedeemerTag.Proposing, toExec ); + } + + return new ProposalProcedure( proposalProcedure ); + }) : undef; const auxData = metadata !== undefined? new AuxiliaryData({ metadata }) : undefined; @@ -984,7 +1062,9 @@ export class TxBuilder spendRedeemers .concat( mintRedeemers ) .concat( withdrawRedeemers ) - .concat( certRedeemers ); + .concat( certRedeemers ) + .concat( voteRedeemers ) + .concat( proposeRedeemers ); const dummyTxWitnesses = new TxWitnessSet({ vkeyWitnesses, @@ -993,7 +1073,8 @@ export class TxBuilder redeemers, nativeScripts: nativeScriptsWitnesses, plutusV1Scripts: plutusV1ScriptsWitnesses, - plutusV2Scripts: plutusV2ScriptsWitnesses + plutusV2Scripts: plutusV2ScriptsWitnesses, + plutusV3Scripts: plutusV3ScriptsWitnesses }); const datumsScriptData = @@ -1101,6 +1182,10 @@ export class TxBuilder auxDataHash: auxData?.hash, scriptDataHash: getScriptDataHash( redeemers, datumsScriptData, languageViews ), network, + votingProcedures: _votingProcedures, + proposalProcedures: _proposalProcedures, + currentTreasuryValue: currentTreasuryValue, + donation: paymentToTreasury, }), witnesses: dummyTxWitnesses, auxiliaryData: auxData, @@ -1185,10 +1270,9 @@ function eqUTxOByRef( a: UTxO, b: UTxO ): boolean function pushUniqueScript( arr: Script[], toPush: Script ): void { + const hashToPush = toPush.hash.toString(); if( - !arr.some( script => - script.hash.toString() === toPush.hash.toString() - ) + !arr.some( script => script.hash.toString() === hashToPush ) ) arr.push( toPush ); } @@ -1196,14 +1280,28 @@ function getCtx( scriptType: ScriptType, spendingPurpose: DataConstr, txInfosV1: Data | undefined, - txInfosV2: Data + txInfosV2: Data | undefined, + txInfosV3: Data ): DataConstr { - if( scriptType === ScriptType.PlutusV2 ) + if( scriptType === ScriptType.PlutusV3 ) { return new DataConstr( - 0, - [ + 0, [ + txInfosV3, + spendingPurpose + ] + ) + } + else if( scriptType === ScriptType.PlutusV2 ) + { + if( txInfosV2 === undefined ) + throw new Error( + "plutus script v1 included in a v2 transaction" + ); + + return new DataConstr( + 0, [ txInfosV2, spendingPurpose ] @@ -1213,7 +1311,7 @@ function getCtx( { if( txInfosV1 === undefined ) throw new Error( - "plutus script v1 included in a v2 transaction" + "plutus script v1 included in a v2 or v3 transaction" ); return new DataConstr( diff --git a/packages/offchain/src/TxBuilder/TxBuilderRunner/TxBuilderRunner.ts b/packages/offchain/src/TxBuilder/TxBuilderRunner/TxBuilderRunner.ts index 4ecd9ed0..50165c3a 100644 --- a/packages/offchain/src/TxBuilder/TxBuilderRunner/TxBuilderRunner.ts +++ b/packages/offchain/src/TxBuilder/TxBuilderRunner/TxBuilderRunner.ts @@ -1230,7 +1230,7 @@ export class TxBuilderRunner buildArgs.inputs.push({ utxo, - referenceScriptV2: { + referenceScript: { refUtxo: ref, redeemer, datum: isInlineDatum ? "inline" : datum as Data diff --git a/packages/offchain/src/TxBuilder/utils.ts b/packages/offchain/src/TxBuilder/utils.ts new file mode 100644 index 00000000..b09e34ab --- /dev/null +++ b/packages/offchain/src/TxBuilder/utils.ts @@ -0,0 +1,11 @@ +import { ScriptType } from "@harmoniclabs/cardano-ledger-ts"; +import type { ToDataVersion } from "@harmoniclabs/cardano-ledger-ts/dist/toData/defaultToDataVersion"; + +export function scriptTypeToDataVersion( scriptType: ScriptType ): ToDataVersion | undefined +{ + // if( scriptType === ScriptType.NativeScript ) return undefined; + if( scriptType === ScriptType.PlutusV1 ) return "v1"; + if( scriptType === ScriptType.PlutusV2 ) return "v2"; + if( scriptType === ScriptType.PlutusV3 ) return "v3"; + return undefined; +} \ No newline at end of file diff --git a/packages/offchain/src/__tests__/TxBuilder.build.fee.test.ts b/packages/offchain/src/__tests__/TxBuilder.build.fee.test.ts index 2dd3881b..91f4164a 100644 --- a/packages/offchain/src/__tests__/TxBuilder.build.fee.test.ts +++ b/packages/offchain/src/__tests__/TxBuilder.build.fee.test.ts @@ -1088,7 +1088,7 @@ describe("fee calculation", () => { inputs: [ { utxo: validatorMasterUtxo, - referenceScriptV2: { + referenceScript: { refUtxo: deployedScriptRefUtxo, datum: "inline", redeemer: nonce_redeemer diff --git a/packages/offchain/src/__tests__/TxBuilder.build.test.ts b/packages/offchain/src/__tests__/TxBuilder.build.test.ts index 2c3527b1..33b10095 100644 --- a/packages/offchain/src/__tests__/TxBuilder.build.test.ts +++ b/packages/offchain/src/__tests__/TxBuilder.build.test.ts @@ -215,7 +215,7 @@ describe("TxBuilder.build", () => { datum: new DataConstr( 0, [] ) } }), - referenceScriptV2: { + referenceScript: { datum: "inline", redeemer: new DataConstr( 0, [] ), refUtxo: new UTxO({ @@ -284,7 +284,7 @@ describe("TxBuilder.build", () => { // datum: new DataConstr( 0, [] ) } }), - referenceScriptV2: { + referenceScript: { datum: "inline", redeemer: new DataConstr( 0, [] ), refUtxo: new UTxO({ diff --git a/packages/offchain/src/__tests__/TxBuilder.buildSync.test.ts b/packages/offchain/src/__tests__/TxBuilder.buildSync.test.ts index 3ab157a9..d128881f 100644 --- a/packages/offchain/src/__tests__/TxBuilder.buildSync.test.ts +++ b/packages/offchain/src/__tests__/TxBuilder.buildSync.test.ts @@ -144,7 +144,7 @@ describe("TxBuilder.buildSync", () => { datum: new DataConstr( 0, [] ) } }), - referenceScriptV2: { + referenceScript: { datum: "inline", redeemer: new DataConstr( 0, [] ), refUtxo: new UTxO({ @@ -211,7 +211,7 @@ describe("TxBuilder.buildSync", () => { // datum: new DataConstr( 0, [] ) } }), - referenceScriptV2: { + referenceScript: { datum: "inline", redeemer: new DataConstr( 0, [] ), refUtxo: new UTxO({ diff --git a/packages/offchain/src/toOnChain/getSpendingPurposeData.ts b/packages/offchain/src/toOnChain/getSpendingPurposeData.ts index 916aa70f..18f6b55c 100644 --- a/packages/offchain/src/toOnChain/getSpendingPurposeData.ts +++ b/packages/offchain/src/toOnChain/getSpendingPurposeData.ts @@ -1,12 +1,14 @@ import { Hash28, StakeCredentials, StakeValidatorHash, Tx, TxBody, TxRedeemer, TxRedeemerTag } from "@harmoniclabs/cardano-ledger-ts"; -import { Data, DataB, DataConstr } from "@harmoniclabs/plutus-data"; +import type { ToDataVersion } from "@harmoniclabs/cardano-ledger-ts/dist/toData/defaultToDataVersion"; +import { Data, DataB, DataConstr, DataList } from "@harmoniclabs/plutus-data"; import { lexCompare } from "@harmoniclabs/uint8array-utils"; -export function getSpendingPurposeData( rdmr: TxRedeemer, tx: TxBody ): DataConstr +export function getSpendingPurposeData( rdmr: TxRedeemer, tx: TxBody, version: ToDataVersion ): DataConstr { + version = version ?? "v2"; const tag = rdmr.tag; - let ctorIdx: 0 | 1 | 2 | 3; + let ctorIdx: 0 | 1 | 2 | 3 | 4 | 5; let purposeArgData: Data; if( tag === TxRedeemerTag.Mint ) @@ -38,7 +40,8 @@ export function getSpendingPurposeData( rdmr: TxRedeemer, tx: TxBody ): DataCons "invalid 'Spend' redeemer index: " + rdmr.index.toString() + "; tx.inputs.length is: " + tx.inputs.length.toString() ); - purposeArgData = utxoRef.toData(); + // @ts-ignore Expected 0 arguments, but got 1.t + purposeArgData = utxoRef.toData( version ); } else if( tag === TxRedeemerTag.Withdraw ) { @@ -51,7 +54,9 @@ export function getSpendingPurposeData( rdmr: TxRedeemer, tx: TxBody ): DataCons purposeArgData = new StakeCredentials( "script", new StakeValidatorHash( stakeAddr.credentials ) - ).toData(); + ) + // @ts-ignore Expected 0 arguments, but got 1.t + .toData( version ); } else if( tag === TxRedeemerTag.Cert ) { @@ -61,7 +66,16 @@ export function getSpendingPurposeData( rdmr: TxRedeemer, tx: TxBody ): DataCons throw new Error( "invalid certificate for certifyng redeemer " + rdmr.index.toString() ); - purposeArgData = cert.toData(); + let tmp: Data; + + tmp = cert.toData( version ); + + while( tmp instanceof DataList ) + { + tmp = tmp.list[0]; + } + + purposeArgData = tmp; } else throw new Error( "invalid redeemer tag" diff --git a/packages/offchain/src/toOnChain/getTxInfos.ts b/packages/offchain/src/toOnChain/getTxInfos.ts index 1fa1d86e..241762a0 100644 --- a/packages/offchain/src/toOnChain/getTxInfos.ts +++ b/packages/offchain/src/toOnChain/getTxInfos.ts @@ -4,6 +4,7 @@ import { Data, DataB, DataConstr, DataList, DataMap, DataPair, hashData } from " import { Tx, TxRedeemer, UTxO, Value } from "@harmoniclabs/cardano-ledger-ts"; import { getSpendingPurposeData } from "./getSpendingPurposeData"; import { lexCompare } from "@harmoniclabs/uint8array-utils"; +import type { ToDataVersion } from "@harmoniclabs/cardano-ledger-ts/dist/toData/defaultToDataVersion"; function sortUTxO( a: UTxO, b: UTxO ): number { const ord = lexCompare( a.utxoRef.id.toBuffer(), b.utxoRef.id.toBuffer() ); @@ -16,19 +17,19 @@ function sortUTxO( a: UTxO, b: UTxO ): number { export function getTxInfos( transaction: Tx, genesisInfos: GenesisInfos | undefined -): { v1: Data | undefined, v2: Data } +): { v1: Data | undefined, v2: Data | undefined, v3: Data } { const { body: tx, witnesses } = transaction; - function redeemerToDataPair( rdmr: TxRedeemer ): DataPair + function redeemerToDataPair( rdmr: TxRedeemer, version: ToDataVersion ): DataPair { return new DataPair( - getSpendingPurposeData( rdmr, tx ), + getSpendingPurposeData( rdmr, tx, version ), rdmr.data.clone() - ) + ); } const sortedInputs = tx.inputs.slice().sort( sortUTxO ); @@ -36,8 +37,6 @@ export function getTxInfos( const feeData = Value.lovelaces( tx.fee ).toData(); const mintData = (tx.mint ?? Value.lovelaces( 0 ) ).toData(); - const certsData = new DataList( tx.certs?.map( cert => cert.toData() ) ?? [] ); - const withdrawsData = tx.withdrawals?.toData() ?? new DataMap([]); const intervalData = getTxIntervalData( tx.validityIntervalStart, tx.ttl, genesisInfos ); const sigsData = new DataList( tx.requiredSigners?.map( sig => sig.toData() ) ?? [] ); const datumsData = new DataMap( @@ -69,9 +68,9 @@ export function getTxInfos( // mint mintData.clone(), // dCertificates - certsData.clone(), + new DataList( tx.certs?.map( cert => cert.toData("v1") ) ?? [] ), // withderawals - withdrawsData.clone(), + tx.withdrawals?.toData( "v1" ) ?? new DataMap([]), // interval intervalData.clone(), // signatories @@ -88,30 +87,69 @@ export function getTxInfos( v1 = undefined; } - const v2 = new DataConstr( + let v2: DataConstr | undefined = undefined; + try { + v2 = new DataConstr( + 0, // PTxInfo; only costructor + [ + // inputs + new DataList( sortedInputs.map( input => input.toData("v2") ) ), + // refInputs + new DataList( sortedRefInputs?.map( refIn => refIn.toData("v2") ) ?? [] ), + // outputs + new DataList( tx.outputs.map( out => out.toData("v2") ) ), + // fee + feeData, + // mint + mintData, + // dCertificates + new DataList( tx.certs?.map( cert => cert.toData("v2") ) ?? [] ), + // withderawals + tx.withdrawals?.toData( "v2" ) ?? new DataMap([]), + // interval + intervalData, + // signatories + sigsData, + // redeemers + new DataMap( + witnesses.redeemers?.map( rdmr => redeemerToDataPair( rdmr, "v2" ) ) ?? [] + ), + // datums + datumsData, + // id + txIdData + ] + ); + } + catch + { + v2 = undefined; + } + + const v3 = new DataConstr( 0, // PTxInfo; only costructor [ // inputs - new DataList( sortedInputs.map( input => input.toData("v2") ) ), + new DataList( sortedInputs.map( input => input.toData("v3") ) ), // refInputs - new DataList( sortedRefInputs?.map( refIn => refIn.toData("v2") ) ?? [] ), + new DataList( sortedRefInputs?.map( refIn => refIn.toData("v3") ) ?? [] ), // outputs - new DataList( tx.outputs.map( out => out.toData("v2") ) ), + new DataList( tx.outputs.map( out => out.toData("v3") ) ), // fee feeData, // mint mintData, // dCertificates - certsData, + new DataList( tx.certs?.map( cert => cert.toData("v3") ) ?? [] ), // withderawals - withdrawsData, + tx.withdrawals?.toData( "v3" ) ?? new DataMap([]), // interval intervalData, // signatories sigsData, // redeemers new DataMap( - witnesses.redeemers?.map( redeemerToDataPair ) ?? [] + witnesses.redeemers?.map( rdmr => redeemerToDataPair( rdmr, "v3" ) ) ?? [] ), // datums datumsData, @@ -120,5 +158,5 @@ export function getTxInfos( ] ); - return { v1, v2 }; + return { v1, v2, v3 }; } \ No newline at end of file diff --git a/packages/offchain/src/txBuild/ITxBuildArgs.ts b/packages/offchain/src/txBuild/ITxBuildArgs.ts index 4ccda2f4..3c0c83f4 100644 --- a/packages/offchain/src/txBuild/ITxBuildArgs.ts +++ b/packages/offchain/src/txBuild/ITxBuildArgs.ts @@ -1,4 +1,4 @@ -import { Address, AddressStr, CanBeHash28, Hash32, IUTxO, IVotingProcedures, PubKeyHash, Script, TxMetadata, TxOut, UTxO, VotingProcedures, isIUTxO } from "@harmoniclabs/cardano-ledger-ts"; +import { Address, AddressStr, CanBeHash28, Hash32, IProposalProcedure, IUTxO, IVotingProcedures, IVotingProceduresEntry, PubKeyHash, Script, TxMetadata, TxOut, UTxO, VotingProcedures, isIProposalProcedure, isIUTxO, isIVotingProceduresEntry } from "@harmoniclabs/cardano-ledger-ts"; import { NormalizedITxBuildCert, type ITxBuildCert, normalizeITxBuildCert } from "./ITxBuildCert"; import { NormalizedITxBuildInput, type ITxBuildInput, normalizeITxBuildInput } from "./ITxBuildInput/ITxBuildInput"; import { NormalizedITxBuildMint, type ITxBuildMint, normalizeITxBuildMint } from "./ITxBuildMint"; @@ -34,8 +34,8 @@ export interface ITxBuildArgs { withdrawals?: ITxBuildWithdrawal[], metadata?: TxMetadata, // conway - votingProcedures?: ITxBuildVotingProcedure[], - proposalProcedures?: ITxBuildProposalProcedure[], + votingProcedures?: (IVotingProceduresEntry | ITxBuildVotingProcedure)[], + proposalProcedures?: (IProposalProcedure | ITxBuildProposalProcedure)[], currentTreasuryValue?: CanBeUInteger, paymentToTreasury?: CanBeUInteger } @@ -103,8 +103,26 @@ export function normalizeITxBuildArgs({ certificates: certificates?.map( normalizeITxBuildCert ), withdrawals: withdrawals?.map( normalizeITxBuildWithdrawal ), metadata, - votingProcedures: Array.isArray( votingProcedures ) ? votingProcedures.map( normalizeITxBuildVotingProcedure ) : undefined, - proposalProcedures: Array.isArray( proposalProcedures ) ? proposalProcedures.map( normalizeITxBuildProposalProcedure ) : undefined, + votingProcedures: + Array.isArray( votingProcedures ) ? + votingProcedures.map( entry => { + if( isIVotingProceduresEntry( entry ) ) + entry = { + votingProcedure: entry, + script: undefined // for js shape optimization + }; + return normalizeITxBuildVotingProcedure( entry ); + }) : undefined, + proposalProcedures: + Array.isArray( proposalProcedures ) ? + proposalProcedures.map(entry => { + if( isIProposalProcedure( entry ) ) + entry = { + proposalProcedure: entry, + script: undefined + } + return normalizeITxBuildProposalProcedure( entry ); + }) : undefined, currentTreasuryValue: currentTreasuryValue === undefined ? undefined : BigInt( currentTreasuryValue ), paymentToTreasury: paymentToTreasury === undefined ? undefined : BigInt( paymentToTreasury ), }; diff --git a/packages/offchain/src/txBuild/ITxBuildInput/ITxBuildInput.ts b/packages/offchain/src/txBuild/ITxBuildInput/ITxBuildInput.ts index 375d28fb..48173a8b 100644 --- a/packages/offchain/src/txBuild/ITxBuildInput/ITxBuildInput.ts +++ b/packages/offchain/src/txBuild/ITxBuildInput/ITxBuildInput.ts @@ -7,23 +7,29 @@ import { ITxBuildInputInlineScript, NormalizedITxBuildInputInlineScript, normali export interface ITxBuildInput { utxo: IUTxO, + /** @deprecated use `referenceScript` instead */ referenceScriptV2?: ITxBuildInputRefScript + referenceScript?: ITxBuildInputRefScript inputScript?: ITxBuildInputInlineScript } export interface NormalizedITxBuildInput extends ITxBuildInput { utxo: UTxO, - referenceScriptV2?: NormalizedITxBuildInputRefScript + referenceScript?: NormalizedITxBuildInputRefScript inputScript?: NormalizedITxBuildInputInlineScript } export function normalizeITxBuildInput( input: ITxBuildInput ): NormalizedITxBuildInput { + input = { ...input }; // do not modify input object. const result: NormalizedITxBuildInput = {} as any; + if( !input.referenceScript && input.referenceScriptV2 ) + input.referenceScript = input.referenceScriptV2; // support deprecated name, but do not override + result.utxo = input.utxo instanceof UTxO ? input.utxo.clone() : new UTxO( input.utxo ); - result.referenceScriptV2 = input.referenceScriptV2 ? - normalizeITxBuildInputRefScript( input.referenceScriptV2 ) : + result.referenceScript = input.referenceScript ? + normalizeITxBuildInputRefScript( input.referenceScript ) : undefined; result.inputScript = input.inputScript ? normalizeITxBuildInputInlineScript( input.inputScript ) : @@ -38,11 +44,11 @@ export function normalizeITxBuildInput( input: ITxBuildInput ): NormalizedITxBui */ export function cloneITxBuildInput({ utxo, - referenceScriptV2: ref, + referenceScript: ref, inputScript: inScript }: ITxBuildInput ): ITxBuildInput { - const referenceScriptV2: ITxBuildInputRefScript | undefined = ref === undefined ? undefined : + const referenceScript: ITxBuildInputRefScript | undefined = ref === undefined ? undefined : { refUtxo: new UTxO( ref.refUtxo ), datum: ref.datum === "inline" ? "inline" : cloneCanBeData( ref.datum ), @@ -58,7 +64,7 @@ export function cloneITxBuildInput({ return { utxo: new UTxO( utxo ), - referenceScriptV2, + referenceScript, inputScript } } \ No newline at end of file diff --git a/packages/offchain/src/txBuild/ITxBuildVotingProcedure.ts b/packages/offchain/src/txBuild/ITxBuildVotingProcedure.ts index e09deda3..c8672d9d 100644 --- a/packages/offchain/src/txBuild/ITxBuildVotingProcedure.ts +++ b/packages/offchain/src/txBuild/ITxBuildVotingProcedure.ts @@ -32,10 +32,7 @@ export function eqIVoter( a: IVoter, b: IVoter ): boolean export function normalizeITxBuildVotingProcedure({ votingProcedure, script }: ITxBuildVotingProcedure): NormalizedITxBuildVotingProcedure { return { - votingProcedure: { - voter: new Voter( votingProcedure.voter ), - votes: votingProcedure.votes.map( normalizeVotesEntry ) - }, + votingProcedure: normalizeVotingProcedureEntry( votingProcedure ), script: script === undefined ? undefined : normalizeIScriptWithRedeemer( script ) }; } @@ -55,4 +52,12 @@ function normalizeVotesEntry({ govActionId: forceTxOutRef( govActionId ), vote: new VotingProcedure( vote ) }; +} + +export function normalizeVotingProcedureEntry( votingProcedure: IVotingProceduresEntry ): ITypedVotingProceduresEntry +{ + return { + voter: new Voter( votingProcedure.voter ), + votes: votingProcedure.votes.map( normalizeVotesEntry ) + }; } \ No newline at end of file diff --git a/packages/onchain/src/IR/toUPLC/subRoutines/replaceNatives/nativeToIR.ts b/packages/onchain/src/IR/toUPLC/subRoutines/replaceNatives/nativeToIR.ts index 93a6cdcc..3b4f72ec 100644 --- a/packages/onchain/src/IR/toUPLC/subRoutines/replaceNatives/nativeToIR.ts +++ b/packages/onchain/src/IR/toUPLC/subRoutines/replaceNatives/nativeToIR.ts @@ -12,7 +12,7 @@ import { IRVar } from "../../../IRNodes/IRVar"; import { IRTerm } from "../../../IRTerm"; import { _ir_apps } from "../../../tree_utils/_ir_apps"; -const innerZ = new IRFunc( 1, +const innerZ = new IRFunc( 1, // f new IRApp( new IRVar( 1 ), // Z new IRFunc( 1, @@ -28,7 +28,7 @@ const innerZ = new IRFunc( 1, ); const hoisted_z_comb = new IRHoisted( - new IRFunc( 1, + new IRFunc( 1, // Z new IRApp( innerZ.clone(), innerZ.clone() diff --git a/packages/onchain/src/pluts/API/V1/PDCert/index.ts b/packages/onchain/src/pluts/API/V1/PDCert/index.ts index a3f56541..e5d4bef7 100644 --- a/packages/onchain/src/pluts/API/V1/PDCert/index.ts +++ b/packages/onchain/src/pluts/API/V1/PDCert/index.ts @@ -12,7 +12,7 @@ export const PDCert = pstruct({ }, PoolRegistration: { poolId: PPubKeyHash.type, - poolVFR: PPubKeyHash.type, + poolVRF: PPubKeyHash.type, }, PoolRetire: { poolId: PPubKeyHash.type, diff --git a/packages/onchain/src/pluts/API/V3/PDRep.ts b/packages/onchain/src/pluts/API/V3/PDRep.ts new file mode 100644 index 00000000..2a0e6ac7 --- /dev/null +++ b/packages/onchain/src/pluts/API/V3/PDRep.ts @@ -0,0 +1,8 @@ +import { pstruct } from "../../PTypes"; +import { PCredential } from "../V1"; + +export const PDrep = pstruct({ + DRep: { credentials: PCredential.type }, + AlwaysAbstain: {}, + AlwaysNoConfidence: {} +}); \ No newline at end of file diff --git a/packages/onchain/src/pluts/API/V3/PDelegatee.ts b/packages/onchain/src/pluts/API/V3/PDelegatee.ts new file mode 100644 index 00000000..7ce67abe --- /dev/null +++ b/packages/onchain/src/pluts/API/V3/PDelegatee.ts @@ -0,0 +1,9 @@ +import { pstruct } from "../../PTypes/PStruct/pstruct"; +import { PPubKeyHash } from "../V1/PubKey/PPubKeyHash"; +import { PDrep } from "./PDRep"; + +export const PDelegatee = pstruct({ + DelegStake: { poolId: PPubKeyHash.type }, + DelegVote: { drep: PDrep.type }, + DelegStakeVote: { poolId: PPubKeyHash.type, drep: PDrep.type } +}); \ No newline at end of file diff --git a/packages/onchain/src/pluts/API/V3/ScriptContext/PTxInfo/PCertificate.ts b/packages/onchain/src/pluts/API/V3/ScriptContext/PTxInfo/PCertificate.ts new file mode 100644 index 00000000..2ee707ba --- /dev/null +++ b/packages/onchain/src/pluts/API/V3/ScriptContext/PTxInfo/PCertificate.ts @@ -0,0 +1,54 @@ +import { pstruct } from "../../../../PTypes/PStruct/pstruct"; +import { PMaybe } from "../../../../lib/std/PMaybe/PMaybe"; +import { int } from "../../../../type_system/types"; +import { PCredential } from "../../../V1/Address/PCredential"; +import { PPubKeyHash } from "../../../V1/PubKey/PPubKeyHash"; +import { PDelegatee } from "../../PDelegatee"; + +const PMaybeInt = PMaybe( int ); + +export const PCertificate = pstruct({ + StakeRegistration: { + stakeKey: PCredential.type, + deposit: PMaybeInt.type + }, + StakeDeRegistration: { + stakeKey: PCredential.type, + refound: PMaybeInt.type + }, + Delegation: { + delegator: PCredential.type, + delegatee: PDelegatee.type + }, + RegistrationAndDelegation: { + delegator: PCredential.type, + delegatee: PDelegatee.type, + lovelacesDeposit: int + }, + DRepRegistration: { + drep: PCredential.type, + lovelacesDeposit: int + }, + DRepUpdate: { + drep: PCredential.type + }, + DRepDeRegistration: { + drep: PCredential.type, + refound: int + }, + PoolRegistration: { + poolId: PPubKeyHash.type, + poolVFR: PPubKeyHash.type, + }, + PoolRetire: { + poolId: PPubKeyHash.type, + epoch: int, // epoch + }, + CommitteeHotAuthorization: { + cold: PCredential.type, + hot: PCredential.type + }, + CommitteeResignation: { + cold: PCredential.type + } +}); \ No newline at end of file diff --git a/packages/onchain/src/pluts/lib/std/UtilityTerms/TermList.ts b/packages/onchain/src/pluts/lib/std/UtilityTerms/TermList.ts index a279059d..0a84af8e 100644 --- a/packages/onchain/src/pluts/lib/std/UtilityTerms/TermList.ts +++ b/packages/onchain/src/pluts/lib/std/UtilityTerms/TermList.ts @@ -26,8 +26,27 @@ import { TermBool } from "./TermBool"; import { TermInt } from "./TermInt"; import { peqList, pincludes, plookup } from "../list"; +function* fixedLengthIter( + list: TermList, + maxLength: number +): Generator, void, unknown> +{ + if( !Number.isSafeInteger( maxLength ) ) + throw new Error("max length for 'fixedLengthIterable' is not an integer"); + + if( maxLength < 0 ) maxLength = -maxLength; + + for( let i = 0; i < maxLength; i++ ) + { + yield list.head; + list = list.tail; + } +} + export type TermList = Term> & { + fixedLengthIterable: ( maxLength: number ) => Generator, void, unknown> + /** * **O(1)** * @@ -198,6 +217,15 @@ export function addPListMethods( _lst: Term( lst: Term>, elemsT: TermType ): void { + defineReadOnlyProperty( + lst, + "fixedLengthIterable", + // as type conversion is fine because this is a funciton + // and will only always be accessed after this function has + // defined the necessary methods for "TermList" to be satisfied + ( max: number ) => fixedLengthIter( lst as TermList, max ) + ); + definePropertyIfNotPresent( lst, "head", diff --git a/packages/onchain/src/pluts/lib/std/list/pindexList.ts b/packages/onchain/src/pluts/lib/std/list/pindexList.ts index 11e6b47a..eea3da4b 100644 --- a/packages/onchain/src/pluts/lib/std/list/pindexList.ts +++ b/packages/onchain/src/pluts/lib/std/list/pindexList.ts @@ -22,6 +22,7 @@ export function pindexList( elemsT: ElemsT ) int ],elemsT) (( self, list, idx ) => + // TODO this "pif" is useless pif( elemsT ).$( pisEmpty.$( list )