diff --git a/ledger/common/script/scriptcontext.go b/ledger/common/script/context.go similarity index 73% rename from ledger/common/script/scriptcontext.go rename to ledger/common/script/context.go index 1532ca2c..3f5d974f 100644 --- a/ledger/common/script/scriptcontext.go +++ b/ledger/common/script/context.go @@ -258,94 +258,6 @@ func (t TimeRange) ToPlutusData() data.PlutusData { ) } -type ScriptInfo interface { - isScriptInfo() - ToPlutusData -} - -type ScriptInfoMinting struct { - PolicyId lcommon.Blake2b224 -} - -func (ScriptInfoMinting) isScriptInfo() {} - -func (s ScriptInfoMinting) ToPlutusData() data.PlutusData { - return data.NewConstr( - 0, - data.NewByteString(s.PolicyId.Bytes()), - ) -} - -type ScriptInfoSpending struct { - Input lcommon.TransactionInput - Datum data.PlutusData -} - -func (ScriptInfoSpending) isScriptInfo() {} - -func (s ScriptInfoSpending) ToPlutusData() data.PlutusData { - if s.Datum == nil { - return data.NewConstr( - 1, - s.Input.ToPlutusData(), - ) - } - return data.NewConstr( - 1, - s.Input.ToPlutusData(), - data.NewConstr( - 0, - s.Datum, - ), - ) -} - -type ScriptInfoRewarding struct { - StakeCredential lcommon.Credential -} - -func (ScriptInfoRewarding) isScriptInfo() {} - -func (s ScriptInfoRewarding) ToPlutusData() data.PlutusData { - // TODO - return nil -} - -type ScriptInfoCertifying struct { - Size uint64 - Certificate lcommon.Certificate -} - -func (ScriptInfoCertifying) isScriptInfo() {} - -func (s ScriptInfoCertifying) ToPlutusData() data.PlutusData { - // TODO - return nil -} - -type ScriptInfoVoting struct { - Voter lcommon.Voter -} - -func (ScriptInfoVoting) isScriptInfo() {} - -func (s ScriptInfoVoting) ToPlutusData() data.PlutusData { - // TODO - return nil -} - -type ScriptInfoProposing struct { - Size uint64 - ProposalProcedure lcommon.ProposalProcedure -} - -func (ScriptInfoProposing) isScriptInfo() {} - -func (s ScriptInfoProposing) ToPlutusData() data.PlutusData { - // TODO - return nil -} - func sortInputs(inputs []lcommon.TransactionInput) []lcommon.TransactionInput { ret := make([]lcommon.TransactionInput, len(inputs)) copy(ret, inputs) @@ -484,66 +396,3 @@ func signatoriesInfo( ) return tmp } - -type toScriptPurposeFunc func(lcommon.RedeemerKey) ScriptInfo - -// scriptPurposeBuilder creates a reusable function preloaded with information about a particular transaction -func scriptPurposeBuilder( - resolvedInputs []lcommon.Utxo, - inputs []lcommon.TransactionInput, - mint lcommon.MultiAsset[lcommon.MultiAssetTypeMint], - // TODO: certificates - withdrawals map[*lcommon.Address]uint64, - // TODO: proposal procedures - // TODO: votes -) toScriptPurposeFunc { - return func(redeemerKey lcommon.RedeemerKey) ScriptInfo { - // 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 { - case lcommon.RedeemerTagSpend: - var datum data.PlutusData - tmpInput := inputs[redeemerKey.Index] - for _, resolvedInput := range resolvedInputs { - if resolvedInput.Id.String() == tmpInput.String() { - if tmpDatum := resolvedInput.Output.Datum(); tmpDatum != nil { - datum = tmpDatum.Data - } - break - } - } - return ScriptInfoSpending{ - Input: inputs[redeemerKey.Index], - 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{ - PolicyId: mintPolicies[redeemerKey.Index], - } - case lcommon.RedeemerTagCert: - return nil - case lcommon.RedeemerTagReward: - return nil - case lcommon.RedeemerTagVoting: - return nil - case lcommon.RedeemerTagProposing: - return nil - } - return nil - } -} - -func scriptPurposeStripDatum(purpose ScriptInfo) ScriptInfo { - switch p := purpose.(type) { - case ScriptInfoSpending: - p.Datum = nil - return p - } - return purpose -} diff --git a/ledger/common/script/scriptcontext_test.go b/ledger/common/script/context_test.go similarity index 100% rename from ledger/common/script/scriptcontext_test.go rename to ledger/common/script/context_test.go diff --git a/ledger/common/script/purpose.go b/ledger/common/script/purpose.go new file mode 100644 index 00000000..46cc3aa5 --- /dev/null +++ b/ledger/common/script/purpose.go @@ -0,0 +1,206 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package script + +import ( + "bytes" + "slices" + + lcommon "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/plutigo/data" +) + +type ScriptInfo interface { + isScriptInfo() + ScriptHash() lcommon.ScriptHash + ToPlutusData +} + +type ScriptInfoMinting struct { + PolicyId lcommon.Blake2b224 +} + +func (ScriptInfoMinting) isScriptInfo() {} + +func (s ScriptInfoMinting) ScriptHash() lcommon.ScriptHash { + return s.PolicyId +} + +func (s ScriptInfoMinting) ToPlutusData() data.PlutusData { + return data.NewConstr( + 0, + data.NewByteString(s.PolicyId.Bytes()), + ) +} + +type ScriptInfoSpending struct { + Input lcommon.Utxo + Datum data.PlutusData +} + +func (ScriptInfoSpending) isScriptInfo() {} + +func (s ScriptInfoSpending) 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(), + ) + } + return data.NewConstr( + 1, + s.Input.Id.ToPlutusData(), + data.NewConstr( + 0, + s.Datum, + ), + ) +} + +type ScriptInfoRewarding struct { + StakeCredential lcommon.Credential +} + +func (ScriptInfoRewarding) isScriptInfo() {} + +func (s ScriptInfoRewarding) ScriptHash() lcommon.ScriptHash { + // TODO + return lcommon.ScriptHash{} +} + +func (s ScriptInfoRewarding) ToPlutusData() data.PlutusData { + // TODO + return nil +} + +type ScriptInfoCertifying struct { + Size uint64 + Certificate lcommon.Certificate +} + +func (ScriptInfoCertifying) isScriptInfo() {} + +func (s ScriptInfoCertifying) ScriptHash() lcommon.ScriptHash { + // TODO + return lcommon.ScriptHash{} +} + +func (s ScriptInfoCertifying) ToPlutusData() data.PlutusData { + // TODO + return nil +} + +type ScriptInfoVoting struct { + Voter lcommon.Voter +} + +func (ScriptInfoVoting) isScriptInfo() {} + +func (s ScriptInfoVoting) ScriptHash() lcommon.ScriptHash { + // TODO + return lcommon.ScriptHash{} +} + +func (s ScriptInfoVoting) ToPlutusData() data.PlutusData { + // TODO + return nil +} + +type ScriptInfoProposing struct { + Size uint64 + ProposalProcedure lcommon.ProposalProcedure +} + +func (ScriptInfoProposing) isScriptInfo() {} + +func (s ScriptInfoProposing) ScriptHash() lcommon.ScriptHash { + // TODO + return lcommon.ScriptHash{} +} + +func (s ScriptInfoProposing) ToPlutusData() data.PlutusData { + // TODO + return nil +} + +type toScriptPurposeFunc func(lcommon.RedeemerKey) ScriptInfo + +// scriptPurposeBuilder creates a reusable function preloaded with information about a particular transaction +func scriptPurposeBuilder( + resolvedInputs []lcommon.Utxo, + inputs []lcommon.TransactionInput, + mint lcommon.MultiAsset[lcommon.MultiAssetTypeMint], + // TODO: certificates + withdrawals map[*lcommon.Address]uint64, + // TODO: proposal procedures + // TODO: votes +) toScriptPurposeFunc { + return func(redeemerKey lcommon.RedeemerKey) ScriptInfo { + // 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 { + case lcommon.RedeemerTagSpend: + var datum data.PlutusData + tmpInput := inputs[redeemerKey.Index] + var resolvedInput lcommon.Utxo + for _, tmpResolvedInput := range resolvedInputs { + if tmpResolvedInput.Id.String() == tmpInput.String() { + resolvedInput = tmpResolvedInput + if tmpDatum := resolvedInput.Output.Datum(); tmpDatum != nil { + datum = tmpDatum.Data + } + break + } + } + return ScriptInfoSpending{ + 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{ + PolicyId: mintPolicies[redeemerKey.Index], + } + case lcommon.RedeemerTagCert: + return nil + case lcommon.RedeemerTagReward: + return nil + case lcommon.RedeemerTagVoting: + return nil + case lcommon.RedeemerTagProposing: + return nil + } + return nil + } +} + +func scriptPurposeStripDatum(purpose ScriptInfo) ScriptInfo { + switch p := purpose.(type) { + case ScriptInfoSpending: + p.Datum = nil + return p + } + return purpose +}