diff --git a/ledger/common/gov.go b/ledger/common/gov.go index 31da5b1d..6a557e8a 100644 --- a/ledger/common/gov.go +++ b/ledger/common/gov.go @@ -16,6 +16,7 @@ package common import ( "fmt" + "math/big" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/plutigo/pkg/data" @@ -108,12 +109,26 @@ type GovAnchor struct { DataHash [32]byte } +func (a *GovAnchor) ToPlutusData() data.PlutusData { + return data.NewConstr(0, + data.NewByteString([]byte(a.Url)), + data.NewByteString(a.DataHash[:]), + ) +} + type GovActionId struct { cbor.StructAsArray TransactionId [32]byte GovActionIdx uint32 } +func (id *GovActionId) ToPlutusData() data.PlutusData { + return data.NewConstr(0, + data.NewByteString(id.TransactionId[:]), + data.NewInteger(big.NewInt(int64(id.GovActionIdx))), + ) +} + type ProposalProcedure struct { cbor.StructAsArray Deposit uint64 @@ -122,6 +137,14 @@ type ProposalProcedure struct { 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(), + ) +} + const ( GovActionTypeParameterChange = 0 GovActionTypeHardForkInitiation = 1 @@ -137,6 +160,10 @@ type GovActionWrapper struct { 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) @@ -178,6 +205,7 @@ func (g *GovActionWrapper) MarshalCBOR() ([]byte, error) { type GovAction interface { isGovAction() + ToPlutusData() data.PlutusData } type ParameterChangeGovAction struct { @@ -188,6 +216,14 @@ type ParameterChangeGovAction struct { PolicyHash []byte } +func (a *ParameterChangeGovAction) ToPlutusData() data.PlutusData { + return data.NewConstr(0, + a.ActionId.ToPlutusData(), + data.NewByteString(a.ParamUpdate), + data.NewByteString(a.PolicyHash), + ) +} + func (a ParameterChangeGovAction) isGovAction() {} type HardForkInitiationGovAction struct { @@ -201,6 +237,16 @@ type HardForkInitiationGovAction struct { } } +func (a *HardForkInitiationGovAction) ToPlutusData() data.PlutusData { + return data.NewConstr(1, + a.ActionId.ToPlutusData(), + data.NewConstr(0, + data.NewInteger(new(big.Int).SetUint64(uint64(a.ProtocolVersion.Major))), + data.NewInteger(new(big.Int).SetUint64(uint64(a.ProtocolVersion.Minor))), + ), + ) +} + func (a HardForkInitiationGovAction) isGovAction() {} type TreasuryWithdrawalGovAction struct { @@ -210,6 +256,20 @@ type TreasuryWithdrawalGovAction struct { PolicyHash []byte } +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()), + data.NewInteger(new(big.Int).SetUint64(amount)), + }) + } + return data.NewConstr(2, + data.NewMap(pairs), + data.NewByteString(a.PolicyHash), + ) +} + func (a TreasuryWithdrawalGovAction) isGovAction() {} type NoConfidenceGovAction struct { @@ -218,6 +278,12 @@ type NoConfidenceGovAction struct { ActionId *GovActionId } +func (a *NoConfidenceGovAction) ToPlutusData() data.PlutusData { + return data.NewConstr(3, + a.ActionId.ToPlutusData(), + ) +} + func (a NoConfidenceGovAction) isGovAction() {} type UpdateCommitteeGovAction struct { @@ -226,7 +292,40 @@ type UpdateCommitteeGovAction struct { ActionId *GovActionId Credentials []Credential CredEpochs map[*Credential]uint - Unknown cbor.Rat + Quorum cbor.Rat +} + +func (a *UpdateCommitteeGovAction) ToPlutusData() data.PlutusData { + removedItems := make([]data.PlutusData, 0, len(a.Credentials)) + for _, cred := range a.Credentials { + removedItems = append(removedItems, cred.ToPlutusData()) + } + + addedPairs := make([][2]data.PlutusData, 0, len(a.CredEpochs)) + for cred, epoch := range a.CredEpochs { + addedPairs = append(addedPairs, [2]data.PlutusData{ + cred.ToPlutusData(), + data.NewInteger(new(big.Int).SetUint64(uint64(epoch))), + }) + } + + // Get numerator and denominator using Rat methods + var num, den *big.Int + if a.Quorum != (cbor.Rat{}) { + num = a.Quorum.Num() + den = a.Quorum.Denom() + } else { + num = big.NewInt(0) + den = big.NewInt(1) + } + + return data.NewConstr(4, + a.ActionId.ToPlutusData(), + data.NewList(removedItems...), + data.NewMap(addedPairs), + data.NewInteger(num), + data.NewInteger(den), + ) } func (a UpdateCommitteeGovAction) isGovAction() {} @@ -242,6 +341,16 @@ type NewConstitutionGovAction struct { } } +func (a *NewConstitutionGovAction) ToPlutusData() data.PlutusData { + return data.NewConstr(5, + a.ActionId.ToPlutusData(), + data.NewConstr(0, + a.Constitution.Anchor.ToPlutusData(), + data.NewByteString(a.Constitution.ScriptHash), + ), + ) +} + func (a NewConstitutionGovAction) isGovAction() {} type InfoGovAction struct { @@ -249,4 +358,8 @@ type InfoGovAction struct { Type uint } +func (a *InfoGovAction) ToPlutusData() data.PlutusData { + return data.NewConstr(6) +} + func (a InfoGovAction) isGovAction() {} diff --git a/ledger/common/gov_test.go b/ledger/common/gov_test.go index 2c110b77..71df0ea4 100644 --- a/ledger/common/gov_test.go +++ b/ledger/common/gov_test.go @@ -18,7 +18,9 @@ import ( "reflect" "testing" + "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/plutigo/pkg/data" + "github.com/stretchr/testify/assert" ) // Ttests the ToPlutusData method for Voter types @@ -177,3 +179,167 @@ 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{ + TransactionId: txId, + GovActionIdx: 42, + } + + pd := govActionId.ToPlutusData() + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(0), constr.Tag) + 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{}, + ProtocolVersion: struct { + cbor.StructAsArray + Major uint + Minor uint + }{ + Major: 8, + Minor: 0, + }, + } + + pd := action.ToPlutusData() + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(1), constr.Tag) + assert.Len(t, constr.Fields, 2) +} + +func TestTreasuryWithdrawalGovActionToPlutusData(t *testing.T) { + addr := Address{} + withdrawals := map[*Address]uint64{ + &addr: 5000000, + } + + action := &TreasuryWithdrawalGovAction{ + Withdrawals: withdrawals, + PolicyHash: []byte{1, 2, 3}, + } + + pd := action.ToPlutusData() + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(2), constr.Tag) + assert.Len(t, constr.Fields, 2) +} + +func TestNoConfidenceGovActionToPlutusData(t *testing.T) { + action := &NoConfidenceGovAction{ + ActionId: &GovActionId{}, + } + + pd := action.ToPlutusData() + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(3), constr.Tag) + assert.Len(t, constr.Fields, 1) +} + +func TestUpdateCommitteeGovActionToPlutusData(t *testing.T) { + cred := Credential{ + CredType: CredentialTypeAddrKeyHash, + Credential: NewBlake2b224([]byte("test")), + } + creds := []Credential{cred} + credEpochs := map[*Credential]uint{ + &cred: 42, + } + + // Test with zero value Rat + t.Run("ZeroValueRat", func(t *testing.T) { + action := &UpdateCommitteeGovAction{ + ActionId: &GovActionId{}, + Credentials: creds, + CredEpochs: credEpochs, + Quorum: cbor.Rat{}, // Zero value + } + + pd := action.ToPlutusData() + assert.NotNil(t, pd) + + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(4), constr.Tag) + + // Verify default values were used + num, ok := constr.Fields[3].(*data.Integer) + assert.True(t, ok) + assert.Equal(t, int64(0), num.Inner.Int64()) + + den, ok := constr.Fields[4].(*data.Integer) + assert.True(t, ok) + assert.Equal(t, int64(1), den.Inner.Int64()) + }) + + t.Run("NilCase", func(t *testing.T) { + }) +} + +func TestNewConstitutionGovActionToPlutusData(t *testing.T) { + action := &NewConstitutionGovAction{ + ActionId: &GovActionId{}, + Constitution: struct { + cbor.StructAsArray + Anchor GovAnchor + ScriptHash []byte + }{ + ScriptHash: []byte{1, 2, 3}, + }, + } + + pd := action.ToPlutusData() + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(5), constr.Tag) + assert.Len(t, constr.Fields, 2) +} + +func TestInfoGovActionToPlutusData(t *testing.T) { + action := &InfoGovAction{} + + pd := action.ToPlutusData() + constr, ok := pd.(*data.Constr) + assert.True(t, ok) + assert.Equal(t, uint(6), constr.Tag) + assert.Len(t, constr.Fields, 0) +}