From c02a886aef3da30c0a74867380bb02bef65bf4aa Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Fri, 12 Sep 2025 08:24:25 -0400 Subject: [PATCH] refactor: split script purpose to ScriptPurpose and ScriptInfo Fixes #1185 Signed-off-by: Aurora Gaffney --- ledger/common/script/context.go | 37 +++---- ledger/common/script/context_test.go | 11 +- ledger/common/script/purpose.go | 159 ++++++++++++++++++--------- 3 files changed, 132 insertions(+), 75 deletions(-) diff --git a/ledger/common/script/context.go b/ledger/common/script/context.go index 123e5fdc..4e0fbdfd 100644 --- a/ledger/common/script/context.go +++ b/ledger/common/script/context.go @@ -41,9 +41,9 @@ func (s ScriptContextV1V2) ToPlutusData() data.PlutusData { } type ScriptContextV3 struct { - TxInfo TxInfo - Redeemer Redeemer - Purpose ScriptInfo + TxInfo TxInfo + Redeemer Redeemer + ScriptInfo ScriptInfo } func (ScriptContextV3) isScriptContext() {} @@ -53,19 +53,19 @@ func (s ScriptContextV3) ToPlutusData() data.PlutusData { 0, s.TxInfo.ToPlutusData(), s.Redeemer.ToPlutusData(), - s.Purpose.ToPlutusData(), + s.ScriptInfo.ToPlutusData(), ) } func NewScriptContextV3( txInfo TxInfo, redeemer Redeemer, - purpose ScriptInfo, + purpose ScriptPurpose, ) ScriptContext { return ScriptContextV3{ - TxInfo: txInfo, - Redeemer: redeemer, - Purpose: purpose, + TxInfo: txInfo, + Redeemer: redeemer, + ScriptInfo: purpose.ToScriptInfo(), } } @@ -84,7 +84,7 @@ type TxInfoV1 struct { ValidRange TimeRange Signatories []lcommon.Blake2b224 Data KeyValuePairs[lcommon.Blake2b256, data.PlutusData] - Redeemers KeyValuePairs[ScriptInfo, Redeemer] + Redeemers KeyValuePairs[ScriptPurpose, Redeemer] Id lcommon.Blake2b256 } @@ -105,7 +105,7 @@ type TxInfoV2 struct { Withdrawals KeyValuePairs[*lcommon.Address, Coin] ValidRange TimeRange Signatories []lcommon.Blake2b224 - Redeemers KeyValuePairs[ScriptInfo, Redeemer] + Redeemers KeyValuePairs[ScriptPurpose, Redeemer] Data KeyValuePairs[lcommon.Blake2b256, data.PlutusData] Id lcommon.Blake2b256 } @@ -127,7 +127,7 @@ type TxInfoV3 struct { Withdrawals KeyValuePairs[*lcommon.Address, uint64] ValidRange TimeRange Signatories []lcommon.Blake2b224 - Redeemers KeyValuePairs[ScriptInfo, Redeemer] + Redeemers KeyValuePairs[ScriptPurpose, Redeemer] Data KeyValuePairs[lcommon.Blake2b256, data.PlutusData] Id lcommon.Blake2b256 Votes KeyValuePairs[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]] @@ -139,13 +139,6 @@ type TxInfoV3 struct { func (TxInfoV3) isTxInfo() {} func (t TxInfoV3) ToPlutusData() data.PlutusData { - tmpRedeemers := make(KeyValuePairs[ScriptInfo, Redeemer], len(t.Redeemers)) - for i, pair := range t.Redeemers { - tmpRedeemers[i] = KeyValuePair[ScriptInfo, Redeemer]{ - Key: scriptPurposeStripDatum(pair.Key), - Value: pair.Value, - } - } return data.NewConstr( 0, toPlutusData(t.Inputs), @@ -157,7 +150,7 @@ func (t TxInfoV3) ToPlutusData() data.PlutusData { toPlutusData(t.Withdrawals), t.ValidRange.ToPlutusData(), toPlutusData(t.Signatories), - tmpRedeemers.ToPlutusData(), + t.Redeemers.ToPlutusData(), t.Data.ToPlutusData(), data.NewByteString(t.Id.Bytes()), t.Votes.ToPlutusData(), @@ -417,8 +410,8 @@ func dataInfo( func redeemersInfo( witnessSet lcommon.TransactionWitnessSet, toScriptPurpose toScriptPurposeFunc, -) KeyValuePairs[ScriptInfo, Redeemer] { - var ret KeyValuePairs[ScriptInfo, Redeemer] +) KeyValuePairs[ScriptPurpose, Redeemer] { + var ret KeyValuePairs[ScriptPurpose, Redeemer] redeemers := witnessSet.Redeemers() redeemerKeys := sortedRedeemerKeys(redeemers) for _, key := range redeemerKeys { @@ -426,7 +419,7 @@ func redeemersInfo( purpose := toScriptPurpose(key) ret = append( ret, - KeyValuePair[ScriptInfo, Redeemer]{ + KeyValuePair[ScriptPurpose, Redeemer]{ Key: purpose, Value: Redeemer{ Tag: key.Tag, diff --git a/ledger/common/script/context_test.go b/ledger/common/script/context_test.go index 1487ae5c..7abd302b 100644 --- a/ledger/common/script/context_test.go +++ b/ledger/common/script/context_test.go @@ -265,6 +265,15 @@ var scriptContextV3TestDefs = []struct { redeemerIndex: 0, expectedCbor: "d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87980d87a80ffffff8080182aa080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a1d87e9f00d8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd8799fd87a80b81d00182c011a00025ef5021a00016000031940000419044c051a001e8480061a1dcd65000712081901f4099f030aff0a9f031903e8ff0b9f0105ff10190154111910d6139f9f1902411903e8ff9f1902d11a000f4240ffff149f1a00d59f801b00000002540be400ff159f1a03b20b801b00000004a817c800ff1619138817189618180318199f9f18331864ff9f0d1819ff9f18351864ff9f181b1832ff9f0b14ffff181a9f9f18431864ff9f18431864ff9f0305ff9f0304ff9f0305ff9f18431864ff9f18431864ff9f18431864ff9f0304ff9f18431864ffff181b07181c1892181d06181e1b000000174876e800181f1a1dcd650018201418219f0f01ffd8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffd87980a05820a88a9bf8af51ea738bf212fe204e5ef2fd05312c995dad1ec194dcdc5aa07737a09fd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd8799fd87a80b81d00182c011a00025ef5021a00016000031940000419044c051a001e8480061a1dcd65000712081901f4099f030aff0a9f031903e8ff0b9f0105ff10190154111910d6139f9f1902411903e8ff9f1902d11a000f4240ffff149f1a00d59f801b00000002540be400ff159f1a03b20b801b00000004a817c800ff1619138817189618180318199f9f18331864ff9f0d1819ff9f18351864ff9f181b1832ff9f0b14ffff181a9f9f18431864ff9f18431864ff9f0305ff9f0304ff9f0305ff9f18431864ff9f18431864ff9f18431864ff9f0304ff9f18431864ffff181b07181c1892181d06181e1b000000174876e800181f1a1dcd650018201418219f0f01ffd8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffd87a80d87a80ffd87980d87e9f00d8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd8799fd87a80b81d00182c011a00025ef5021a00016000031940000419044c051a001e8480061a1dcd65000712081901f4099f030aff0a9f031903e8ff0b9f0105ff10190154111910d6139f9f1902411903e8ff9f1902d11a000f4240ffff149f1a00d59f801b00000002540be400ff159f1a03b20b801b00000004a817c800ff1619138817189618180318199f9f18331864ff9f0d1819ff9f18351864ff9f181b1832ff9f0b14ffff181a9f9f18431864ff9f18431864ff9f0305ff9f0304ff9f0305ff9f18431864ff9f18431864ff9f18431864ff9f0304ff9f18431864ffff181b07181c1892181d06181e1b000000174876e800181f1a1dcd650018201418219f0f01ffd8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffff", }, + { + name: "SpendNoDatum", + txHex: "84a800d9010281825820757529554ac6d89833713481e4bf1d3ac3923a2fd1367703f5c855245666cdb3000dd9010281825820757529554ac6d89833713481e4bf1d3ac3923a2fd1367703f5c855245666cdb30112d9010281825820796c9a7ddf7d282425cfc04502cbf6afeaa4ba84d7ac57b898af5d11971a904f00018182581d60f403040f87d20a97bb16c3cc0b49bd057850e1c1f61eaf9c6d9ebb8d1a004990371082581d60f403040f87d20a97bb16c3cc0b49bd057850e1c1f61eaf9c6d9ebb8d1b0000000252d15b28111a0004188e021a0002bb090b58207b8920e8bdd120024a862d4b3f04abbb45553b63eaf2d44110d704c1bb3b970fa200d90102818258209fb8feba96ebf9daf366bbd8267af4ac90700e7293e8a28980318c8b15ec22305840bf637c3a878b101045885d6b3c339dc90c06d50aedda8b4e6518409ae1f1a3b1c549cfc87b73b4af7daf9024d1402ef05a5d3db170542bc3a2824a997a82550a05a182000082d8799f182aff8219286f1a002cc0b1f5f6", + inputsHex: "82825820757529554ac6d89833713481e4bf1d3ac3923a2fd1367703f5c855245666cdb300825820796c9a7ddf7d282425cfc04502cbf6afeaa4ba84d7ac57b898af5d11971a904f00", + outputsHex: "8282581d703f919a6912708379420f321462ffd069b9ca06dabd8748c0015dc5df1a004c4b40a300581d60f403040f87d20a97bb16c3cc0b49bd057850e1c1f61eaf9c6d9ebb8d011a00e4e1c003d818585d820358595857010100323232323225333002323232323253330073370e900118041baa00113232324a26018601a004601600260126ea800458c024c028008c020004c020008c018004c010dd50008a4c26cacae6955ceaab9e5742ae89", + redeemerTag: lcommon.RedeemerTagSpend, + redeemerIndex: 0, + expectedCbor: "d8799fd8799f9fd8799fd8799f5820757529554ac6d89833713481e4bf1d3ac3923a2fd1367703f5c855245666cdb300ffd8799fd8799fd87a9f581c3f919a6912708379420f321462ffd069b9ca06dabd8748c0015dc5dfffd87a80ffa140a1401a004c4b40d87980d87a80ffffff9fd8799fd8799f5820796c9a7ddf7d282425cfc04502cbf6afeaa4ba84d7ac57b898af5d11971a904f00ffd8799fd8799fd8799f581cf403040f87d20a97bb16c3cc0b49bd057850e1c1f61eaf9c6d9ebb8dffd87a80ffa140a1401a00e4e1c0d87980d8799f581c3f919a6912708379420f321462ffd069b9ca06dabd8748c0015dc5dfffffffff9fd8799fd8799fd8799f581cf403040f87d20a97bb16c3cc0b49bd057850e1c1f61eaf9c6d9ebb8dffd87a80ffa140a1401a00499037d87980d87a80ffff1a0002bb09a080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a1d87a9fd8799f5820757529554ac6d89833713481e4bf1d3ac3923a2fd1367703f5c855245666cdb300ffffd8799f182affa05820b7532002df0695ced267eddd58445c66a04c8ddec485adabfca3eb90f5136d8ca080d87a80d87a80ffd8799f182affd87a9fd8799f5820757529554ac6d89833713481e4bf1d3ac3923a2fd1367703f5c855245666cdb300ffd87a80ffff", + }, } func TestScriptContextV3(t *testing.T) { @@ -283,7 +292,7 @@ func TestScriptContextV3(t *testing.T) { } // Extract purpose and redeemer from TxInfo - var purpose ScriptInfo + var purpose ScriptPurpose var redeemer Redeemer for _, redeemerPair := range txInfo.(TxInfoV3).Redeemers { if redeemerPair.Value.Tag == testDef.redeemerTag && diff --git a/ledger/common/script/purpose.go b/ledger/common/script/purpose.go index fdef0a3a..7db8018b 100644 --- a/ledger/common/script/purpose.go +++ b/ledger/common/script/purpose.go @@ -24,83 +24,92 @@ import ( "github.com/blinklabs-io/plutigo/data" ) +type ScriptPurpose interface { + isScriptPurpose() + ScriptHash() lcommon.ScriptHash + ToScriptInfo() ScriptInfo + ToPlutusData +} + type ScriptInfo interface { isScriptInfo() ScriptHash() lcommon.ScriptHash ToPlutusData } -type ScriptInfoMinting struct { +type ScriptPurposeMinting struct { PolicyId lcommon.Blake2b224 } -func (ScriptInfoMinting) isScriptInfo() {} +func (ScriptPurposeMinting) isScriptPurpose() {} -func (s ScriptInfoMinting) ScriptHash() lcommon.ScriptHash { +func (s ScriptPurposeMinting) ScriptHash() lcommon.ScriptHash { return s.PolicyId } -func (s ScriptInfoMinting) ToPlutusData() data.PlutusData { +func (s ScriptPurposeMinting) ToPlutusData() data.PlutusData { return data.NewConstr( 0, data.NewByteString(s.PolicyId.Bytes()), ) } -type ScriptInfoSpending struct { +func (s ScriptPurposeMinting) ToScriptInfo() ScriptInfo { + return ScriptInfoMinting{s} +} + +type ScriptPurposeSpending struct { Input lcommon.Utxo Datum data.PlutusData } -func (ScriptInfoSpending) isScriptInfo() {} +func (ScriptPurposeSpending) isScriptPurpose() {} -func (s ScriptInfoSpending) ScriptHash() lcommon.ScriptHash { +func (s ScriptPurposeSpending) ScriptHash() lcommon.ScriptHash { tmpAddr := s.Input.Output.Address() return tmpAddr.PaymentKeyHash() } -func (s ScriptInfoSpending) ToPlutusData() data.PlutusData { - if s.Datum == nil { - return data.NewConstr( - 1, - s.Input.Id.ToPlutusData(), - ) - } +func (s ScriptPurposeSpending) ToPlutusData() data.PlutusData { return data.NewConstr( 1, s.Input.Id.ToPlutusData(), - data.NewConstr( - 0, - s.Datum, - ), ) } -type ScriptInfoRewarding struct { +func (s ScriptPurposeSpending) ToScriptInfo() ScriptInfo { + return ScriptInfoSpending{s} +} + +type ScriptPurposeRewarding struct { StakeCredential lcommon.Credential } -func (ScriptInfoRewarding) isScriptInfo() {} +func (ScriptPurposeRewarding) isScriptPurpose() {} -func (s ScriptInfoRewarding) ScriptHash() lcommon.ScriptHash { +func (s ScriptPurposeRewarding) ScriptHash() lcommon.ScriptHash { return lcommon.ScriptHash(s.StakeCredential.Credential) } -func (s ScriptInfoRewarding) ToPlutusData() data.PlutusData { +func (s ScriptPurposeRewarding) ToPlutusData() data.PlutusData { return data.NewConstr( 2, s.StakeCredential.ToPlutusData(), ) } -type ScriptInfoCertifying struct { +func (s ScriptPurposeRewarding) ToScriptInfo() ScriptInfo { + return ScriptInfoRewarding{s} +} + +type ScriptPurposeCertifying struct { Index uint32 Certificate lcommon.Certificate } -func (ScriptInfoCertifying) isScriptInfo() {} +func (ScriptPurposeCertifying) isScriptPurpose() {} -func (s ScriptInfoCertifying) ScriptHash() lcommon.ScriptHash { +func (s ScriptPurposeCertifying) ScriptHash() lcommon.ScriptHash { var cred *lcommon.Credential switch c := s.Certificate.(type) { case *lcommon.StakeDeregistrationCertificate: @@ -140,7 +149,7 @@ func (s ScriptInfoCertifying) ScriptHash() lcommon.ScriptHash { return lcommon.ScriptHash{} } -func (s ScriptInfoCertifying) ToPlutusData() data.PlutusData { +func (s ScriptPurposeCertifying) ToPlutusData() data.PlutusData { return data.NewConstr( 3, data.NewInteger(new(big.Int).SetUint64(uint64(s.Index))), @@ -148,31 +157,39 @@ func (s ScriptInfoCertifying) ToPlutusData() data.PlutusData { ) } -type ScriptInfoVoting struct { +func (s ScriptPurposeCertifying) ToScriptInfo() ScriptInfo { + return ScriptInfoCertifying{s} +} + +type ScriptPurposeVoting struct { Voter lcommon.Voter } -func (ScriptInfoVoting) isScriptInfo() {} +func (ScriptPurposeVoting) isScriptPurpose() {} -func (s ScriptInfoVoting) ScriptHash() lcommon.ScriptHash { +func (s ScriptPurposeVoting) ScriptHash() lcommon.ScriptHash { return lcommon.ScriptHash(s.Voter.Hash[:]) } -func (s ScriptInfoVoting) ToPlutusData() data.PlutusData { +func (s ScriptPurposeVoting) ToPlutusData() data.PlutusData { return data.NewConstr( 4, s.Voter.ToPlutusData(), ) } -type ScriptInfoProposing struct { +func (s ScriptPurposeVoting) ToScriptInfo() ScriptInfo { + return ScriptInfoVoting{s} +} + +type ScriptPurposeProposing struct { Index uint32 ProposalProcedure lcommon.ProposalProcedure } -func (ScriptInfoProposing) isScriptInfo() {} +func (ScriptPurposeProposing) isScriptPurpose() {} -func (s ScriptInfoProposing) ScriptHash() lcommon.ScriptHash { +func (s ScriptPurposeProposing) ScriptHash() lcommon.ScriptHash { switch a := s.ProposalProcedure.GovAction().(type) { case *conway.ConwayParameterChangeGovAction: return lcommon.ScriptHash(a.PolicyHash) @@ -182,7 +199,7 @@ func (s ScriptInfoProposing) ScriptHash() lcommon.ScriptHash { return lcommon.ScriptHash{} } -func (s ScriptInfoProposing) ToPlutusData() data.PlutusData { +func (s ScriptPurposeProposing) ToPlutusData() data.PlutusData { return data.NewConstr( 5, toPlutusData(uint64(s.Index)), @@ -190,7 +207,55 @@ func (s ScriptInfoProposing) ToPlutusData() data.PlutusData { ) } -type toScriptPurposeFunc func(lcommon.RedeemerKey) ScriptInfo +func (s ScriptPurposeProposing) ToScriptInfo() ScriptInfo { + return ScriptInfoProposing{s} +} + +type ScriptInfoSpending struct { + ScriptPurposeSpending +} + +func (ScriptInfoSpending) isScriptInfo() {} + +func (s ScriptInfoSpending) ToPlutusData() data.PlutusData { + return data.NewConstr( + 1, + s.Input.Id.ToPlutusData(), + Option[data.PlutusData]{s.Datum}.ToPlutusData(), + ) +} + +type ScriptInfoMinting struct { + ScriptPurposeMinting +} + +func (ScriptInfoMinting) isScriptInfo() {} + +type ScriptInfoRewarding struct { + ScriptPurposeRewarding +} + +func (ScriptInfoRewarding) isScriptInfo() {} + +type ScriptInfoCertifying struct { + ScriptPurposeCertifying +} + +func (ScriptInfoCertifying) isScriptInfo() {} + +type ScriptInfoVoting struct { + ScriptPurposeVoting +} + +func (ScriptInfoVoting) isScriptInfo() {} + +type ScriptInfoProposing struct { + ScriptPurposeProposing +} + +func (ScriptInfoProposing) isScriptInfo() {} + +type toScriptPurposeFunc func(lcommon.RedeemerKey) ScriptPurpose // scriptPurposeBuilder creates a reusable function preloaded with information about a particular transaction func scriptPurposeBuilder( @@ -202,7 +267,7 @@ func scriptPurposeBuilder( votes KeyValuePairs[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]], proposalProcedures []lcommon.ProposalProcedure, ) toScriptPurposeFunc { - return func(redeemerKey lcommon.RedeemerKey) ScriptInfo { + return func(redeemerKey lcommon.RedeemerKey) ScriptPurpose { // TODO: implement additional redeemer tags // https://github.com/aiken-lang/aiken/blob/af4e04b91e54dbba3340de03fc9e65a90f24a93b/crates/uplc/src/tx/script_context.rs#L771-L826 switch redeemerKey.Tag { @@ -219,38 +284,37 @@ func scriptPurposeBuilder( break } } - return ScriptInfoSpending{ + return ScriptPurposeSpending{ Input: resolvedInput, Datum: datum, } case lcommon.RedeemerTagMint: - // TODO: fix this to work for more than one minted policy mintPolicies := mint.Policies() slices.SortFunc( mintPolicies, func(a, b lcommon.Blake2b224) int { return bytes.Compare(a.Bytes(), b.Bytes()) }, ) - return ScriptInfoMinting{ + return ScriptPurposeMinting{ PolicyId: mintPolicies[redeemerKey.Index], } case lcommon.RedeemerTagCert: - return ScriptInfoCertifying{ + return ScriptPurposeCertifying{ Index: redeemerKey.Index, Certificate: certificates[redeemerKey.Index], } case lcommon.RedeemerTagReward: - return ScriptInfoRewarding{ + return ScriptPurposeRewarding{ StakeCredential: lcommon.Credential{ CredType: lcommon.CredentialTypeScriptHash, Credential: withdrawals[redeemerKey.Index].Key.StakeKeyHash(), }, } case lcommon.RedeemerTagVoting: - return ScriptInfoVoting{ + return ScriptPurposeVoting{ Voter: *(votes[redeemerKey.Index].Key), } case lcommon.RedeemerTagProposing: - return ScriptInfoProposing{ + return ScriptPurposeProposing{ Index: redeemerKey.Index, ProposalProcedure: proposalProcedures[redeemerKey.Index], } @@ -258,12 +322,3 @@ func scriptPurposeBuilder( return nil } } - -func scriptPurposeStripDatum(purpose ScriptInfo) ScriptInfo { - switch p := purpose.(type) { - case ScriptInfoSpending: - p.Datum = nil - return p - } - return purpose -}