diff --git a/go.mod b/go.mod index 090b464e..8a3897b4 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.6 + github.com/blinklabs-io/plutigo v0.0.7 github.com/btcsuite/btcd/btcutil v1.1.6 github.com/fxamacker/cbor/v2 v2.9.0 github.com/jinzhu/copier v0.4.0 @@ -18,10 +18,17 @@ require ( ) require ( + github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/consensys/gnark-crypto v0.18.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/sys v0.35.0 // indirect google.golang.org/protobuf v1.36.6 // indirect diff --git a/go.sum b/go.sum index 675b1807..a6844585 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,20 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= +github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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.6 h1:malkUZ9K1mPMnqWKQLQSS8lX0ott+N7GCdAjRhMIV1M= -github.com/blinklabs-io/plutigo v0.0.6/go.mod h1:gxTWAu9n7+4SgQ+zAoO91LYU+5WanUNdRkl9mLdm8f8= +github.com/blinklabs-io/plutigo v0.0.7 h1:wgb7v47FggrZEfikolV12WhPsWEXzFyzLtak5IrAOEk= +github.com/blinklabs-io/plutigo v0.0.7/go.mod h1:gxTWAu9n7+4SgQ+zAoO91LYU+5WanUNdRkl9mLdm8f8= 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= github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU= +github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= @@ -18,6 +22,7 @@ github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/ github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= @@ -28,6 +33,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= +github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -35,7 +42,11 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -62,10 +73,12 @@ github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -78,6 +91,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -131,13 +146,15 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ledger/babbage/babbage.go b/ledger/babbage/babbage.go index c796f0bf..7cb65d7b 100644 --- a/ledger/babbage/babbage.go +++ b/ledger/babbage/babbage.go @@ -528,16 +528,38 @@ func (o BabbageTransactionOutput) ToPlutusData() data.PlutusData { assetDataMap.Pairs..., ) } + var datumOptionPd data.PlutusData + switch { + case o.DatumOption == nil: + datumOptionPd = data.NewConstr(0) + case o.DatumOption.hash != nil: + datumOptionPd = data.NewConstr( + 1, + data.NewByteString(o.DatumOption.hash.Bytes()), + ) + case o.DatumOption.data != nil: + datumOptionPd = data.NewConstr( + 2, + o.DatumOption.data.Data, + ) + } + var scriptRefPd data.PlutusData + if o.TxOutScriptRef == nil { + scriptRefPd = data.NewConstr(1) + } else { + scriptRefPd = data.NewConstr( + 0, + data.NewByteString( + o.TxOutScriptRef.Script.Hash().Bytes(), + ), + ) + } tmpData := data.NewConstr( 0, o.OutputAddress.ToPlutusData(), data.NewMap(valueData), - // Empty datum option - // TODO: implement this - data.NewConstr(0), - // Empty script ref - // TODO: implement this - data.NewConstr(1), + datumOptionPd, + scriptRefPd, ) return tmpData } diff --git a/ledger/common/script.go b/ledger/common/script.go index 9212de83..33d938c9 100644 --- a/ledger/common/script.go +++ b/ledger/common/script.go @@ -20,6 +20,9 @@ import ( "slices" "github.com/blinklabs-io/gouroboros/cbor" + "github.com/blinklabs-io/plutigo/cek" + "github.com/blinklabs-io/plutigo/data" + "github.com/blinklabs-io/plutigo/syn" ) const ( @@ -137,6 +140,49 @@ func (s PlutusV3Script) Hash() ScriptHash { ) } +func (s PlutusV3Script) Evaluate(scriptContext data.PlutusData, budget ExUnits) (ExUnits, error) { + var usedExUnits ExUnits + // Set budget + machineBudget := cek.DefaultExBudget + if budget.Steps > 0 || budget.Memory > 0 { + machineBudget = cek.ExBudget{ + Cpu: int64(budget.Steps), // nolint: gosec + Mem: int64(budget.Memory), // nolint: gosec + } + } + // Decode raw script as bytestring to get actual script bytes + var innerScript []byte + if _, err := cbor.Decode([]byte(s), &innerScript); err != nil { + return usedExUnits, err + } + // Decode program + program, err := syn.Decode[syn.DeBruijn]([]byte(innerScript)) + if err != nil { + return usedExUnits, fmt.Errorf("decode script: %w", err) + } + // Apply script context to program + contextTerm := &syn.Constant{ + Con: &syn.Data{ + Inner: scriptContext, + }, + } + wrappedProgram := &syn.Apply[syn.DeBruijn]{ + Function: program.Term, + Argument: contextTerm, + } + // Execute wrapped program + machine := cek.NewMachine[syn.DeBruijn](200) + machine.ExBudget = machineBudget + _, err = machine.Run(wrappedProgram) + if err != nil { + return usedExUnits, fmt.Errorf("execute script: %w", err) + } + consumedBudget := machineBudget.Sub(&machine.ExBudget) + usedExUnits.Memory = uint64(consumedBudget.Mem) // nolint:gosec + usedExUnits.Steps = uint64(consumedBudget.Cpu) // nolint:gosec + return usedExUnits, nil +} + type NativeScript struct { cbor.DecodeStoreCbor item any diff --git a/ledger/common/script/scriptcontext.go b/ledger/common/script/scriptcontext.go index e402b41c..1532ca2c 100644 --- a/ledger/common/script/scriptcontext.go +++ b/ledger/common/script/scriptcontext.go @@ -15,9 +15,9 @@ package script import ( + "bytes" "math/big" "slices" - "strings" lcommon "github.com/blinklabs-io/gouroboros/ledger/common" "github.com/blinklabs-io/plutigo/data" @@ -139,6 +139,13 @@ 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), @@ -150,9 +157,8 @@ func (t TxInfoV3) ToPlutusData() data.PlutusData { toPlutusData([]any{}), toPlutusData(t.Withdrawals), t.ValidRange.ToPlutusData(), - // TODO: signatories - toPlutusData([]any{}), - t.Redeemers.ToPlutusData(), + toPlutusData(t.Signatories), + tmpRedeemers.ToPlutusData(), t.Data.ToPlutusData(), data.NewByteString(t.Id.Bytes()), // TODO: votes @@ -174,10 +180,11 @@ func NewTxInfoV3FromTransaction( if assetMint == nil { assetMint = &lcommon.MultiAsset[lcommon.MultiAssetTypeMint]{} } - inputs := sortInputs(tx.Consumed()) + inputs := sortInputs(tx.Inputs()) redeemers := redeemersInfo( tx.Witnesses(), scriptPurposeBuilder( + resolvedInputs, inputs, *assetMint, // TODO: certificates @@ -186,6 +193,7 @@ func NewTxInfoV3FromTransaction( // TODO: votes ), ) + tmpData := dataInfo(tx.Witnesses()) ret := TxInfoV3{ Inputs: expandInputs(inputs, resolvedInputs), ReferenceInputs: expandInputs( @@ -200,10 +208,10 @@ func NewTxInfoV3FromTransaction( tx.ValidityIntervalStart(), }, Withdrawals: tx.Withdrawals(), - // TODO: Signatories - Redeemers: redeemers, - // TODO: Data - Id: tx.Hash(), + Signatories: signatoriesInfo(tx.RequiredSigners()), + Redeemers: redeemers, + Data: tmpData, + Id: tx.Hash(), // TODO: Votes // TODO: ProposalProcedures // TODO: CurrentTreasuryAmount @@ -345,10 +353,11 @@ func sortInputs(inputs []lcommon.TransactionInput) []lcommon.TransactionInput { ret, func(a, b lcommon.TransactionInput) int { // Compare TX ID - x := strings.Compare(a.Id().String(), b.Id().String()) + x := bytes.Compare(a.Id().Bytes(), b.Id().Bytes()) if x != 0 { return x } + // Compare index if a.Index() < b.Index() { return -1 } else if a.Index() > b.Index() { @@ -413,6 +422,29 @@ func sortedRedeemerKeys( return ret } +func dataInfo( + witnessSet lcommon.TransactionWitnessSet, +) KeyValuePairs[lcommon.DatumHash, data.PlutusData] { + var ret KeyValuePairs[lcommon.DatumHash, data.PlutusData] + for _, datum := range witnessSet.PlutusData() { + ret = append( + ret, + KeyValuePair[lcommon.DatumHash, data.PlutusData]{ + Key: datum.Hash(), + Value: datum.Data, + }, + ) + } + // Sort by datum hash + slices.SortFunc( + ret, + func(a, b KeyValuePair[lcommon.DatumHash, data.PlutusData]) int { + return bytes.Compare(a.Key.Bytes(), b.Key.Bytes()) + }, + ) + return ret +} + func redeemersInfo( witnessSet lcommon.TransactionWitnessSet, toScriptPurpose toScriptPurposeFunc, @@ -422,8 +454,7 @@ func redeemersInfo( redeemerKeys := sortedRedeemerKeys(redeemers) for _, key := range redeemerKeys { redeemerValue := redeemers.Value(uint(key.Index), key.Tag) - datum := redeemerValue.Data.Data - purpose := toScriptPurpose(key, datum) + purpose := toScriptPurpose(key) ret = append( ret, KeyValuePair[ScriptInfo, Redeemer]{ @@ -431,7 +462,7 @@ func redeemersInfo( Value: Redeemer{ Tag: key.Tag, Index: key.Index, - Data: datum, + Data: redeemerValue.Data.Data, ExUnits: redeemerValue.ExUnits, }, }, @@ -440,10 +471,25 @@ func redeemersInfo( return ret } -type toScriptPurposeFunc func(lcommon.RedeemerKey, data.PlutusData) ScriptInfo +func signatoriesInfo( + requiredSigners []lcommon.Blake2b224, +) []lcommon.Blake2b224 { + tmp := make([]lcommon.Blake2b224, len(requiredSigners)) + copy(tmp, requiredSigners) + slices.SortFunc( + tmp, + func(a, b lcommon.Blake2b224) int { + return bytes.Compare(a.Bytes(), b.Bytes()) + }, + ) + 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 @@ -451,11 +497,21 @@ func scriptPurposeBuilder( // TODO: proposal procedures // TODO: votes ) toScriptPurposeFunc { - return func(redeemerKey lcommon.RedeemerKey, datum data.PlutusData) ScriptInfo { + 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, @@ -463,8 +519,12 @@ func scriptPurposeBuilder( 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[0], + PolicyId: mintPolicies[redeemerKey.Index], } case lcommon.RedeemerTagCert: return nil @@ -478,3 +538,12 @@ func scriptPurposeBuilder( 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/scriptcontext_test.go index a1923946..f4ea0baf 100644 --- a/ledger/common/script/scriptcontext_test.go +++ b/ledger/common/script/scriptcontext_test.go @@ -18,13 +18,13 @@ import ( "encoding/hex" "errors" "fmt" - "os" "regexp" "strings" "testing" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/gouroboros/ledger/babbage" + "github.com/blinklabs-io/gouroboros/ledger/common" lcommon "github.com/blinklabs-io/gouroboros/ledger/common" "github.com/blinklabs-io/gouroboros/ledger/conway" "github.com/blinklabs-io/gouroboros/ledger/shelley" @@ -120,37 +120,106 @@ func buildTxInfoV3( return txInfo, nil } -func TestScriptContextV3SimpleSend(t *testing.T) { - // NOTE: these values come from the Aiken tests - // https://github.com/aiken-lang/aiken/blob/af4e04b91e54dbba3340de03fc9e65a90f24a93b/crates/uplc/src/tx/script_context.rs#L1189-L1225 - txInfo, err := buildTxInfoV3( - `84a70081825820000000000000000000000000000000000000000000000000000000000000000000018182581d60111111111111111111111111111111111111111111111111111111111a3b9aca0002182a0b5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0d818258200000000000000000000000000000000000000000000000000000000000000000001082581d60000000000000000000000000000000000000000000000000000000001a3b9aca001101a20581840000d87980821a000f42401a05f5e100078152510101003222253330044a229309b2b2b9a1f5f6`, - `81825820000000000000000000000000000000000000000000000000000000000000000000`, - `81a300581d7039f47fd3b388ef53c48f08de24766d3e55dade6cae908cc24e0f4f3e011a3b9aca00028201d81843d87980`, - ) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - // Extract purpose and redeemer from first redeemer in TxInfo - redeemerPair := txInfo.(TxInfoV3).Redeemers[0] - purpose := redeemerPair.Key - redeemer := redeemerPair.Value - // Build script context - sc := NewScriptContextV3(txInfo, redeemer, purpose) - // Read expected structure from file - expectedBytes, err := os.ReadFile( - `testdata/simple_send_expected_structure.txt`, - ) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - expected := strings.TrimSpace(string(expectedBytes)) - scPd := strings.TrimSpace(formatPlutusData(sc.ToPlutusData())) - if scPd != expected { - t.Fatalf( - "did not get expected structure\n\n got:\n\n%s\n\n wanted:\n\n%s", - scPd, - expected, +var scriptContextV3TestDefs = []struct { + name string + txHex string + inputsHex string + outputsHex string + redeemerTag common.RedeemerTag + redeemerIndex uint32 + expectedCbor string +}{ + { + name: "SimpleSend", + txHex: `84a70081825820000000000000000000000000000000000000000000000000000000000000000000018182581d60111111111111111111111111111111111111111111111111111111111a3b9aca0002182a0b5820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0d818258200000000000000000000000000000000000000000000000000000000000000000001082581d60000000000000000000000000000000000000000000000000000000001a3b9aca001101a20581840000d87980821a000f42401a05f5e100078152510101003222253330044a229309b2b2b9a1f5f6`, + inputsHex: `81825820000000000000000000000000000000000000000000000000000000000000000000`, + outputsHex: `81a300581d7039f47fd3b388ef53c48f08de24766d3e55dade6cae908cc24e0f4f3e011a3b9aca00028201d81843d87980`, + redeemerTag: common.RedeemerTagSpend, + redeemerIndex: 0, + expectedCbor: `d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd87a9f581c39f47fd3b388ef53c48f08de24766d3e55dade6cae908cc24e0f4f3effd87a80ffa140a1401a3b9aca00d87b9fd87980ffd87a80ffffff809fd8799fd8799fd8799f581c11111111111111111111111111111111111111111111111111111111ffd87a80ffa140a1401a3b9aca00d87980d87a80ffff182aa080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a1d87a9fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffffd87980a0582078ec148ea647cf9969446891af31939c5d57b275a2455706782c6183ef0b62f1a080d87a80d87a80ffd87980d87a9fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd87980ffffff`, + }, + { + name: "Mint", + txHex: "84a9008182582000000000000000000000000000000000000000000000000000" + + "00000000000000000183a300581d600000000000000000000000000000000000" + + "0000000000000000000000011a000f42400282005820923918e403bf43c34b4e" + + "f6b48eb2ee04babed17320d8d1b9ff9ad086e86f44eca2005839000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000001821a000f4240a2581c12593b" + + "4cbf7fdfd8636db99fe356437cd6af8539aadaa0a401964874a14474756e611b" + + "00005af3107a4000581c0c8eaf490c53afbf27e3d84a3b57da51fbafe5aa7844" + + "3fcec2dc262ea14561696b656e182aa300583910000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "00000000000000000000000001821a000f4240a1581c0c8eaf490c53afbf27e3" + + "d84a3b57da51fbafe5aa78443fcec2dc262ea14763617264616e6f0103d81847" + + "82034463666f6f02182a09a2581c12593b4cbf7fdfd8636db99fe356437cd6af" + + "8539aadaa0a401964874a14474756e611b00005af3107a4000581c0c8eaf490c" + + "53afbf27e3d84a3b57da51fbafe5aa78443fcec2dc262ea24763617264616e6f" + + "014561696b656e2d0b5820ffffffffffffffffffffffffffffffffffffffffff" + + "ffffffffffffffffffffff0d8182582000000000000000000000000000000000" + + "00000000000000000000000000000000001082581d6000000000000000000000" + + "0000000000000000000000000000000000001a3b9aca00110112818258200000" + + "00000000000000000000000000000000000000000000000000000000000000a3" + + "0582840100d87980821a000f42401a05f5e100840101182a821a000f42401a05" + + "f5e1000481d879800782587d587b010100323232323232322533333300800115" + + "3330033370e900018029baa001153330073006375400224a6660089445261533" + + "0054911856616c696461746f722072657475726e65642066616c736500136560" + + "02002002002002002153300249010b5f746d70323a20566f696400165734ae71" + + "55ceaab9e5573eae915895589301010032323232323232253333330080011533" + + "30033370e900018029baa001153330073006375400224a666008a6600a920110" + + "5f5f5f5f5f6d696e745f325f5f5f5f5f0014a22930a99802a4811856616c6964" + + "61746f722072657475726e65642066616c736500136560020020020020020021" + + "53300249010b5f746d70323a20566f696400165734ae7155ceaab9e5573eae91" + + "f5f6", + inputsHex: "81825820000000000000000000000000000000000000000000000000000000000000000000", + outputsHex: "81a200581d6000000000000000000000000000000000000000000000000000000000011a000f4240", + redeemerTag: common.RedeemerTagMint, + redeemerIndex: 1, + expectedCbor: "d8799fd8799f9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87980d87a80ffffff9fd8799fd8799f5820000000000000000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87980d87a80ffffff9fd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd87a80ffa140a1401a000f4240d87a9f5820923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ecffd87a80ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffffffffa340a1401a000f4240581c0c8eaf490c53afbf27e3d84a3b57da51fbafe5aa78443fcec2dc262ea14561696b656e182a581c12593b4cbf7fdfd8636db99fe356437cd6af8539aadaa0a401964874a14474756e611b00005af3107a4000d87980d87a80ffd8799fd8799fd87a9f581c00000000000000000000000000000000000000000000000000000000ffd8799fd8799fd8799f581c00000000000000000000000000000000000000000000000000000000ffffffffa240a1401a000f4240581c0c8eaf490c53afbf27e3d84a3b57da51fbafe5aa78443fcec2dc262ea14763617264616e6f01d87980d8799f581c68ad54b3a8124d9fe5caaaf2011a85d72096e696a2fb3d7f86c41717ffffff182aa2581c0c8eaf490c53afbf27e3d84a3b57da51fbafe5aa78443fcec2dc262ea24561696b656e2d4763617264616e6f01581c12593b4cbf7fdfd8636db99fe356437cd6af8539aadaa0a401964874a14474756e611b00005af3107a400080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff80a2d8799f581c0c8eaf490c53afbf27e3d84a3b57da51fbafe5aa78443fcec2dc262effd87980d8799f581c12593b4cbf7fdfd8636db99fe356437cd6af8539aadaa0a401964874ff182aa15820923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ecd879805820e757985e48e43a95a185ddba08c814bc20f81cb68544ac937a9b992e4e6c38a0a080d87a80d87a80ff182ad8799f581c12593b4cbf7fdfd8636db99fe356437cd6af8539aadaa0a401964874ffff", + }, + { + name: "SendWithRequiredSigners", + txHex: "84a90081825820c9f355431b2546acfc03b11540dc8bee31b9afc5674081a61cc39e0845380e7a000181825839009fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e505064b671634d14cb8d543e71dd8eb437a47efb47b0b22882866c420d1a009569e4021a00032c9c0b5820b460412e9073d311f8ebbc4e931ca4cb709d491b5d50d06b6bfede0a959eba1b0d81825820c9f355431b2546acfc03b11540dc8bee31b9afc5674081a61cc39e0845380e7a010e81581c9fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e5010825839009fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e505064b671634d14cb8d543e71dd8eb437a47efb47b0b22882866c420d821b00000001f6b06007a7581c0d26f1decee50c24498585cb9cba2b6aa629c83023b327bb10fb67b9a14c4d696e7457697468647261770e581c22691d3d969ecf5802226290c2fb98e2bc08522d5b726c1f5f400105a3534275726e61626c65546f6b656e506c75747573014454657374181c5820accbfb633f637e3bb1abee40c9539d1effd742cd2716b3b1db9de3aaf3f3779401581c61d96f9000bf5d325da17258ee0693e19d441cecee64825289ee6b7da14c4d696e74576974686472617701581c966a0d009812f82bf198cfa1329602f48e0f600cff430de75ca90fc5a1476d79746f6b656e01581ccac67dd80f706e084b2aac605288b2ff793475ea43b2313e1ed384aba14454657374182a581cef6ed47a6917a3cbbeb46561e8853da969343794d66128598a34af2ca34d4275726e61626c65546f6b656e18574e4275726e61626c65546f6b656e3218f35820accbfb633f637e3bb1abee40c9539d1effd742cd2716b3b1db9de3aaf3f3779401581cf654f6a31f6c4cc2c39a169f2c022404aa9f19d43137b0448b219a3ea144546573741823111a004c4b4012818258204f184460ae3f3c15c6811f1380c3abc7bb73bf3cbbdffe874913c2410fc3577600a200818258200abb7b89e091dcd3201aea501854a4cb05290862d88b6eb30afa6dfd23f5446758403ccad89397ca9e583ae03b072b9ddf3b9c3840cde959339afc5c7c88d9a33b92599336baf7c46876806babfa04dd1b2bf7b77d35a4780b56eb5400d30dc908090581840000d8799f4d48656c6c6f2c20576f726c6421ff82196def1a0087f824f5f6", + inputsHex: "82825820c9f355431b2546acfc03b11540dc8bee31b9afc5674081a61cc39e0845380e7a008258204f184460ae3f3c15c6811f1380c3abc7bb73bf3cbbdffe874913c2410fc3577600", + outputsHex: "82a400581d70fdd6640d1c9a4392dd7e829f0cc4e26766539c48b2cf594959e33559011a00989680028201d8185822d8799f581c9fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e50ff03d81859010b820359010659010301010032323232323225333002323232323253330073370e900118041baa0011323232533300a3370e900018059baa00513232533300f30110021533300c3370e900018069baa003132533300d3371e6eb8c044c03cdd50042450d48656c6c6f2c20576f726c642100100114a06644646600200200644a66602600229404c94ccc044cdc79bae301500200414a2266006006002602a0026eb0c040c044c044c044c044c044c044c044c044c038dd50049bae3010300e37546020601c6ea800c5858dd7180780098061baa00516300d300e002300c001300937540022c6014601600460120026012004600e00260086ea8004526136565734aae7555cf2ab9f5742ae89a400581d70fdd6640d1c9a4392dd7e829f0cc4e26766539c48b2cf594959e33559011a00989680028201d8185822d8799f581c86b74c779bb9cf43532b357323b0ced1bdd6aa4276c45aee845f33feff03d81859010b820359010659010301010032323232323225333002323232323253330073370e900118041baa0011323232533300a3370e900018059baa00513232533300f30110021533300c3370e900018069baa003132533300d3371e6eb8c044c03cdd50042450d48656c6c6f2c20576f726c642100100114a06644646600200200644a66602600229404c94ccc044cdc79bae301500200414a2266006006002602a0026eb0c040c044c044c044c044c044c044c044c044c038dd50049bae3010300e37546020601c6ea800c5858dd7180780098061baa00516300d300e002300c001300937540022c6014601600460120026012004600e00260086ea8004526136565734aae7555cf2ab9f5742ae89", + redeemerTag: common.RedeemerTagSpend, + redeemerIndex: 0, + expectedCbor: "d8799fd8799f9fd8799fd8799f5820c9f355431b2546acfc03b11540dc8bee31b9afc5674081a61cc39e0845380e7a00ffd8799fd8799fd87a9f581cfdd6640d1c9a4392dd7e829f0cc4e26766539c48b2cf594959e33559ffd87a80ffa140a1401a00989680d87b9fd8799f581c9fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e50ffffd8799f581cfdd6640d1c9a4392dd7e829f0cc4e26766539c48b2cf594959e33559ffffffff9fd8799fd8799f58204f184460ae3f3c15c6811f1380c3abc7bb73bf3cbbdffe874913c2410fc3577600ffd8799fd8799fd87a9f581cfdd6640d1c9a4392dd7e829f0cc4e26766539c48b2cf594959e33559ffd87a80ffa140a1401a00989680d87b9fd8799f581c86b74c779bb9cf43532b357323b0ced1bdd6aa4276c45aee845f33feffffd8799f581cfdd6640d1c9a4392dd7e829f0cc4e26766539c48b2cf594959e33559ffffffff9fd8799fd8799fd8799f581c9fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e50ffd8799fd8799fd8799f581c5064b671634d14cb8d543e71dd8eb437a47efb47b0b22882866c420dffffffffa140a1401a009569e4d87980d87a80ffff1a00032c9ca080a0d8799fd8799fd87980d87a80ffd8799fd87b80d87a80ffff9f581c9fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e50ffa1d87a9fd8799f5820c9f355431b2546acfc03b11540dc8bee31b9afc5674081a61cc39e0845380e7a00ffffd8799f4d48656c6c6f2c20576f726c6421ffa05820ac73d6545da30a8cc0395de0c2a5fa3ad99329f41d319901ebe2922f8554d5e3a080d87a80d87a80ffd8799f4d48656c6c6f2c20576f726c6421ffd87a9fd8799f5820c9f355431b2546acfc03b11540dc8bee31b9afc5674081a61cc39e0845380e7a00ffd8799fd8799f581c9fc430ea1f3adc20eebb813b2649e85c934ea5bc13d7b7fbe2b24e50ffffffff", + }, +} + +func TestScriptContextV3(t *testing.T) { + for _, testDef := range scriptContextV3TestDefs { + t.Run( + testDef.name, + func(t *testing.T) { + txInfo, err := buildTxInfoV3(testDef.txHex, testDef.inputsHex, testDef.outputsHex) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + // Extract purpose and redeemer from TxInfo + var purpose ScriptInfo + var redeemer Redeemer + for _, redeemerPair := range txInfo.(TxInfoV3).Redeemers { + if redeemerPair.Value.Tag == testDef.redeemerTag && + redeemerPair.Value.Index == testDef.redeemerIndex { + purpose = redeemerPair.Key + redeemer = redeemerPair.Value + break + } + } + // Build script context + sc := NewScriptContextV3(txInfo, redeemer, purpose) + scCbor, err := data.Encode(sc.ToPlutusData()) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + scCborHex := hex.EncodeToString(scCbor) + if scCborHex != testDef.expectedCbor { + t.Fatalf("did not get expected ScriptContext CBOR\n got: %s\n wanted: %s", scCborHex, testDef.expectedCbor) + } + }, ) } } diff --git a/ledger/common/script/wrappers.go b/ledger/common/script/wrappers.go index f1fce760..9dd92330 100644 --- a/ledger/common/script/wrappers.go +++ b/ledger/common/script/wrappers.go @@ -79,6 +79,8 @@ func toPlutusData(val any) data.PlutusData { tmpItems[i] = item.ToPlutusData() } return data.NewList(tmpItems...) + case data.PlutusData: + return v default: rv := reflect.ValueOf(v) // nolint:exhaustive