diff --git a/ledger/common/gov.go b/ledger/common/gov.go index c020ddfc..f3ca056e 100644 --- a/ledger/common/gov.go +++ b/ledger/common/gov.go @@ -15,7 +15,6 @@ package common import ( - "fmt" "math/big" "github.com/blinklabs-io/gouroboros/cbor" @@ -129,21 +128,19 @@ func (id *GovActionId) ToPlutusData() data.PlutusData { ) } -type ProposalProcedure struct { - cbor.StructAsArray - Deposit uint64 - RewardAccount Address - GovAction GovActionWrapper - Anchor GovAnchor +type ProposalProcedure interface { + isProposalProcedure() + ToPlutusData() data.PlutusData + Deposit() uint64 + RewardAccount() Address + GovAction() GovAction + Anchor() GovAnchor } -func (p *ProposalProcedure) ToPlutusData() data.PlutusData { - return data.NewConstr(0, - data.NewInteger(new(big.Int).SetUint64(p.Deposit)), - p.RewardAccount.ToPlutusData(), - p.GovAction.ToPlutusData(), - ) -} +type ProposalProcedureBase struct{} + +// nolint:unused +func (ProposalProcedureBase) isProposalProcedure() {} const ( GovActionTypeParameterChange = 0 @@ -155,76 +152,15 @@ const ( GovActionTypeInfo = 6 ) -type GovActionWrapper struct { - Type uint - Action GovAction -} - -func (g *GovActionWrapper) ToPlutusData() data.PlutusData { - return g.Action.ToPlutusData() -} - -func (g *GovActionWrapper) UnmarshalCBOR(data []byte) error { - // Determine action type - actionType, err := cbor.DecodeIdFromList(data) - if err != nil { - return err - } - var tmpAction GovAction - switch actionType { - case GovActionTypeParameterChange: - tmpAction = &ParameterChangeGovAction{} - case GovActionTypeHardForkInitiation: - tmpAction = &HardForkInitiationGovAction{} - case GovActionTypeTreasuryWithdrawal: - tmpAction = &TreasuryWithdrawalGovAction{} - case GovActionTypeNoConfidence: - tmpAction = &NoConfidenceGovAction{} - case GovActionTypeUpdateCommittee: - tmpAction = &UpdateCommitteeGovAction{} - case GovActionTypeNewConstitution: - tmpAction = &NewConstitutionGovAction{} - case GovActionTypeInfo: - tmpAction = &InfoGovAction{} - default: - return fmt.Errorf("unknown governance action type: %d", actionType) - } - // Decode action - if _, err := cbor.Decode(data, tmpAction); err != nil { - return err - } - // action type is known within uint range - g.Type = uint(actionType) // #nosec G115 - g.Action = tmpAction - return nil -} - -func (g *GovActionWrapper) MarshalCBOR() ([]byte, error) { - return cbor.Encode(g.Action) -} - type GovAction interface { isGovAction() ToPlutusData() data.PlutusData } -type ParameterChangeGovAction struct { - cbor.StructAsArray - Type uint - ActionId *GovActionId - ParamUpdate cbor.RawMessage // NOTE: we use raw to defer processing to account for per-era types - PolicyHash []byte -} - -func (a *ParameterChangeGovAction) ToPlutusData() data.PlutusData { - return data.NewConstr(0, - a.ActionId.ToPlutusData(), - data.NewByteString(a.ParamUpdate), - data.NewByteString(a.PolicyHash), - ) -} +type GovActionBase struct{} -func (a ParameterChangeGovAction) isGovAction() {} +// nolint:unused +func (GovActionBase) isGovAction() {} type HardForkInitiationGovAction struct { cbor.StructAsArray @@ -238,8 +174,12 @@ type HardForkInitiationGovAction struct { } func (a *HardForkInitiationGovAction) ToPlutusData() data.PlutusData { + actionId := data.NewConstr(1) + if a.ActionId != nil { + actionId = data.NewConstr(0, a.ActionId.ToPlutusData()) + } return data.NewConstr(1, - a.ActionId.ToPlutusData(), + actionId, data.NewConstr( 0, data.NewInteger( @@ -265,13 +205,20 @@ func (a *TreasuryWithdrawalGovAction) ToPlutusData() data.PlutusData { pairs := make([][2]data.PlutusData, 0, len(a.Withdrawals)) for addr, amount := range a.Withdrawals { pairs = append(pairs, [2]data.PlutusData{ - data.NewConstr(0, addr.ToPlutusData()), + addr.ToPlutusData(), data.NewInteger(new(big.Int).SetUint64(amount)), }) } + policyHash := data.NewConstr(1) + if len(a.PolicyHash) > 0 { + policyHash = data.NewConstr( + 0, + data.NewByteString(a.PolicyHash), + ) + } return data.NewConstr(2, data.NewMap(pairs), - data.NewByteString(a.PolicyHash), + policyHash, ) } @@ -284,8 +231,12 @@ type NoConfidenceGovAction struct { } func (a *NoConfidenceGovAction) ToPlutusData() data.PlutusData { + actionId := data.NewConstr(1) + if a.ActionId != nil { + actionId = data.NewConstr(0, a.ActionId.ToPlutusData()) + } return data.NewConstr(3, - a.ActionId.ToPlutusData(), + actionId, ) } @@ -301,6 +252,10 @@ type UpdateCommitteeGovAction struct { } func (a *UpdateCommitteeGovAction) ToPlutusData() data.PlutusData { + actionId := data.NewConstr(1) + if a.ActionId != nil { + actionId = data.NewConstr(0, a.ActionId.ToPlutusData()) + } removedItems := make([]data.PlutusData, 0, len(a.Credentials)) for _, cred := range a.Credentials { removedItems = append(removedItems, cred.ToPlutusData()) @@ -325,11 +280,14 @@ func (a *UpdateCommitteeGovAction) ToPlutusData() data.PlutusData { } return data.NewConstr(4, - a.ActionId.ToPlutusData(), + actionId, data.NewList(removedItems...), data.NewMap(addedPairs), - data.NewInteger(num), - data.NewInteger(den), + data.NewConstr( + 0, + data.NewInteger(num), + data.NewInteger(den), + ), ) } @@ -347,11 +305,21 @@ type NewConstitutionGovAction struct { } func (a *NewConstitutionGovAction) ToPlutusData() data.PlutusData { + actionId := data.NewConstr(1) + if a.ActionId != nil { + actionId = data.NewConstr(0, a.ActionId.ToPlutusData()) + } + scriptHash := data.NewConstr(1) + if len(a.Constitution.ScriptHash) > 0 { + scriptHash = data.NewConstr( + 0, + data.NewByteString(a.Constitution.ScriptHash), + ) + } return data.NewConstr(5, - a.ActionId.ToPlutusData(), + actionId, data.NewConstr(0, - a.Constitution.Anchor.ToPlutusData(), - data.NewByteString(a.Constitution.ScriptHash), + scriptHash, ), ) } diff --git a/ledger/common/gov_test.go b/ledger/common/gov_test.go index 6d79981f..4d421609 100644 --- a/ledger/common/gov_test.go +++ b/ledger/common/gov_test.go @@ -262,23 +262,6 @@ func TestVotingProcedureToPlutusData(t *testing.T) { } } -func TestProposalProcedureToPlutusData(t *testing.T) { - addr := Address{} - action := &InfoGovAction{} - - pp := &ProposalProcedure{ - Deposit: 1000000, - RewardAccount: addr, - GovAction: GovActionWrapper{Action: action}, - } - - pd := pp.ToPlutusData() - constr, ok := pd.(*data.Constr) - assert.True(t, ok) - assert.Equal(t, uint(0), constr.Tag) - assert.Len(t, constr.Fields, 3) -} - func TestGovActionIdToPlutusData(t *testing.T) { txId := [32]byte{1, 2, 3, 4} govActionId := &GovActionId{ @@ -293,20 +276,6 @@ func TestGovActionIdToPlutusData(t *testing.T) { assert.Len(t, constr.Fields, 2) } -func TestParameterChangeGovActionToPlutusData(t *testing.T) { - action := &ParameterChangeGovAction{ - ActionId: &GovActionId{}, - ParamUpdate: []byte{1, 2, 3}, - PolicyHash: []byte{4, 5, 6}, - } - - pd := action.ToPlutusData() - constr, ok := pd.(*data.Constr) - assert.True(t, ok) - assert.Equal(t, uint(0), constr.Tag) - assert.Len(t, constr.Fields, 3) -} - func TestHardForkInitiationGovActionToPlutusData(t *testing.T) { action := &HardForkInitiationGovAction{ ActionId: &GovActionId{}, @@ -383,12 +352,16 @@ func TestUpdateCommitteeGovActionToPlutusData(t *testing.T) { assert.True(t, ok) assert.Equal(t, uint(4), constr.Tag) + innerConstr, ok := constr.Fields[3].(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(0), innerConstr.Tag) + // Verify default values were used - num, ok := constr.Fields[3].(*data.Integer) + num, ok := innerConstr.Fields[0].(*data.Integer) assert.True(t, ok) assert.Equal(t, int64(0), num.Inner.Int64()) - den, ok := constr.Fields[4].(*data.Integer) + den, ok := innerConstr.Fields[1].(*data.Integer) assert.True(t, ok) assert.Equal(t, int64(1), den.Inner.Int64()) }) diff --git a/ledger/common/script/context.go b/ledger/common/script/context.go index 09e48e3b..123e5fdc 100644 --- a/ledger/common/script/context.go +++ b/ledger/common/script/context.go @@ -161,8 +161,7 @@ func (t TxInfoV3) ToPlutusData() data.PlutusData { t.Data.ToPlutusData(), data.NewByteString(t.Id.Bytes()), t.Votes.ToPlutusData(), - // TODO: proposal procedures - toPlutusData([]any{}), + toPlutusData(t.ProposalProcedures), t.CurrentTreasuryAmount.ToPlutusData(), t.TreasuryDonation.ToPlutusData(), ) @@ -188,6 +187,7 @@ func NewTxInfoV3FromTransaction( inputs := sortInputs(tx.Inputs()) withdrawals := withdrawalsInfo(tx.Withdrawals()) votes := votingInfo(tx.VotingProcedures()) + proposalProcedures := tx.ProposalProcedures() redeemers := redeemersInfo( tx.Witnesses(), scriptPurposeBuilder( @@ -196,8 +196,8 @@ func NewTxInfoV3FromTransaction( *assetMint, tx.Certificates(), withdrawals, - // TODO: proposal procedures votes, + proposalProcedures, ), ) tmpData := dataInfo(tx.Witnesses()) @@ -207,18 +207,18 @@ func NewTxInfoV3FromTransaction( sortInputs(tx.ReferenceInputs()), resolvedInputs, ), - Outputs: collapseOutputs(tx.Produced()), - Fee: tx.Fee(), - Mint: *assetMint, - ValidRange: validityRange, - Certificates: tx.Certificates(), - Withdrawals: withdrawals, - Signatories: signatoriesInfo(tx.RequiredSigners()), - Redeemers: redeemers, - Data: tmpData, - Id: tx.Hash(), - Votes: votes, - // TODO: ProposalProcedures + Outputs: collapseOutputs(tx.Produced()), + Fee: tx.Fee(), + Mint: *assetMint, + ValidRange: validityRange, + Certificates: tx.Certificates(), + Withdrawals: withdrawals, + Signatories: signatoriesInfo(tx.RequiredSigners()), + Redeemers: redeemers, + Data: tmpData, + Id: tx.Hash(), + Votes: votes, + ProposalProcedures: proposalProcedures, } if amt := tx.CurrentTreasuryValue(); amt > 0 { ret.CurrentTreasuryAmount.Value = amt diff --git a/ledger/common/script/context_test.go b/ledger/common/script/context_test.go index 134d10d4..1487ae5c 100644 --- a/ledger/common/script/context_test.go +++ b/ledger/common/script/context_test.go @@ -247,6 +247,24 @@ var scriptContextV3TestDefs = []struct { // uses indef-length encoding while the in-TX redeemer datum uses def-length encoding expectedCbor: "d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87980d87a80ffffff8080182aa080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a2d87d9fd8799fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffffd87981182ad87d9fd87a9fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffffd87980a05820e042ea3e95cf8156842e35639778441becaf3c862cb9459ee74b44aeeb88c673a5d8799fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffa1d8799f5820999999999999999999999999999999999999999999999999999999999999999901ffd87a80d8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffffa1d8799f582099999999999999999999999999999999999999999999999999999999999999991898ffd87980d87a9fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffa1d8799f5820999999999999999999999999999999999999999999999999999999999999999903ffd87980d87a9fd8799f581c00000000000000000000000000000000000000000000000000000000ffffa3d8799f5820777777777777777777777777777777777777777777777777777777777777777702ffd87b80d8799f5820888888888888888888888888888888888888888888888888888888888888888801ffd87b80d8799f5820999999999999999999999999999999999999999999999999999999999999999900ffd87b80d87b9f581c00000000000000000000000000000000000000000000000000000000ffa1d8799f5820999999999999999999999999999999999999999999999999999999999999999904ffd87a8080d87a80d87a80ffd87981182ad87d9fd8799fd87a9f581c43fa47afc68a7913fbe2f400e3849cb492d9a2610c85966de0f2ba1effffffff", }, + { + name: "ProposeAllButPparams", + txHex: "84a40081825820000000000000000000000000000000000000000000000000000000000000000000018002182a14d9010289841a001e8480581df0000000000000000000000000000000000000000000000000000000008301f6820a00827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581df0000000000000000000000000000000000000000000000000000000008301825820000000000000000000000000000000000000000000000000000000000000000000820b00827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581df0000000000000000000000000000000000000000000000000000000008302a1581de0111111111111111111111111111111111111111111111111111111111a000f4240f6827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581df0000000000000000000000000000000000000000000000000000000008302a1581de0222222222222222222222222222222222222222222222222222222221a000f4240581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581df0000000000000000000000000000000000000000000000000000000008203f6827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581df0000000000000000000000000000000000000000000000000000000008504f6818200581c00000000000000000000000000000000000000000000000000000000a18200581c000000000000000000000000000000000000000000000000000000001901f4d81e820102827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581df0000000000000000000000000000000000000000000000000000000008305f68282782068747470733a2f2f636f6e737469747574696f6e2e63617264616e6f2e6f726758200000000000000000000000000000000000000000000000000000000000000000f6827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581df0000000000000000000000000000000000000000000000000000000008305f68282782068747470733a2f2f636f6e737469747574696f6e2e63617264616e6f2e6f726758200000000000000000000000000000000000000000000000000000000000000000581c00000000000000000000000000000000000000000000000000000000827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000841a001e8480581de0000000000000000000000000000000000000000000000000000000008106827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000a20581840503d87980821a000f42401a05f5e1000781587d587b0101003232323232323225333333008001153330033370e900018029baa001153330073006375400224a66600894452615330054911856616c696461746f722072657475726e65642066616c73650013656002002002002002002153300249010b5f746d70313a20566f696400165734ae7155ceaab9e5573eae91f5f6", + inputsHex: "81825820000000000000000000000000000000000000000000000000000000000000000000", + outputsHex: "81a200581d6000000000000000000000000000000000000000000000000000000000011a000f4240", + redeemerTag: lcommon.RedeemerTagProposing, + redeemerIndex: 3, + expectedCbor: "d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87980d87a80ffffff8080182aa080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a1d87e9f03d8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87b9fa1d8799f581c22222222222222222222222222222222222222222222222222222222ff1a000f4240d8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffd87980a05820644c54128c2d5a09a7b5cf1f533ffabac64975224bd6e8f6443d2932ba61fccda09fd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87a9fd87a80d8799f0a00ffffffd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87a9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffffd8799f0b00ffffffd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87b9fa1d8799f581c11111111111111111111111111111111111111111111111111111111ff1a000f4240d87a80ffffd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87b9fa1d8799f581c22222222222222222222222222222222222222222222222222222222ff1a000f4240d8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87c9fd87a80ffffd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87d9fd87a809fd8799f581c00000000000000000000000000000000000000000000000000000000ffffa1d8799f581c00000000000000000000000000000000000000000000000000000000ff1901f4d8799f0102ffffffd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87e9fd87a80d8799fd87a80ffffffd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87e9fd87a80d8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffffffffd8799f1a001e8480d8799f581c00000000000000000000000000000000000000000000000000000000ffd87f80ffffd87a80d87a80ffd87980d87e9f03d8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd87b9fa1d8799f581c22222222222222222222222222222222222222222222222222222222ff1a000f4240d8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffff", + }, + { + name: "ProposePparamsNoCostModels", + txHex: "84a40081825820000000000000000000000000000000000000000000000000000000000000000000018002182a14d9010281841a001e8480581df0000000000000000000000000000000000000000000000000000000008400f6b81d00182c011a00025ef50712081901f409d81e82030a0ad81e82031903e80bd81e82020a021a00016000031940000419044c051a001e8480061a1dcd650010190154111910d61382d81e821902411903e8d81e821902d11a000f424014821a00d59f801b00000002540be40015821a03b20b801b00000004a817c80016191388171896181803181985d81e8218331864d81e8218341864d81e8218351864d81e8218361864d81e8218371864181a8ad81e8218431864d81e8218431864d81e82183c1864d81e82184b1864d81e82183c1864d81e8218431864d81e8218431864d81e8218431864d81e82184b1864d81e8218431864181b07181c1892181d06181e1b000000174876e800181f1a1dcd65001820141821d81e820f01581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101827668747470733a2f2f61696b656e2d6c616e672e6f726758200000000000000000000000000000000000000000000000000000000000000000a20581840500d87980821a000f42401a05f5e1000781587d587b0101003232323232323225333333008001153330033370e900018029baa001153330073006375400224a66600894452615330054911856616c696461746f722072657475726e65642066616c73650013656002002002002002002153300249010b5f746d70313a20566f696400165734ae7155ceaab9e5573eae91f5f6", + inputsHex: "81825820000000000000000000000000000000000000000000000000000000000000000000", + outputsHex: "81a200581d6000000000000000000000000000000000000000000000000000000000011a000f4240", + redeemerTag: lcommon.RedeemerTagProposing, + redeemerIndex: 0, + expectedCbor: "d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87980d87a80ffffff8080182aa080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a1d87e9f00d8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd8799fd87a80b81d00182c011a00025ef5021a00016000031940000419044c051a001e8480061a1dcd65000712081901f4099f030aff0a9f031903e8ff0b9f0105ff10190154111910d6139f9f1902411903e8ff9f1902d11a000f4240ffff149f1a00d59f801b00000002540be400ff159f1a03b20b801b00000004a817c800ff1619138817189618180318199f9f18331864ff9f0d1819ff9f18351864ff9f181b1832ff9f0b14ffff181a9f9f18431864ff9f18431864ff9f0305ff9f0304ff9f0305ff9f18431864ff9f18431864ff9f18431864ff9f0304ff9f18431864ffff181b07181c1892181d06181e1b000000174876e800181f1a1dcd650018201418219f0f01ffd8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffd87980a05820a88a9bf8af51ea738bf212fe204e5ef2fd05312c995dad1ec194dcdc5aa07737a09fd8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd8799fd87a80b81d00182c011a00025ef5021a00016000031940000419044c051a001e8480061a1dcd65000712081901f4099f030aff0a9f031903e8ff0b9f0105ff10190154111910d6139f9f1902411903e8ff9f1902d11a000f4240ffff149f1a00d59f801b00000002540be400ff159f1a03b20b801b00000004a817c800ff1619138817189618180318199f9f18331864ff9f0d1819ff9f18351864ff9f181b1832ff9f0b14ffff181a9f9f18431864ff9f18431864ff9f0305ff9f0304ff9f0305ff9f18431864ff9f18431864ff9f18431864ff9f0304ff9f18431864ffff181b07181c1892181d06181e1b000000174876e800181f1a1dcd650018201418219f0f01ffd8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffd87a80d87a80ffd87980d87e9f00d8799f1a001e8480d87a9f581c00000000000000000000000000000000000000000000000000000000ffd8799fd87a80b81d00182c011a00025ef5021a00016000031940000419044c051a001e8480061a1dcd65000712081901f4099f030aff0a9f031903e8ff0b9f0105ff10190154111910d6139f9f1902411903e8ff9f1902d11a000f4240ffff149f1a00d59f801b00000002540be400ff159f1a03b20b801b00000004a817c800ff1619138817189618180318199f9f18331864ff9f0d1819ff9f18351864ff9f181b1832ff9f0b14ffff181a9f9f18431864ff9f18431864ff9f0305ff9f0304ff9f0305ff9f18431864ff9f18431864ff9f18431864ff9f0304ff9f18431864ffff181b07181c1892181d06181e1b000000174876e800181f1a1dcd650018201418219f0f01ffd8799f581c9b24324046544393443e1fb35c8b72c3c39e18a516a95df5f6654101ffffffffff", + }, } func TestScriptContextV3(t *testing.T) { diff --git a/ledger/common/script/purpose.go b/ledger/common/script/purpose.go index d5e01c0f..fdef0a3a 100644 --- a/ledger/common/script/purpose.go +++ b/ledger/common/script/purpose.go @@ -20,6 +20,7 @@ import ( "slices" lcommon "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/gouroboros/ledger/conway" "github.com/blinklabs-io/plutigo/data" ) @@ -172,13 +173,21 @@ type ScriptInfoProposing struct { func (ScriptInfoProposing) isScriptInfo() {} func (s ScriptInfoProposing) ScriptHash() lcommon.ScriptHash { - // TODO + switch a := s.ProposalProcedure.GovAction().(type) { + case *conway.ConwayParameterChangeGovAction: + return lcommon.ScriptHash(a.PolicyHash) + case *lcommon.TreasuryWithdrawalGovAction: + return lcommon.ScriptHash(a.PolicyHash) + } return lcommon.ScriptHash{} } func (s ScriptInfoProposing) ToPlutusData() data.PlutusData { - // TODO - return nil + return data.NewConstr( + 5, + toPlutusData(uint64(s.Index)), + s.ProposalProcedure.ToPlutusData(), + ) } type toScriptPurposeFunc func(lcommon.RedeemerKey) ScriptInfo @@ -191,7 +200,7 @@ func scriptPurposeBuilder( certificates []lcommon.Certificate, withdrawals KeyValuePairs[*lcommon.Address, uint64], votes KeyValuePairs[*lcommon.Voter, KeyValuePairs[*lcommon.GovActionId, lcommon.VotingProcedure]], - // TODO: proposal procedures + proposalProcedures []lcommon.ProposalProcedure, ) toScriptPurposeFunc { return func(redeemerKey lcommon.RedeemerKey) ScriptInfo { // TODO: implement additional redeemer tags @@ -241,7 +250,10 @@ func scriptPurposeBuilder( Voter: *(votes[redeemerKey.Index].Key), } case lcommon.RedeemerTagProposing: - return nil + return ScriptInfoProposing{ + Index: redeemerKey.Index, + ProposalProcedure: proposalProcedures[redeemerKey.Index], + } } return nil } diff --git a/ledger/conway/conway.go b/ledger/conway/conway.go index 5c5e6fb2..785b8552 100644 --- a/ledger/conway/conway.go +++ b/ledger/conway/conway.go @@ -374,7 +374,7 @@ type ConwayTransactionBody struct { TxTotalCollateral uint64 `cbor:"17,keyasint,omitempty"` TxReferenceInputs cbor.SetType[shelley.ShelleyTransactionInput] `cbor:"18,keyasint,omitempty,omitzero"` TxVotingProcedures common.VotingProcedures `cbor:"19,keyasint,omitempty"` - TxProposalProcedures []common.ProposalProcedure `cbor:"20,keyasint,omitempty"` + TxProposalProcedures []ConwayProposalProcedure `cbor:"20,keyasint,omitempty"` TxCurrentTreasuryValue int64 `cbor:"21,keyasint,omitempty"` TxDonation uint64 `cbor:"22,keyasint,omitempty"` } @@ -492,7 +492,11 @@ func (b *ConwayTransactionBody) VotingProcedures() common.VotingProcedures { } func (b *ConwayTransactionBody) ProposalProcedures() []common.ProposalProcedure { - return b.TxProposalProcedures + ret := make([]common.ProposalProcedure, len(b.TxProposalProcedures)) + for i, item := range b.TxProposalProcedures { + ret[i] = item + } + return ret } func (b *ConwayTransactionBody) CurrentTreasuryValue() int64 { diff --git a/ledger/conway/gov.go b/ledger/conway/gov.go new file mode 100644 index 00000000..5d40883c --- /dev/null +++ b/ledger/conway/gov.go @@ -0,0 +1,133 @@ +// 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 conway + +import ( + "fmt" + "math/big" + + "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/plutigo/data" +) + +type ConwayProposalProcedure struct { + common.ProposalProcedureBase + cbor.StructAsArray + PPDeposit uint64 + PPRewardAccount common.Address + PPGovAction ConwayGovAction + PPAnchor common.GovAnchor +} + +func (p ConwayProposalProcedure) ToPlutusData() data.PlutusData { + return data.NewConstr(0, + data.NewInteger(new(big.Int).SetUint64(p.PPDeposit)), + p.PPRewardAccount.ToPlutusData(), + p.PPGovAction.ToPlutusData(), + ) +} + +func (p ConwayProposalProcedure) Deposit() uint64 { + return p.PPDeposit +} + +func (p ConwayProposalProcedure) RewardAccount() common.Address { + return p.PPRewardAccount +} + +func (p ConwayProposalProcedure) GovAction() common.GovAction { + return p.PPGovAction.Action +} + +func (p ConwayProposalProcedure) Anchor() common.GovAnchor { + return p.PPAnchor +} + +type ConwayGovAction struct { + Type uint + Action common.GovAction +} + +func (g ConwayGovAction) ToPlutusData() data.PlutusData { + return g.Action.ToPlutusData() +} + +func (g *ConwayGovAction) UnmarshalCBOR(data []byte) error { + // Determine action type + actionType, err := cbor.DecodeIdFromList(data) + if err != nil { + return err + } + var tmpAction common.GovAction + switch actionType { + case common.GovActionTypeParameterChange: + tmpAction = &ConwayParameterChangeGovAction{} + case common.GovActionTypeHardForkInitiation: + tmpAction = &common.HardForkInitiationGovAction{} + case common.GovActionTypeTreasuryWithdrawal: + tmpAction = &common.TreasuryWithdrawalGovAction{} + case common.GovActionTypeNoConfidence: + tmpAction = &common.NoConfidenceGovAction{} + case common.GovActionTypeUpdateCommittee: + tmpAction = &common.UpdateCommitteeGovAction{} + case common.GovActionTypeNewConstitution: + tmpAction = &common.NewConstitutionGovAction{} + case common.GovActionTypeInfo: + tmpAction = &common.InfoGovAction{} + default: + return fmt.Errorf("unknown governance action type: %d", actionType) + } + // Decode action + if _, err := cbor.Decode(data, tmpAction); err != nil { + return err + } + // action type is known within uint range + g.Type = uint(actionType) // #nosec G115 + g.Action = tmpAction + return nil +} + +func (g *ConwayGovAction) MarshalCBOR() ([]byte, error) { + return cbor.Encode(g.Action) +} + +type ConwayParameterChangeGovAction struct { + common.GovActionBase + cbor.StructAsArray + Type uint + ActionId *common.GovActionId + ParamUpdate ConwayProtocolParameterUpdate + PolicyHash []byte +} + +func (a *ConwayParameterChangeGovAction) ToPlutusData() data.PlutusData { + actionId := data.NewConstr(1) + if a.ActionId != nil { + actionId = data.NewConstr(0, a.ActionId.ToPlutusData()) + } + policyHash := data.NewConstr(1) + if len(a.PolicyHash) > 0 { + policyHash = data.NewConstr( + 0, + data.NewByteString(a.PolicyHash), + ) + } + return data.NewConstr(0, + actionId, + a.ParamUpdate.ToPlutusData(), + policyHash, + ) +} diff --git a/ledger/conway/gov_test.go b/ledger/conway/gov_test.go new file mode 100644 index 00000000..02704c4d --- /dev/null +++ b/ledger/conway/gov_test.go @@ -0,0 +1,40 @@ +// 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 conway + +import ( + "testing" + + "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/plutigo/data" + "github.com/stretchr/testify/assert" +) + +func TestConwayProposalProcedureToPlutusData(t *testing.T) { + addr := common.Address{} + action := &common.InfoGovAction{} + + pp := &ConwayProposalProcedure{ + PPDeposit: 1000000, + PPRewardAccount: addr, + PPGovAction: ConwayGovAction{Action: action}, + } + + pd := pp.ToPlutusData() + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(0), constr.Tag) + assert.Len(t, constr.Fields, 3) +} diff --git a/ledger/conway/pparams.go b/ledger/conway/pparams.go index 37b5f7d1..be97d24c 100644 --- a/ledger/conway/pparams.go +++ b/ledger/conway/pparams.go @@ -17,10 +17,12 @@ package conway import ( "errors" "math" + "math/big" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/gouroboros/ledger/babbage" "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/plutigo/data" cardano "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" ) @@ -342,16 +344,38 @@ func (p *ConwayProtocolParameters) UpdateFromGenesis( } type ConwayProtocolParameterUpdate struct { - babbage.BabbageProtocolParameterUpdate - PoolVotingThresholds *PoolVotingThresholds `cbor:"25,keyasint"` - DRepVotingThresholds *DRepVotingThresholds `cbor:"26,keyasint"` - MinCommitteeSize *uint `cbor:"27,keyasint"` - CommitteeTermLimit *uint64 `cbor:"28,keyasint"` - GovActionValidityPeriod *uint64 `cbor:"29,keyasint"` - GovActionDeposit *uint64 `cbor:"30,keyasint"` - DRepDeposit *uint64 `cbor:"31,keyasint"` - DRepInactivityPeriod *uint64 `cbor:"32,keyasint"` - MinFeeRefScriptCostPerByte *cbor.Rat `cbor:"33,keyasint"` + cbor.DecodeStoreCbor + MinFeeA *uint `cbor:"0,keyasint"` + MinFeeB *uint `cbor:"1,keyasint"` + MaxBlockBodySize *uint `cbor:"2,keyasint"` + MaxTxSize *uint `cbor:"3,keyasint"` + MaxBlockHeaderSize *uint `cbor:"4,keyasint"` + KeyDeposit *uint `cbor:"5,keyasint"` + PoolDeposit *uint `cbor:"6,keyasint"` + MaxEpoch *uint `cbor:"7,keyasint"` + NOpt *uint `cbor:"8,keyasint"` + A0 *cbor.Rat `cbor:"9,keyasint"` + Rho *cbor.Rat `cbor:"10,keyasint"` + Tau *cbor.Rat `cbor:"11,keyasint"` + ProtocolVersion *common.ProtocolParametersProtocolVersion `cbor:"14,keyasint"` + MinPoolCost *uint64 `cbor:"16,keyasint"` + AdaPerUtxoByte *uint64 `cbor:"17,keyasint"` + CostModels map[uint][]int64 `cbor:"18,keyasint"` + ExecutionCosts *common.ExUnitPrice `cbor:"19,keyasint"` + MaxTxExUnits *common.ExUnits `cbor:"20,keyasint"` + MaxBlockExUnits *common.ExUnits `cbor:"21,keyasint"` + MaxValueSize *uint `cbor:"22,keyasint"` + CollateralPercentage *uint `cbor:"23,keyasint"` + MaxCollateralInputs *uint `cbor:"24,keyasint"` + PoolVotingThresholds *PoolVotingThresholds `cbor:"25,keyasint"` + DRepVotingThresholds *DRepVotingThresholds `cbor:"26,keyasint"` + MinCommitteeSize *uint `cbor:"27,keyasint"` + CommitteeTermLimit *uint64 `cbor:"28,keyasint"` + GovActionValidityPeriod *uint64 `cbor:"29,keyasint"` + GovActionDeposit *uint64 `cbor:"30,keyasint"` + DRepDeposit *uint64 `cbor:"31,keyasint"` + DRepInactivityPeriod *uint64 `cbor:"32,keyasint"` + MinFeeRefScriptCostPerByte *cbor.Rat `cbor:"33,keyasint"` } func (u *ConwayProtocolParameterUpdate) UnmarshalCBOR(cborData []byte) error { @@ -365,6 +389,149 @@ func (u *ConwayProtocolParameterUpdate) UnmarshalCBOR(cborData []byte) error { return nil } +func (u ConwayProtocolParameterUpdate) ToPlutusData() data.PlutusData { + tmpPairs := make([][2]data.PlutusData, 0, 30) + push := func(idx int, pd data.PlutusData) { + tmpPairs = append( + tmpPairs, + [2]data.PlutusData{ + data.NewInteger(new(big.Int).SetInt64(int64(idx))), + pd, + }, + ) + } + if u.MinFeeA != nil { + push(0, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MinFeeA)))) + } + if u.MinFeeB != nil { + push(1, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MinFeeB)))) + } + if u.MaxBlockBodySize != nil { + push(2, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MaxBlockBodySize)))) + } + if u.MaxTxSize != nil { + push(3, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MaxTxSize)))) + } + if u.MaxBlockHeaderSize != nil { + push(4, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MaxBlockHeaderSize)))) + } + if u.KeyDeposit != nil { + push(5, data.NewInteger(new(big.Int).SetUint64(uint64(*u.KeyDeposit)))) + } + if u.PoolDeposit != nil { + push(6, data.NewInteger(new(big.Int).SetUint64(uint64(*u.PoolDeposit)))) + } + if u.MaxEpoch != nil { + push(7, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MaxEpoch)))) + } + if u.NOpt != nil { + push(8, data.NewInteger(new(big.Int).SetUint64(uint64(*u.NOpt)))) + } + if u.A0 != nil { + push(9, + data.NewList( + data.NewInteger(u.A0.Num()), + data.NewInteger(u.A0.Denom()), + ), + ) + } + if u.Rho != nil { + push(10, + data.NewList( + data.NewInteger(u.Rho.Num()), + data.NewInteger(u.Rho.Denom()), + ), + ) + } + if u.Tau != nil { + push(11, + data.NewList( + data.NewInteger(u.Tau.Num()), + data.NewInteger(u.Tau.Denom()), + ), + ) + } + if u.MinPoolCost != nil { + push(16, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MinPoolCost)))) + } + if u.AdaPerUtxoByte != nil { + push(17, data.NewInteger(new(big.Int).SetUint64(uint64(*u.AdaPerUtxoByte)))) + } + // TODO: CostModels + if u.ExecutionCosts != nil { + push(19, + data.NewList( + data.NewList( + data.NewInteger(u.ExecutionCosts.MemPrice.Num()), + data.NewInteger(u.ExecutionCosts.MemPrice.Denom()), + ), + data.NewList( + data.NewInteger(u.ExecutionCosts.StepPrice.Num()), + data.NewInteger(u.ExecutionCosts.StepPrice.Denom()), + ), + ), + ) + } + if u.MaxTxExUnits != nil { + push(20, + data.NewList( + data.NewInteger(big.NewInt(u.MaxTxExUnits.Memory)), + data.NewInteger(big.NewInt(u.MaxTxExUnits.Steps)), + ), + ) + } + if u.MaxBlockExUnits != nil { + push(21, + data.NewList( + data.NewInteger(big.NewInt(u.MaxBlockExUnits.Memory)), + data.NewInteger(big.NewInt(u.MaxBlockExUnits.Steps)), + ), + ) + } + if u.MaxValueSize != nil { + push(22, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MaxValueSize)))) + } + if u.CollateralPercentage != nil { + push(23, data.NewInteger(new(big.Int).SetUint64(uint64(*u.CollateralPercentage)))) + } + if u.MaxCollateralInputs != nil { + push(24, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MaxCollateralInputs)))) + } + if u.PoolVotingThresholds != nil { + push(25, u.PoolVotingThresholds.ToPlutusData()) + } + if u.DRepVotingThresholds != nil { + push(26, u.DRepVotingThresholds.ToPlutusData()) + } + if u.MinCommitteeSize != nil { + push(27, data.NewInteger(new(big.Int).SetUint64(uint64(*u.MinCommitteeSize)))) + } + if u.CommitteeTermLimit != nil { + push(28, data.NewInteger(new(big.Int).SetUint64(*u.CommitteeTermLimit))) + } + if u.GovActionValidityPeriod != nil { + push(29, data.NewInteger(new(big.Int).SetUint64(*u.GovActionValidityPeriod))) + } + if u.GovActionDeposit != nil { + push(30, data.NewInteger(new(big.Int).SetUint64(*u.GovActionDeposit))) + } + if u.DRepDeposit != nil { + push(31, data.NewInteger(new(big.Int).SetUint64(*u.DRepDeposit))) + } + if u.DRepInactivityPeriod != nil { + push(32, data.NewInteger(new(big.Int).SetUint64(*u.DRepInactivityPeriod))) + } + if u.MinFeeRefScriptCostPerByte != nil { + push(33, + data.NewList( + data.NewInteger(u.MinFeeRefScriptCostPerByte.Num()), + data.NewInteger(u.MinFeeRefScriptCostPerByte.Denom()), + ), + ) + } + return data.NewMap(tmpPairs) +} + type PoolVotingThresholds struct { cbor.StructAsArray MotionNoConfidence cbor.Rat @@ -374,6 +541,31 @@ type PoolVotingThresholds struct { PpSecurityGroup cbor.Rat } +func (t PoolVotingThresholds) ToPlutusData() data.PlutusData { + return data.NewList( + data.NewList( + data.NewInteger(t.MotionNoConfidence.Num()), + data.NewInteger(t.MotionNoConfidence.Denom()), + ), + data.NewList( + data.NewInteger(t.CommitteeNormal.Num()), + data.NewInteger(t.CommitteeNormal.Denom()), + ), + data.NewList( + data.NewInteger(t.CommitteeNoConfidence.Num()), + data.NewInteger(t.CommitteeNoConfidence.Denom()), + ), + data.NewList( + data.NewInteger(t.HardForkInitiation.Num()), + data.NewInteger(t.HardForkInitiation.Denom()), + ), + data.NewList( + data.NewInteger(t.PpSecurityGroup.Num()), + data.NewInteger(t.PpSecurityGroup.Denom()), + ), + ) +} + type DRepVotingThresholds struct { cbor.StructAsArray MotionNoConfidence cbor.Rat @@ -388,6 +580,51 @@ type DRepVotingThresholds struct { TreasuryWithdrawal cbor.Rat } +func (t DRepVotingThresholds) ToPlutusData() data.PlutusData { + return data.NewList( + data.NewList( + data.NewInteger(t.MotionNoConfidence.Num()), + data.NewInteger(t.MotionNoConfidence.Denom()), + ), + data.NewList( + data.NewInteger(t.CommitteeNormal.Num()), + data.NewInteger(t.CommitteeNormal.Denom()), + ), + data.NewList( + data.NewInteger(t.CommitteeNoConfidence.Num()), + data.NewInteger(t.CommitteeNoConfidence.Denom()), + ), + data.NewList( + data.NewInteger(t.UpdateToConstitution.Num()), + data.NewInteger(t.UpdateToConstitution.Denom()), + ), + data.NewList( + data.NewInteger(t.HardForkInitiation.Num()), + data.NewInteger(t.HardForkInitiation.Denom()), + ), + data.NewList( + data.NewInteger(t.PpNetworkGroup.Num()), + data.NewInteger(t.PpNetworkGroup.Denom()), + ), + data.NewList( + data.NewInteger(t.PpEconomicGroup.Num()), + data.NewInteger(t.PpEconomicGroup.Denom()), + ), + data.NewList( + data.NewInteger(t.PpTechnicalGroup.Num()), + data.NewInteger(t.PpTechnicalGroup.Denom()), + ), + data.NewList( + data.NewInteger(t.PpGovGroup.Num()), + data.NewInteger(t.PpGovGroup.Denom()), + ), + data.NewList( + data.NewInteger(t.TreasuryWithdrawal.Num()), + data.NewInteger(t.TreasuryWithdrawal.Denom()), + ), + ) +} + func UpgradePParams( prevPParams babbage.BabbageProtocolParameters, ) ConwayProtocolParameters { diff --git a/ledger/conway/rules.go b/ledger/conway/rules.go index f68fff9f..b247b9c4 100644 --- a/ledger/conway/rules.go +++ b/ledger/conway/rules.go @@ -294,7 +294,7 @@ func UtxoValidateValueNotConservedUtxo( } } for _, proposal := range tx.ProposalProcedures() { - producedValue += proposal.Deposit + producedValue += proposal.Deposit() } if consumedValue == producedValue { return nil