From 42c4125c992d7b21fa7c3a4c9b52924311cde7d0 Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Thu, 17 Jul 2025 09:41:16 -0400 Subject: [PATCH] feat: helper function to convert Address to PlutusData Fixes #1103 Signed-off-by: Aurora Gaffney --- go.mod | 2 +- go.sum | 6 ++-- ledger/byron/byron.go | 6 ++-- ledger/common/address.go | 64 +++++++++++++++++++++++++++++++++++ ledger/common/address_test.go | 54 +++++++++++++++++++++++++++++ ledger/common/credentials.go | 17 ++++++++++ ledger/shelley/shelley.go | 6 ++-- ledger/shelley/tx_test.go | 6 ++-- 8 files changed, 144 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 72aa2a86..686b1270 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.24.1 require ( filippo.io/edwards25519 v1.1.0 github.com/blinklabs-io/ouroboros-mock v0.3.8 - github.com/blinklabs-io/plutigo v0.0.2-0.20250716022656-7418e5ead692 + github.com/blinklabs-io/plutigo v0.0.2-0.20250717183329-b331a97fb319 github.com/btcsuite/btcd/btcutil v1.1.6 github.com/fxamacker/cbor/v2 v2.9.0 github.com/jinzhu/copier v0.4.0 diff --git a/go.sum b/go.sum index 883d421a..06565fcd 100644 --- a/go.sum +++ b/go.sum @@ -3,10 +3,8 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4 github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/blinklabs-io/ouroboros-mock v0.3.8 h1:+DAt2rx0ouZUxee5DBMgZq3I1+ZdxFSHG9g3tYl/FKU= github.com/blinklabs-io/ouroboros-mock v0.3.8/go.mod h1:UwQIf4KqZwO13P9d90fbi3UL/X7JaJfeEbqk+bEeFQA= -github.com/blinklabs-io/plutigo v0.0.1 h1:IBM3+dm7Sy92zgm9SBvIwVXN/73Jvg/ipDwbgRDeVFo= -github.com/blinklabs-io/plutigo v0.0.1/go.mod h1:N7id3F9kjRcvm/BMxwZsBWUSJTGsUEwVRfFz3Tt5LPM= -github.com/blinklabs-io/plutigo v0.0.2-0.20250716022656-7418e5ead692 h1:irMl0GGNv5heP8LpYBG4Z+a/RYqmmHVESZEa/Ba/67s= -github.com/blinklabs-io/plutigo v0.0.2-0.20250716022656-7418e5ead692/go.mod h1:Bh2zD801zEN90MIFv78HF1d3c/RpG/GD18wQrbdeN+0= +github.com/blinklabs-io/plutigo v0.0.2-0.20250717183329-b331a97fb319 h1:1LDEbQwobPtbaZYJ+imWoDQ0n5aAKKbFQocek2HxXXU= +github.com/blinklabs-io/plutigo v0.0.2-0.20250717183329-b331a97fb319/go.mod h1:Bh2zD801zEN90MIFv78HF1d3c/RpG/GD18wQrbdeN+0= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= diff --git a/ledger/byron/byron.go b/ledger/byron/byron.go index 88a616a6..9c8884c3 100644 --- a/ledger/byron/byron.go +++ b/ledger/byron/byron.go @@ -383,10 +383,8 @@ func (i ByronTransactionInput) ToPlutusData() data.PlutusData { // This will never actually get called, but it's identical to Shelley return data.NewConstr( 0, - []data.PlutusData{ - data.NewByteString(i.TxId.Bytes()), - data.NewInteger(big.NewInt(int64(i.OutputIndex))), - }, + data.NewByteString(i.TxId.Bytes()), + data.NewInteger(big.NewInt(int64(i.OutputIndex))), ) } diff --git a/ledger/common/address.go b/ledger/common/address.go index 79b076c6..7757c61d 100644 --- a/ledger/common/address.go +++ b/ledger/common/address.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/plutigo/pkg/data" "github.com/btcsuite/btcd/btcutil/base58" "github.com/btcsuite/btcd/btcutil/bech32" "golang.org/x/crypto/sha3" @@ -285,6 +286,69 @@ func (a *Address) MarshalCBOR() ([]byte, error) { return cbor.Encode(addrBytes) } +func (a *Address) ToPlutusData() data.PlutusData { + if a.addressType == AddressTypeByron { + // There is no PlutusData representation for Byron addresses + return nil + } + // Build payment part + var paymentPd data.PlutusData + switch a.addressType { + case AddressTypeKeyKey, AddressTypeKeyScript, AddressTypeKeyPointer, AddressTypeKeyNone: + tmpCred := &Credential{ + CredType: CredentialTypeAddrKeyHash, + Credential: NewBlake2b224(a.paymentAddress), + } + paymentPd = data.NewConstr( + 0, + tmpCred.ToPlutusData(), + ) + case AddressTypeScriptKey, AddressTypeScriptScript, AddressTypeScriptPointer, AddressTypeScriptNone: + tmpCred := &Credential{ + CredType: CredentialTypeScriptHash, + Credential: NewBlake2b224(a.paymentAddress), + } + paymentPd = data.NewConstr( + 1, + tmpCred.ToPlutusData(), + ) + default: + return nil + } + // Build stake part + var stakePd data.PlutusData + switch a.addressType { + case AddressTypeKeyKey, AddressTypeScriptKey, AddressTypeNoneKey: + tmpCred := &Credential{ + CredType: CredentialTypeAddrKeyHash, + Credential: NewBlake2b224(a.stakingAddress), + } + stakePd = data.NewConstr( + 0, + tmpCred.ToPlutusData(), + ) + case AddressTypeKeyScript, AddressTypeScriptScript, AddressTypeNoneScript: + tmpCred := &Credential{ + CredType: CredentialTypeScriptHash, + Credential: NewBlake2b224(a.stakingAddress), + } + stakePd = data.NewConstr( + 0, + tmpCred.ToPlutusData(), + ) + case AddressTypeKeyNone, AddressTypeScriptNone: + stakePd = data.NewConstr(1) + // TODO: add support for pointer addresses once we add it to Address + default: + return nil + } + return data.NewConstr( + 0, + paymentPd, + stakePd, + ) +} + func (a Address) NetworkId() uint { if a.addressType == AddressTypeByron { // Use Shelley network ID convention diff --git a/ledger/common/address_test.go b/ledger/common/address_test.go index 0b553e0c..310368fb 100644 --- a/ledger/common/address_test.go +++ b/ledger/common/address_test.go @@ -15,9 +15,11 @@ package common import ( + "reflect" "testing" "github.com/blinklabs-io/gouroboros/internal/test" + "github.com/blinklabs-io/plutigo/pkg/data" "github.com/stretchr/testify/assert" ) @@ -323,3 +325,55 @@ func TestAddressNetworkId(t *testing.T) { } } } + +func TestAddressToPlutusData(t *testing.T) { + testDefs := []struct { + address string + expectedData data.PlutusData + }{ + // Payment-only address + // This was adapted from the Aiken tests + { + address: "addr_test1vqg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygxrcya6", + expectedData: data.NewConstr( + 0, + data.NewConstr( + 0, + data.NewConstr( + 0, + data.NewByteString( + []byte{ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + }, + ), + ), + ), + data.NewConstr( + 1, + ), + ), + }, + /* + { + address: "addr1qyln2c2cx5jc4hw768pwz60n5245462dvp4auqcw09rl2xz07huw84puu6cea3qe0ce3apks7hjckqkh5ad4uax0l9ws0q9xty", + }, + { + address: "addr1wysmmrpwphe0h6fpxlmcmw46frmzxz89yvpsf8cdv29kcnqsw3vw6", + }, + // Script address with script staking key + { + address: "addr1x8nz307k3sr60gu0e47cmajssy4fmld7u493a4xztjrll0aj764lvrxdayh2ux30fl0ktuh27csgmpevdu89jlxppvrswgxsta", + }, + */ + } + for _, testDef := range testDefs { + tmpAddr, err := NewAddress(testDef.address) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + tmpPd := tmpAddr.ToPlutusData() + if !reflect.DeepEqual(tmpPd, testDef.expectedData) { + t.Errorf("did not get expected PlutusData\n got: %#v\n wanted: %#v", tmpPd, testDef.expectedData) + } + } +} diff --git a/ledger/common/credentials.go b/ledger/common/credentials.go index d246148a..9ab0044f 100644 --- a/ledger/common/credentials.go +++ b/ledger/common/credentials.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/plutigo/pkg/data" utxorpc "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano" "golang.org/x/crypto/blake2b" ) @@ -77,3 +78,19 @@ func (c *Credential) Utxorpc() (*utxorpc.StakeCredential, error) { } return ret, nil } + +func (c *Credential) ToPlutusData() data.PlutusData { + switch c.CredType { + case CredentialTypeAddrKeyHash: + return data.NewConstr( + 0, + data.NewByteString(c.Credential.Bytes()), + ) + case CredentialTypeScriptHash: + return data.NewConstr( + 1, + data.NewByteString(c.Credential.Bytes()), + ) + } + return nil +} diff --git a/ledger/shelley/shelley.go b/ledger/shelley/shelley.go index a6f3ed4c..99a7d7ee 100644 --- a/ledger/shelley/shelley.go +++ b/ledger/shelley/shelley.go @@ -372,10 +372,8 @@ func (i ShelleyTransactionInput) Utxorpc() (*utxorpc.TxInput, error) { func (i ShelleyTransactionInput) ToPlutusData() data.PlutusData { return data.NewConstr( 0, - []data.PlutusData{ - data.NewByteString(i.TxId.Bytes()), - data.NewInteger(big.NewInt(int64(i.OutputIndex))), - }, + data.NewByteString(i.TxId.Bytes()), + data.NewInteger(big.NewInt(int64(i.OutputIndex))), ) } diff --git a/ledger/shelley/tx_test.go b/ledger/shelley/tx_test.go index e67081c7..5bd7b068 100644 --- a/ledger/shelley/tx_test.go +++ b/ledger/shelley/tx_test.go @@ -37,10 +37,8 @@ func TestShelleyTransactionInputToPlutusData(t *testing.T) { } expectedData := data.NewConstr( 0, - []data.PlutusData{ - data.NewByteString(testTxId), - data.NewInteger(big.NewInt(int64(testTxOutputIdx))), - }, + data.NewByteString(testTxId), + data.NewInteger(big.NewInt(int64(testTxOutputIdx))), ) tmpData := testInput.ToPlutusData() if !reflect.DeepEqual(tmpData, expectedData) {