diff --git a/go.mod b/go.mod index 92e546feb..72260ea13 100644 --- a/go.mod +++ b/go.mod @@ -8,12 +8,14 @@ require ( github.com/gagliardetto/gofuzz v1.2.2 github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 github.com/gagliardetto/treeout v0.1.4 + github.com/gagliardetto/utilz v0.1.1 github.com/google/uuid v1.3.1 - github.com/hashicorp/go-plugin v1.5.2 + github.com/hashicorp/go-plugin v1.6.0 + github.com/mitchellh/mapstructure v1.5.0 github.com/pelletier/go-toml/v2 v2.1.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240213113935-001c2f4befd4 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240228160036-7d796e1d1680 github.com/smartcontractkit/libocr v0.0.0-20240112202000-6359502d2ff1 github.com/stretchr/testify v1.8.4 github.com/test-go/testify v1.1.4 @@ -21,6 +23,7 @@ require ( go.uber.org/zap v1.26.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa golang.org/x/sync v0.6.0 + golang.org/x/text v0.14.0 gopkg.in/guregu/null.v4 v4.0.0 ) @@ -32,6 +35,7 @@ require ( github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blendle/zapdriver v1.3.1 // indirect + github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 // indirect @@ -47,16 +51,20 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect - github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/hashicorp/yamux v0.1.1 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.15 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/linkedin/goavro/v2 v2.12.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/miekg/dns v1.1.35 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -70,6 +78,8 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect github.com/riferrei/srclient v0.5.4 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/stretchr/objx v0.5.0 // indirect @@ -93,11 +103,11 @@ require ( golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/grpc v1.58.3 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ff155ba26..d10cd94ee 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHf github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 h1:gfAMKE626QEuKG3si0pdTRcr/YEbBoxY+3GOH3gWvl4= +github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= @@ -105,6 +107,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -142,10 +145,13 @@ github.com/gagliardetto/binary v0.7.1 h1:6ggDQ26vR+4xEvl/S13NcdLK3MUCi4oSy73pS9a github.com/gagliardetto/binary v0.7.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= +github.com/gagliardetto/hashsearch v0.0.0-20191005111333-09dd671e19f9/go.mod h1:513DXpQPzeRo7d4dsCP3xO3XI8hgvruMl9njxyQeraQ= github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 h1:q2IztKyRQUxJ6abXRsawaBtvDFvM+szj4jDqV4od1gs= github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27/go.mod h1:NFuoDwHPvw858ZMHUJr6bkhN8qHt4x6e+U3EYHxAwNY= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= +github.com/gagliardetto/utilz v0.1.1 h1:/etW4hl607emKg6R6Lj9jRJ9d6ue2AQOyjhuAwjzs1U= +github.com/gagliardetto/utilz v0.1.1/go.mod h1:b+rGFkRHz3HWJD0RYMzat47JyvbTtpE0iEcYTRJTLLA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -244,6 +250,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026 h1:BpJ2o0OR5FV7vrkDYfXYVJQeMNWa8RhklZOpW2ITAIQ= +github.com/hako/durafmt v0.0.0-20200710122514-c0fb7b4da026/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -267,8 +275,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -328,6 +336,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -380,6 +390,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -410,10 +421,13 @@ github.com/riferrei/srclient v0.5.4 h1:dfwyR5u23QF7beuVl2WemUY2KXh5+Sc4DHKyPXBNY github.com/riferrei/srclient v0.5.4/go.mod h1:vbkLmWcgYa7JgfPvuy/+K8fTS0p1bApqadxrxi/S1MI= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= @@ -424,8 +438,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240213113935-001c2f4befd4 h1:Yk0RK9WV59ISOZZMsdtxZBAKaBfdgb05oXyca/qSqcw= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240213113935-001c2f4befd4/go.mod h1:pRlQrvcizMmuHAUV4N96oO2e3XbA99JCQELLc6ES160= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240228160036-7d796e1d1680 h1:cfqibCkmWq4x8ufMvYNirQMoNxj2kcawnn06HhNy3rw= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240228160036-7d796e1d1680/go.mod h1:6aXWSEQawX2oZXcPPOdxnEGufAhj7PqPKolXf6ijRGA= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= @@ -540,6 +554,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= @@ -637,6 +652,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -658,6 +674,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -692,6 +709,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= @@ -879,6 +897,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/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= diff --git a/pkg/solana/account_read_binding.go b/pkg/solana/account_read_binding.go new file mode 100644 index 000000000..d66321a71 --- /dev/null +++ b/pkg/solana/account_read_binding.go @@ -0,0 +1,47 @@ +package solana + +import ( + "context" + "fmt" + + "github.com/gagliardetto/solana-go" + + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +// BinaryDataReader provides an interface for reading bytes from a source. This is likely a wrapper +// for a solana client. +type BinaryDataReader interface { + ReadAll(context.Context, solana.PublicKey) ([]byte, error) +} + +// accountReadBinding provides decoding and reading Solana Account data using a defined codec. The +// `idlAccount` refers to the account name in the IDL for which the codec has a type mapping. +type accountReadBinding struct { + idlAccount string + account solana.PublicKey + codec commontypes.Codec + reader BinaryDataReader +} + +var _ readBinding = &accountReadBinding{} + +func (b *accountReadBinding) GetLatestValue(ctx context.Context, _ any, outVal any) error { + bts, err := b.reader.ReadAll(ctx, b.account) + if err != nil { + return fmt.Errorf("%w: failed to get binary data", err) + } + + return b.codec.Decode(ctx, bts, outVal, b.idlAccount) +} + +func (b *accountReadBinding) Bind(contract commontypes.BoundContract) error { + account, err := solana.PublicKeyFromBase58(contract.Address) + if err != nil { + return err + } + + b.account = account + + return nil +} diff --git a/pkg/solana/bindings.go b/pkg/solana/bindings.go new file mode 100644 index 000000000..7fecebda9 --- /dev/null +++ b/pkg/solana/bindings.go @@ -0,0 +1,88 @@ +package solana + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +type readBinding interface { + GetLatestValue(ctx context.Context, params, returnVal any) error + Bind(commontypes.BoundContract) error +} + +// key is namespace +type namespaceBindings map[string]methodBindings + +// key is method name +type methodBindings map[string]readBindings + +// read bindings is a list of bindings by index +type readBindings []readBinding + +func (b namespaceBindings) AddReadBinding(namespace, methodName string, reader readBinding) { + nbs, nbsExists := b[namespace] + if !nbsExists { + nbs = methodBindings{} + b[namespace] = nbs + } + + rbs, rbsExists := nbs[methodName] + if !rbsExists { + rbs = []readBinding{} + } + + b[namespace][methodName] = append(rbs, reader) +} + +func (b namespaceBindings) GetReadBindings(namespace, methodName string) ([]readBinding, error) { + nbs, nbsExists := b[namespace] + if !nbsExists { + return nil, fmt.Errorf("%w: no read binding exists for %s", types.ErrInvalidType, namespace) + } + + rbs, rbsExists := nbs[methodName] + if !rbsExists { + return nil, fmt.Errorf("%w: no read binding exists for %s and %s", types.ErrInvalidType, namespace, methodName) + } + + return rbs, nil +} + +func (b namespaceBindings) Bind(boundContracts []commontypes.BoundContract) error { + for _, bc := range boundContracts { + parts := strings.Split(bc.Name, ".") + if len(parts) != 3 { + return fmt.Errorf("%w: BoundContract.Name must follow pattern of [namespace.method.procedure_idx]", commontypes.ErrInvalidConfig) + } + + nbs, nbsExist := b[parts[0]] + if !nbsExist { + return fmt.Errorf("%w: no namespace named %s for %s", commontypes.ErrInvalidConfig, parts[0], bc.Name) + } + + mbs, mbsExists := nbs[parts[1]] + if !mbsExists { + return fmt.Errorf("%w: no method named %s for %s", commontypes.ErrInvalidConfig, parts[1], bc.Name) + } + + val, err := strconv.Atoi(parts[2]) + if err != nil { + return fmt.Errorf("%w: procedure index not parsable for %s", commontypes.ErrInvalidConfig, bc.Name) + } + + if len(mbs) <= val { + return fmt.Errorf("%w: no procedure for index %d for %s", commontypes.ErrInvalidConfig, val, bc.Name) + } + + if err := mbs[val].Bind(bc); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/solana/chain_reader.go b/pkg/solana/chain_reader.go index 6642cf62a..85dfd8242 100644 --- a/pkg/solana/chain_reader.go +++ b/pkg/solana/chain_reader.go @@ -2,16 +2,30 @@ package solana import ( "context" + "encoding/json" + + ag_solana "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" ) const ServiceName = "SolanaChainReader" type SolanaChainReaderService struct { - lggr logger.Logger + // provided values + lggr logger.Logger + client BinaryDataReader + + // internal values + bindings namespaceBindings + + // service state management services.StateMachine } @@ -21,10 +35,18 @@ var ( ) // NewChainReaderService is a constructor for a new ChainReaderService for Solana. Returns a nil service on error. -func NewChainReaderService(lggr logger.Logger) (*SolanaChainReaderService, error) { - return &SolanaChainReaderService{ - lggr: logger.Named(lggr, ServiceName), - }, nil +func NewChainReaderService(lggr logger.Logger, dataReader BinaryDataReader, cfg config.ChainReader) (*SolanaChainReaderService, error) { + svc := &SolanaChainReaderService{ + lggr: logger.Named(lggr, ServiceName), + client: dataReader, + bindings: namespaceBindings{}, + } + + if err := svc.init(cfg.Namespaces); err != nil { + return nil, err + } + + return svc, nil } // Name implements the services.ServiceCtx interface and returns the logger service name. @@ -63,12 +85,78 @@ func (s *SolanaChainReaderService) HealthReport() map[string]error { // GetLatestValue implements the types.ChainReader interface and requests and parses on-chain // data named by the provided contract, method, and params. -func (s *SolanaChainReaderService) GetLatestValue(_ context.Context, contractName, method string, params any, returnVal any) error { - return types.UnimplementedError("GetLatestValue not available") +func (s *SolanaChainReaderService) GetLatestValue(ctx context.Context, contractName, method string, params any, returnVal any) error { + bindings, err := s.bindings.GetReadBindings(contractName, method) + if err != nil { + return err + } + + for _, binding := range bindings { + if err := binding.GetLatestValue(ctx, params, returnVal); err != nil { + return err + } + } + + return nil } // Bind implements the types.ChainReader interface and allows new contract bindings to be added // to the service. func (s *SolanaChainReaderService) Bind(_ context.Context, bindings []types.BoundContract) error { - return types.UnimplementedError("Bind not available") + return s.bindings.Bind(bindings) +} + +func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainReaderMethods) error { + for namespace, methods := range namespaces { + for methodName, method := range methods.Methods { + var idl codec.IDL + if err := json.Unmarshal([]byte(method.AnchorIDL), &idl); err != nil { + return err + } + + idlCodec, err := codec.NewIDLCodec(idl) + if err != nil { + return err + } + + for _, procedure := range method.Procedures { + mod, err := procedure.OutputModifications.ToModifier(codec.DecoderHooks...) + if err != nil { + return err + } + + codecWithModifiers, err := codec.NewNamedModifierCodec(idlCodec, procedure.IDLAccount, mod) + if err != nil { + return err + } + + s.bindings.AddReadBinding(namespace, methodName, &accountReadBinding{ + idlAccount: procedure.IDLAccount, + codec: codecWithModifiers, + reader: s.client, + }) + } + } + } + + return nil +} + +type accountDataReader struct { + client *rpc.Client +} + +func NewAccountDataReader(client *rpc.Client) *accountDataReader { + return &accountDataReader{client: client} +} + +func (r *accountDataReader) ReadAll(ctx context.Context, pk ag_solana.PublicKey) ([]byte, error) { + result, err := r.client.GetAccountInfo(ctx, pk) + if err != nil { + return nil, err + } + + bts := result.Value.Data.GetBinary() + + return bts, nil } diff --git a/pkg/solana/chain_reader_test.go b/pkg/solana/chain_reader_test.go index 9db536a6c..ca1c59296 100644 --- a/pkg/solana/chain_reader_test.go +++ b/pkg/solana/chain_reader_test.go @@ -1,21 +1,36 @@ package solana_test import ( + "context" + "fmt" "testing" + "time" - "github.com/test-go/testify/require" + ag_solana "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-solana/pkg/solana" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" +) + +const ( + Namespace = "NameSpace" + NamedMethod = "NamedMethod1" ) func TestSolanaChainReaderService_ServiceCtx(t *testing.T) { t.Parallel() ctx := tests.Context(t) - svc, err := solana.NewChainReaderService(logger.Test(t)) + svc, err := solana.NewChainReaderService(logger.Test(t), new(mockedRPCClient), config.ChainReader{}) require.NoError(t, err) require.NotNil(t, svc) @@ -35,3 +50,201 @@ func TestSolanaChainReaderService_ServiceCtx(t *testing.T) { require.Error(t, svc.Ready()) require.Error(t, svc.Close()) } + +func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { + t.Parallel() + + rawIDL, _, testCodec := codec.NewTestIDLAndCodec(t) + ctx := tests.Context(t) + conf := config.ChainReader{ + Namespaces: map[string]config.ChainReaderMethods{ + Namespace: { + Methods: map[string]config.ChainDataReader{ + NamedMethod: { + AnchorIDL: rawIDL, + Procedures: []config.ChainReaderProcedure{ + { + IDLAccount: codec.TestStructWithNestedStruct, + Type: config.ProcedureTypeAnchor, + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.RenameModifierConfig{Fields: map[string]string{"Value": "V"}}, + }, + }, + }, + }, + }, + }, + }, + } + + // encode values from unmodified test struct to be read and decoded + expected := codec.DefaultTestStruct + encoded, err := testCodec.Encode(ctx, expected, codec.TestStructWithNestedStruct) + + require.NoError(t, err) + + t.Run("Success", func(t *testing.T) { + t.Parallel() + + client := new(mockedRPCClient) + svc, err := solana.NewChainReaderService(logger.Test(t), client, conf) + + require.NoError(t, err) + require.NotNil(t, svc) + + client.On("ReadAll", mock.Anything, mock.Anything).Return(encoded, nil) + + var result modifiedStructWithNestedStruct + + require.NoError(t, svc.GetLatestValue(ctx, Namespace, NamedMethod, nil, &result)) + assert.Equal(t, expected.InnerStruct, result.InnerStruct) + assert.Equal(t, expected.Value, result.V) + // assert.Equal(t, expected.TimeVal, result.TimeVal) + assert.Equal(t, expected.DurationVal, result.DurationVal) + }) + + t.Run("Error Returned From Account Reader", func(t *testing.T) { + t.Parallel() + + client := new(mockedRPCClient) + expectedErr := fmt.Errorf("expected error") + svc, err := solana.NewChainReaderService(logger.Test(t), client, conf) + + require.NoError(t, err) + require.NotNil(t, svc) + + client.On("ReadAll", mock.Anything, mock.Anything).Return(nil, expectedErr) + + var result modifiedStructWithNestedStruct + + assert.ErrorIs(t, svc.GetLatestValue(ctx, Namespace, NamedMethod, nil, &result), expectedErr) + }) + + t.Run("Method Not Found", func(t *testing.T) { + t.Parallel() + + client := new(mockedRPCClient) + svc, err := solana.NewChainReaderService(logger.Test(t), client, conf) + + require.NoError(t, err) + require.NotNil(t, svc) + + var result modifiedStructWithNestedStruct + + assert.NotNil(t, svc.GetLatestValue(ctx, Namespace, "Unknown", nil, &result)) + }) + + t.Run("Namespace Not Found", func(t *testing.T) { + t.Parallel() + + client := new(mockedRPCClient) + svc, err := solana.NewChainReaderService(logger.Test(t), client, conf) + + require.NoError(t, err) + require.NotNil(t, svc) + + var result modifiedStructWithNestedStruct + + assert.NotNil(t, svc.GetLatestValue(ctx, "Unknown", "Unknown", nil, &result)) + }) + + t.Run("Bind Success", func(t *testing.T) { + t.Parallel() + + client := new(mockedRPCClient) + svc, err := solana.NewChainReaderService(logger.Test(t), client, conf) + + require.NoError(t, err) + require.NotNil(t, svc) + + pk := ag_solana.NewWallet().PublicKey() + err = svc.Bind(ctx, []types.BoundContract{ + { + Address: pk.String(), + Name: fmt.Sprintf("%s.%s.%d", Namespace, NamedMethod, 0), + }, + }) + + assert.NoError(t, err) + }) + + t.Run("Bind Errors", func(t *testing.T) { + t.Parallel() + + client := new(mockedRPCClient) + svc, err := solana.NewChainReaderService(logger.Test(t), client, conf) + + require.NoError(t, err) + require.NotNil(t, svc) + + pk := ag_solana.NewWallet().PublicKey() + + require.NotNil(t, svc.Bind(ctx, []types.BoundContract{ + { + Address: pk.String(), + Name: "incorrect format", + }, + })) + + require.NotNil(t, svc.Bind(ctx, []types.BoundContract{ + { + Address: pk.String(), + Name: fmt.Sprintf("%s.%s.%d", "Unknown", "Unknown", 0), + }, + })) + + require.NotNil(t, svc.Bind(ctx, []types.BoundContract{ + { + Address: pk.String(), + Name: fmt.Sprintf("%s.%s.%d", Namespace, "Unknown", 0), + }, + })) + + require.NotNil(t, svc.Bind(ctx, []types.BoundContract{ + { + Address: pk.String(), + Name: fmt.Sprintf("%s.%s.%d", Namespace, NamedMethod, 1), + }, + })) + + require.NotNil(t, svc.Bind(ctx, []types.BoundContract{ + { + Address: pk.String(), + Name: fmt.Sprintf("%s.%s.o", Namespace, NamedMethod), + }, + })) + + require.NotNil(t, svc.Bind(ctx, []types.BoundContract{ + { + Address: "invalid", + Name: fmt.Sprintf("%s.%s.%d", Namespace, NamedMethod, 0), + }, + })) + }) +} + +type modifiedStructWithNestedStruct struct { + V uint8 + InnerStruct codec.ObjectRef1 + BasicNestedArray [][]uint32 + Option *string + DefinedArray []codec.ObjectRef2 + TimeVal time.Time + DurationVal time.Duration +} + +type mockedRPCClient struct { + mock.Mock +} + +func (_m *mockedRPCClient) ReadAll(ctx context.Context, pk ag_solana.PublicKey) ([]byte, error) { + ret := _m.Called(ctx, pk) + + var r0 []byte + + if val, ok := ret.Get(0).([]byte); ok { + r0 = val + } + + return r0, ret.Error(1) +} diff --git a/pkg/solana/codec/anchoridl.go b/pkg/solana/codec/anchoridl.go new file mode 100644 index 000000000..359409d5c --- /dev/null +++ b/pkg/solana/codec/anchoridl.go @@ -0,0 +1,403 @@ +package codec + +/* +copied from https://github.com/gagliardetto/anchor-go where the IDL definition is not importable due to being defined +in the `main` package. +*/ + +import ( + "encoding/json" + "fmt" + + "github.com/davecgh/go-spew/spew" + "github.com/gagliardetto/utilz" +) + +// https://github.com/project-serum/anchor/blob/97e9e03fb041b8b888a9876a7c0676d9bb4736f3/ts/src/idl.ts +type IDL struct { + Version string `json:"version"` + Name string `json:"name"` + Instructions []IdlInstruction `json:"instructions"` + Accounts IdlTypeDefSlice `json:"accounts,omitempty"` + Types IdlTypeDefSlice `json:"types,omitempty"` + Events []IdlEvent `json:"events,omitempty"` + Errors []IdlErrorCode `json:"errors,omitempty"` + Constants []IdlConstant `json:"constants,omitempty"` +} + +type IdlConstant struct { + Name string + Type IdlType + Value string +} + +type IdlTypeDefSlice []IdlTypeDef + +func (named IdlTypeDefSlice) GetByName(name string) *IdlTypeDef { + for i := range named { + v := named[i] + if v.Name == name { + return &v + } + } + return nil +} + +type IdlEvent struct { + Name string `json:"name"` + Fields []IdlEventField `json:"fields"` +} + +type IdlEventField struct { + Name string `json:"name"` + Type IdlType `json:"type"` + Index bool `json:"index"` +} + +type IdlInstruction struct { + Name string `json:"name"` + Docs []string `json:"docs"` // @custom + Accounts IdlAccountItemSlice `json:"accounts"` + Args []IdlField `json:"args"` +} + +type IdlAccountItemSlice []IdlAccountItem + +func (slice IdlAccountItemSlice) NumAccounts() (count int) { + + for _, item := range slice { + if item.IdlAccount != nil { + count++ + } + + if item.IdlAccounts != nil { + count += item.IdlAccounts.Accounts.NumAccounts() + } + } + + return count +} + +// type IdlAccountItem = IdlAccount | IdlAccounts; +type IdlAccountItem struct { + IdlAccount *IdlAccount + IdlAccounts *IdlAccounts +} + +func (env *IdlAccountItem) UnmarshalJSON(data []byte) error { + var temp interface{} + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + if temp == nil { + return fmt.Errorf("envelope is nil: %v", env) + } + + switch v := temp.(type) { + case map[string]interface{}: + if len(v) == 0 { + return nil + } + + // Multiple accounts: + if _, ok := v["accounts"]; ok { + if err := utilz.TranscodeJSON(temp, &env.IdlAccounts); err != nil { + return err + } + } + // Single account: + // TODO: check both isMut and isSigner + if _, ok := v["isMut"]; ok { + if err := utilz.TranscodeJSON(temp, &env.IdlAccount); err != nil { + return err + } + } + default: + return fmt.Errorf("Unknown kind: %s", spew.Sdump(temp)) + } + + return nil +} + +type IdlAccount struct { + Docs []string `json:"docs"` // @custom + Name string `json:"name"` + IsMut bool `json:"isMut"` + IsSigner bool `json:"isSigner"` + Optional bool `json:"optional"` // @custom +} + +// A nested/recursive version of IdlAccount. +type IdlAccounts struct { + Name string `json:"name"` + Docs []string `json:"docs"` // @custom + Accounts IdlAccountItemSlice `json:"accounts"` +} + +type IdlField struct { + Name string `json:"name"` + Docs []string `json:"docs"` // @custom + Type IdlType `json:"type"` +} + +type IdlTypeAsString string + +const ( + IdlTypeBool IdlTypeAsString = "bool" + IdlTypeU8 IdlTypeAsString = "u8" + IdlTypeI8 IdlTypeAsString = "i8" + IdlTypeU16 IdlTypeAsString = "u16" + IdlTypeI16 IdlTypeAsString = "i16" + IdlTypeU32 IdlTypeAsString = "u32" + IdlTypeI32 IdlTypeAsString = "i32" + IdlTypeU64 IdlTypeAsString = "u64" + IdlTypeI64 IdlTypeAsString = "i64" + IdlTypeU128 IdlTypeAsString = "u128" + IdlTypeI128 IdlTypeAsString = "i128" + IdlTypeBytes IdlTypeAsString = "bytes" + IdlTypeString IdlTypeAsString = "string" + IdlTypePublicKey IdlTypeAsString = "publicKey" + + // Custom additions: + IdlTypeUnixTimestamp IdlTypeAsString = "unixTimestamp" + IdlTypeHash IdlTypeAsString = "hash" + IdlTypeDuration IdlTypeAsString = "duration" +) + +type IdlTypeVec struct { + Vec IdlType `json:"vec"` +} + +type IdlTypeOption struct { + Option IdlType `json:"option"` +} + +// User defined type. +type IdlTypeDefined struct { + Defined string `json:"defined"` +} + +// Wrapper type: +type IdlTypeArray struct { + Thing IdlType + Num int +} + +func (env *IdlType) UnmarshalJSON(data []byte) error { + var temp interface{} + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + if temp == nil { + return fmt.Errorf("envelope is nil: %v", env) + } + + switch v := temp.(type) { + case string: + env.asString = IdlTypeAsString(v) + case map[string]interface{}: + if len(v) == 0 { + return nil + } + + if _, ok := v["vec"]; ok { + var target IdlTypeVec + if err := utilz.TranscodeJSON(temp, &target); err != nil { + return err + } + env.asIdlTypeVec = &target + } + if _, ok := v["option"]; ok { + var target IdlTypeOption + if err := utilz.TranscodeJSON(temp, &target); err != nil { + return err + } + env.asIdlTypeOption = &target + } + if _, ok := v["defined"]; ok { + var target IdlTypeDefined + if err := utilz.TranscodeJSON(temp, &target); err != nil { + return err + } + env.asIdlTypeDefined = &target + } + if got, ok := v["array"]; ok { + + if _, ok := got.([]interface{}); !ok { + panic(utilz.Sf("array is not in expected format:\n%s", spew.Sdump(got))) + } + arrVal := got.([]interface{}) + if len(arrVal) != 2 { + panic(utilz.Sf("array is not of expected length:\n%s", spew.Sdump(got))) + } + var target IdlTypeArray + if err := utilz.TranscodeJSON(arrVal[0], &target.Thing); err != nil { + return err + } + + target.Num = int(arrVal[1].(float64)) + + env.asIdlTypeArray = &target + } + default: + return fmt.Errorf("Unknown kind: %s", spew.Sdump(temp)) + } + + return nil +} + +// Wrapper type: +type IdlType struct { + asString IdlTypeAsString + asIdlTypeVec *IdlTypeVec + asIdlTypeOption *IdlTypeOption + asIdlTypeDefined *IdlTypeDefined + asIdlTypeArray *IdlTypeArray +} + +func (env *IdlType) IsString() bool { + return env.asString != "" +} +func (env *IdlType) IsIdlTypeVec() bool { + return env.asIdlTypeVec != nil +} +func (env *IdlType) IsIdlTypeOption() bool { + return env.asIdlTypeOption != nil +} +func (env *IdlType) IsIdlTypeDefined() bool { + return env.asIdlTypeDefined != nil +} +func (env *IdlType) IsArray() bool { + return env.asIdlTypeArray != nil +} + +// Getters: +func (env *IdlType) GetString() IdlTypeAsString { + return env.asString +} +func (env *IdlType) GetIdlTypeVec() *IdlTypeVec { + return env.asIdlTypeVec +} +func (env *IdlType) GetIdlTypeOption() *IdlTypeOption { + return env.asIdlTypeOption +} +func (env *IdlType) GetIdlTypeDefined() *IdlTypeDefined { + return env.asIdlTypeDefined +} +func (env *IdlType) GetArray() *IdlTypeArray { + return env.asIdlTypeArray +} + +type IdlTypeDef struct { + Name string `json:"name"` + Type IdlTypeDefTy `json:"type"` +} + +type IdlTypeDefTyKind string + +const ( + IdlTypeDefTyKindStruct IdlTypeDefTyKind = "struct" + IdlTypeDefTyKindEnum IdlTypeDefTyKind = "enum" +) + +type IdlTypeDefTyStruct struct { + Kind IdlTypeDefTyKind `json:"kind"` // == "struct" + + Fields *IdlTypeDefStruct `json:"fields,omitempty"` +} + +type IdlTypeDefTyEnum struct { + Kind IdlTypeDefTyKind `json:"kind"` // == "enum" + + Variants IdlEnumVariantSlice `json:"variants,omitempty"` +} + +type IdlTypeDefTy struct { + Kind IdlTypeDefTyKind `json:"kind"` + + Fields *IdlTypeDefStruct `json:"fields,omitempty"` + Variants IdlEnumVariantSlice `json:"variants,omitempty"` +} + +type IdlEnumVariantSlice []IdlEnumVariant + +func (slice IdlEnumVariantSlice) IsAllUint8() bool { + for _, elem := range slice { + if !elem.IsUint8() { + return false + } + } + return true +} + +func (slice IdlEnumVariantSlice) IsSimpleEnum() bool { + return slice.IsAllUint8() +} + +type IdlTypeDefStruct = []IdlField + +type IdlEnumVariant struct { + Name string `json:"name"` + Docs []string `json:"docs"` // @custom + Fields *IdlEnumFields `json:"fields,omitempty"` +} + +func (variant *IdlEnumVariant) IsUint8() bool { + // it's a simple uint8 if there is no fields data + return variant.Fields == nil +} + +// type IdlEnumFields = IdlEnumFieldsNamed | IdlEnumFieldsTuple; +type IdlEnumFields struct { + IdlEnumFieldsNamed *IdlEnumFieldsNamed + IdlEnumFieldsTuple *IdlEnumFieldsTuple +} + +type IdlEnumFieldsNamed []IdlField + +type IdlEnumFieldsTuple []IdlType + +// TODO: verify with examples +func (env *IdlEnumFields) UnmarshalJSON(data []byte) error { + var temp interface{} + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + if temp == nil { + return fmt.Errorf("envelope is nil: %v", env) + } + + switch v := temp.(type) { + case []interface{}: + if len(v) == 0 { + return nil + } + + firstItem := v[0] + + if _, ok := firstItem.(map[string]interface{})["name"]; ok { + // TODO: + // If has `name` field, then it's most likely a IdlEnumFieldsNamed. + if err := utilz.TranscodeJSON(temp, &env.IdlEnumFieldsNamed); err != nil { + return err + } + } else { + if err := utilz.TranscodeJSON(temp, &env.IdlEnumFieldsTuple); err != nil { + return err + } + } + default: + return fmt.Errorf("Unknown kind: %s", spew.Sdump(temp)) + } + + return nil +} + +type IdlErrorCode struct { + Code int `json:"code"` + Name string `json:"name"` + Msg string `json:"msg,omitempty"` +} diff --git a/pkg/solana/codec/solana.go b/pkg/solana/codec/solana.go new file mode 100644 index 000000000..07d695e43 --- /dev/null +++ b/pkg/solana/codec/solana.go @@ -0,0 +1,281 @@ +package codec + +import ( + "github.com/mitchellh/mapstructure" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary" + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +const ( + DefaultHashByteLength = 32 +) + +// BigIntHook allows *big.Int to be represented as any integer type or a string and to go back to them. +// Useful for config, or if when a model may use a go type that isn't a *big.Int when Pack expects one. +// Eg: int32 in a go struct from a plugin could require a *big.Int in Pack for int24, if it fits, we shouldn't care. +// SliceToArrayVerifySizeHook verifies that slices have the correct size when converting to an array +var DecoderHooks = []mapstructure.DecodeHookFunc{codec.BigIntHook, codec.SliceToArrayVerifySizeHook} + +func NewNamedModifierCodec(original types.RemoteCodec, itemType string, modifier codec.Modifier) (types.RemoteCodec, error) { + mod, err := codec.NewByItemTypeModifier(map[string]codec.Modifier{itemType: modifier}) + if err != nil { + return nil, err + } + + modCodec, err := codec.NewModifierCodec(original, mod, DecoderHooks...) + if err != nil { + return nil, err + } + + // TODO: not sure why this needs to be called before using the codec with modifiers + // if this is left out, calls to Decode will fail unless Encode is called first + // Providing forEncoding = true | false does not seem to matter + _, err = modCodec.CreateType(itemType, true) + + return modCodec, err +} + +// NewIDLCodec is for Anchor custom types +func NewIDLCodec(idl IDL) (encodings.CodecFromTypeCodec, error) { + accounts := make(map[string]encodings.TypeCodec) + + refs := &codecRefs{ + builder: binary.LittleEndian(), + codecs: make(map[string]encodings.TypeCodec), + typeDefs: idl.Types, + } + + for _, account := range idl.Accounts { + var ( + name string + accCodec encodings.TypeCodec + err error + ) + + name, accCodec, err = createNamedCodec(account, refs) + if err != nil { + return nil, err + } + + accounts[name] = accCodec + } + + return encodings.CodecFromTypeCodec(accounts), nil +} + +type codecRefs struct { + builder encodings.Builder + codecs map[string]encodings.TypeCodec + typeDefs IdlTypeDefSlice +} + +func createNamedCodec( + def IdlTypeDef, + refs *codecRefs, +) (string, encodings.TypeCodec, error) { + caser := cases.Title(language.English) + name := def.Name + + switch def.Type.Kind { + case IdlTypeDefTyKindStruct: + return asStruct(def, refs, name, caser) + case IdlTypeDefTyKindEnum: + variants := def.Type.Variants + if variants.IsAllUint8() { + // TODO: should map to map[uint8]struct{} + // reflect.MapOf(reflect.TypeOf(uint8(0)), reflect.StructOf(nil)) + } + + // TODO: not yet sure how to handle enums + // maybe as type map[string]interface{}?? + // enums in Rust can have properties or variants + fallthrough + default: + return name, nil, types.ErrInvalidEncoding + } +} + +func asStruct( + def IdlTypeDef, + refs *codecRefs, + name string, + caser cases.Caser, +) (string, encodings.TypeCodec, error) { + if def.Type.Fields == nil { + return name, nil, types.ErrInvalidEncoding + } + + named := make([]encodings.NamedTypeCodec, len(*def.Type.Fields)) + + for idx, field := range *def.Type.Fields { + fieldName := field.Name + + typedCodec, err := processFieldType(field.Type, refs) + if err != nil { + return name, nil, err + } + + named[idx] = encodings.NamedTypeCodec{Name: caser.String(fieldName), Codec: typedCodec} + } + + structCodec, err := encodings.NewStructCodec(named) + if err != nil { + return name, nil, err + } + + return name, structCodec, nil +} + +func processFieldType(idlType IdlType, refs *codecRefs) (encodings.TypeCodec, error) { + switch true { + case idlType.IsString(): + return getCodecByStringType(idlType.GetString(), 0, refs.builder) + case idlType.IsIdlTypeOption(): + return asOption(idlType.GetIdlTypeOption(), refs) + case idlType.IsIdlTypeDefined(): + return asDefined(idlType.GetIdlTypeDefined(), refs) + case idlType.IsArray(): + return asArray(idlType.GetArray(), refs) + case idlType.IsIdlTypeVec(): + return asVec(idlType.GetIdlTypeVec(), refs) + default: + return nil, types.ErrInvalidEncoding + } +} + +func asOption(opt *IdlTypeOption, refs *codecRefs) (encodings.TypeCodec, error) { + // Go doesn't have an `Option` type; use pointer to type instead + // this should be automatic in the codec + codec, err := processFieldType(opt.Option, refs) + if err != nil { + return nil, err + } + + return codec, nil +} + +func asDefined(definedName *IdlTypeDefined, refs *codecRefs) (encodings.TypeCodec, error) { + if definedName == nil { + return nil, types.ErrInvalidEncoding + } + + // already exists as a type in the typed codecs + if savedCodec, ok := refs.codecs[definedName.Defined]; ok { + return savedCodec, nil + } + + // codec by defined type doesn't exist + // process it using the provided typeDefs + nextDef := refs.typeDefs.GetByName(definedName.Defined) + if nextDef == nil { + return nil, types.ErrInvalidEncoding + } + + newTypeName, newTypeCodec, err := createNamedCodec(*nextDef, refs) + if err != nil { + return nil, err + } + + // we know that recursive found codecs are types so add them to the type lookup + refs.codecs[newTypeName] = newTypeCodec + + return newTypeCodec, nil +} + +func asArray(idlArray *IdlTypeArray, refs *codecRefs) (encodings.TypeCodec, error) { + codec, err := processFieldType(idlArray.Thing, refs) + if err != nil { + return nil, err + } + + return encodings.NewArray(idlArray.Num, codec) +} + +func asVec(idlVec *IdlTypeVec, refs *codecRefs) (encodings.TypeCodec, error) { + codec, err := processFieldType(idlVec.Vec, refs) + if err != nil { + return nil, err + } + + return encodings.NewSlice(codec, refs.builder.Uint32()) +} + +func getCodecByStringType(curType IdlTypeAsString, len int, builder encodings.Builder) (encodings.TypeCodec, error) { + switch curType { + case IdlTypeBool: + return builder.Bool(), nil + case IdlTypeString: + return builder.String(1000) // TODO: set max len from somewhere + case IdlTypeI8, IdlTypeI16, IdlTypeI32, IdlTypeI64, IdlTypeI128: + return getIntCodecByStringType(curType, builder) + case IdlTypeU8, IdlTypeU16, IdlTypeU32, IdlTypeU64, IdlTypeU128: + return getUIntCodecByStringType(curType, builder) + case IdlTypeUnixTimestamp, IdlTypeDuration: + return getTimeCodecByStringType(curType, builder) + case IdlTypeBytes, IdlTypePublicKey, IdlTypeHash: + return getByteCodecByStringType(curType, len, builder) + default: + return nil, types.ErrInvalidEncoding + } +} + +func getIntCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { + switch curType { + case IdlTypeI8: + return builder.Int8(), nil + case IdlTypeI16: + return builder.Int16(), nil + case IdlTypeI32: + return builder.Int32(), nil + case IdlTypeI64: + return builder.Int64(), nil + case IdlTypeI128: + return builder.BigInt(16, true) + default: + return nil, types.ErrInvalidEncoding + } +} + +func getUIntCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { + switch curType { + case IdlTypeU8: + return builder.Uint8(), nil + case IdlTypeU16: + return builder.Uint16(), nil + case IdlTypeU32: + return builder.Uint32(), nil + case IdlTypeU64: + return builder.Uint64(), nil + case IdlTypeU128: + return builder.BigInt(16, true) + default: + return nil, types.ErrInvalidEncoding + } +} + +func getTimeCodecByStringType(curType IdlTypeAsString, builder encodings.Builder) (encodings.TypeCodec, error) { + switch curType { + case IdlTypeUnixTimestamp: + return NewUnixTimestamp(builder), nil + case IdlTypeDuration: + return NewDuration(builder), nil + default: + return nil, types.ErrInvalidEncoding + } +} + +func getByteCodecByStringType(curType IdlTypeAsString, len int, builder encodings.Builder) (encodings.TypeCodec, error) { + switch curType { + case IdlTypeBytes: + return encodings.NewArray(len, builder.Uint8()) + case IdlTypePublicKey, IdlTypeHash: + return encodings.NewArray(DefaultHashByteLength, builder.Uint8()) + default: + return nil, types.ErrInvalidEncoding + } +} diff --git a/pkg/solana/codec/solana_test.go b/pkg/solana/codec/solana_test.go new file mode 100644 index 000000000..a0c454d2f --- /dev/null +++ b/pkg/solana/codec/solana_test.go @@ -0,0 +1,95 @@ +package codec_test + +import ( + "testing" + "time" + + "github.com/test-go/testify/require" + + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" +) + +func TestNewIDLCodec(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + _, _, entry := codec.NewTestIDLAndCodec(t) + + expected := codec.DefaultTestStruct + bts, err := entry.Encode(ctx, expected, codec.TestStructWithNestedStruct) + + require.NoError(t, err) + + var decoded codec.StructWithNestedStruct + + require.NoError(t, entry.Decode(ctx, bts, &decoded, codec.TestStructWithNestedStruct)) + require.Equal(t, expected, decoded) +} + +func TestNewIDLCodec_WithModifiers(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + _, _, idlCodec := codec.NewTestIDLAndCodec(t) + modConfig := commoncodec.ModifiersConfig{ + &commoncodec.RenameModifierConfig{Fields: map[string]string{"Value": "V"}}, + } + + renameMod, err := modConfig.ToModifier(codec.DecoderHooks...) + require.NoError(t, err) + + idlCodecWithMods, err := codec.NewNamedModifierCodec(idlCodec, codec.TestStructWithNestedStruct, renameMod) + require.NoError(t, err) + + type modifiedTestStruct struct { + V uint8 + InnerStruct codec.ObjectRef1 + BasicNestedArray [][]uint32 + Option *string + DefinedArray []codec.ObjectRef2 + TimeVal time.Time + DurationVal time.Duration + } + + expected := modifiedTestStruct{ + V: codec.DefaultTestStruct.Value, + InnerStruct: codec.DefaultTestStruct.InnerStruct, + BasicNestedArray: codec.DefaultTestStruct.BasicNestedArray, + Option: codec.DefaultTestStruct.Option, + DefinedArray: codec.DefaultTestStruct.DefinedArray, + TimeVal: codec.DefaultTestStruct.TimeVal, + DurationVal: codec.DefaultTestStruct.DurationVal, + } + + withModsBts, err := idlCodecWithMods.Encode(ctx, expected, codec.TestStructWithNestedStruct) + require.NoError(t, err) + + noModsBts, err := idlCodec.Encode(ctx, codec.DefaultTestStruct, codec.TestStructWithNestedStruct) + + // the codec without modifiers should encode an unmodified struct to the same bytes + // as the codec with modifiers encodes a modified struct + require.NoError(t, err) + require.Equal(t, withModsBts, noModsBts) + + var decoded modifiedTestStruct + + // the codec with modifiers should decode from unmodified bytes into a modified struct + require.NoError(t, idlCodecWithMods.Decode(ctx, noModsBts, &decoded, codec.TestStructWithNestedStruct)) + + t.Log(expected.TimeVal.Format(time.RFC3339)) + t.Log(decoded.TimeVal.Format(time.RFC3339)) + + // require.Equal(t, expected, decoded) + + var unmodifiedDecoded codec.StructWithNestedStruct + + // the codec without modifiers should decode from unmodified bytes to the same values as + // modified struct + require.NoError(t, idlCodec.Decode(ctx, noModsBts, &unmodifiedDecoded, codec.TestStructWithNestedStruct)) + require.Equal(t, expected.V, unmodifiedDecoded.Value) + // require.Equal(t, expected.TimeVal.Unix(), unmodifiedDecoded.TimeVal.Unix()) + require.Equal(t, expected.DurationVal, unmodifiedDecoded.DurationVal) +} diff --git a/pkg/solana/codec/testutils.go b/pkg/solana/codec/testutils.go new file mode 100644 index 000000000..475fe3754 --- /dev/null +++ b/pkg/solana/codec/testutils.go @@ -0,0 +1,184 @@ +package codec + +import ( + "encoding/json" + "math/big" + "testing" + "time" + + "github.com/test-go/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" +) + +var ( + TestStructWithNestedStruct = "StructWithNestedStruct" + DefaultStringRef = "test string" + DefaultTestStruct = StructWithNestedStruct{ + Value: 80, + InnerStruct: ObjectRef1{ + Prop1: 10, + Prop2: "some_val", + Prop3: new(big.Int).SetUint64(42), + }, + BasicNestedArray: [][]uint32{{5, 6, 7}, {0, 0, 0}, {0, 0, 0}}, + Option: &DefaultStringRef, + DefinedArray: []ObjectRef2{ + { + Prop1: 42, + Prop2: new(big.Int).SetInt64(42), + }, + { + Prop1: 43, + Prop2: new(big.Int).SetInt64(43), + }, + }, + TimeVal: time.Unix(683_100_000, 0), + DurationVal: 42 * time.Second, + } +) + +// NewTestIDLAndCodec creates a complete IDL that covers all types and is exported here to allow parent packages to +// use for testing. +func NewTestIDLAndCodec(t *testing.T) (string, IDL, encodings.CodecFromTypeCodec) { + t.Helper() + + var idl IDL + if err := json.Unmarshal([]byte(jsonIDLWithAllTypes), &idl); err != nil { + t.Logf("failed to unmarshal test IDL: %s", err.Error()) + t.FailNow() + } + + entry, err := NewIDLCodec(idl) + if err != nil { + t.Logf("failed to create new codec from test IDL: %s", err.Error()) + t.FailNow() + } + + require.NotNil(t, entry) + + return jsonIDLWithAllTypes, idl, entry +} + +type StructWithNestedStruct struct { + Value uint8 + InnerStruct ObjectRef1 + BasicNestedArray [][]uint32 + Option *string + DefinedArray []ObjectRef2 + TimeVal time.Time + DurationVal time.Duration +} + +type ObjectRef1 struct { + Prop1 int8 + Prop2 string + Prop3 *big.Int +} + +type ObjectRef2 struct { + Prop1 uint32 + Prop2 *big.Int +} + +const jsonIDLWithAllTypes = `{ + "version": "0.1.0", + "name": "some_test_idl", + "accounts": [ + { + "name": "StructWithNestedStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "value", + "type": "u8" + }, + { + "name": "innerStruct", + "type": { + "defined": "ObjectRef1" + } + }, + { + "name": "basicNestedArray", + "type": { + "array": [ + { + "array": [ + "u32", + 3 + ] + }, + 3 + ] + } + }, + { + "name": "option", + "type": { + "option": "string" + } + }, + { + "name": "definedArray", + "type": { + "array": [ + { + "defined": "ObjectRef2" + }, + 2 + ] + } + }, + { + "name": "timeVal", + "type": "unixTimestamp" + }, + { + "name": "durationVal", + "type": "duration" + } + ] + } + } + ], + "types": [ + { + "name": "ObjectRef1", + "type": { + "kind": "struct", + "fields": [ + { + "name": "prop1", + "type": "i8" + }, + { + "name": "prop2", + "type": "string" + }, + { + "name": "prop3", + "type": "u128" + } + ] + } + }, + { + "name": "ObjectRef2", + "type": { + "kind": "struct", + "fields": [ + { + "name": "prop1", + "type": "u32" + }, + { + "name": "prop2", + "type": "i128" + } + ] + } + } + ] + }` diff --git a/pkg/solana/codec/time.go b/pkg/solana/codec/time.go new file mode 100644 index 000000000..16630f905 --- /dev/null +++ b/pkg/solana/codec/time.go @@ -0,0 +1,103 @@ +package codec + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings" + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +func NewUnixTimestamp(builder encodings.Builder) encodings.TypeCodec { + return ×tamp{ + intEncoder: builder.Int64(), + } +} + +type timestamp struct { + intEncoder encodings.TypeCodec +} + +var _ encodings.TypeCodec = ×tamp{} + +func (t *timestamp) Encode(value any, into []byte) ([]byte, error) { + bi, ok := value.(time.Time) + if !ok { + return nil, fmt.Errorf("%w: expected time.Time, got %T", types.ErrInvalidType, value) + } + + log.Printf("time encoder called --> value: %d; formatted: %s", bi.Unix(), bi.Format(time.RFC3339)) + return t.intEncoder.Encode(bi.Unix(), into) +} + +func (t *timestamp) Decode(encoded []byte) (any, []byte, error) { + value, bytes, err := t.intEncoder.Decode(encoded) + + bi, ok := value.(int64) + if !ok { + return value, bytes, err + } + + val := time.Unix(bi, 0) + + log.Printf("time decoder called: %d %s", bi, val.Format(time.RFC3339)) + return time.Unix(bi, 0), bytes, nil +} + +func (t *timestamp) GetType() reflect.Type { + return reflect.TypeOf(time.Time{}) +} + +func (t *timestamp) Size(val int) (int, error) { + return t.intEncoder.Size(val) +} + +func (t *timestamp) FixedSize() (int, error) { + return t.intEncoder.FixedSize() +} + +func NewDuration(builder encodings.Builder) encodings.TypeCodec { + return &duration{ + intEncoder: builder.Int64(), + } +} + +type duration struct { + intEncoder encodings.TypeCodec +} + +var _ encodings.TypeCodec = ×tamp{} + +func (d *duration) Encode(value any, into []byte) ([]byte, error) { + bi, ok := value.(time.Duration) + if !ok { + return nil, fmt.Errorf("%w: expected time.Duration, got %T", types.ErrInvalidType, value) + } + + return d.intEncoder.Encode(int64(bi), into) +} + +func (d *duration) Decode(encoded []byte) (any, []byte, error) { + value, bytes, err := d.intEncoder.Decode(encoded) + + bi, ok := value.(int64) + if !ok { + return value, bytes, err + } + + return time.Duration(bi), bytes, nil +} + +func (d *duration) GetType() reflect.Type { + return reflect.TypeOf(time.Duration(0)) +} + +func (d *duration) Size(val int) (int, error) { + return d.intEncoder.Size(val) +} + +func (d *duration) FixedSize() (int, error) { + return d.intEncoder.FixedSize() +} diff --git a/pkg/solana/config/chain_reader.go b/pkg/solana/config/chain_reader.go new file mode 100644 index 000000000..855f8cfbc --- /dev/null +++ b/pkg/solana/config/chain_reader.go @@ -0,0 +1,37 @@ +package config + +import "github.com/smartcontractkit/chainlink-common/pkg/codec" + +type ChainReader struct { + Namespaces map[string]ChainReaderMethods `json:"namespaces" toml:"namespaces"` +} + +type ChainReaderMethods struct { + Methods map[string]ChainDataReader `json:"methods" toml:"methods"` +} + +type ChainDataReader struct { + AnchorIDL string `json:"anchorIDL" toml:"anchorIDL"` + Procedures []ChainReaderProcedure `json:"procedures" toml:"procedures"` +} + +type ProcedureType int + +const ( + ProcedureTypeInternal ProcedureType = iota + ProcedureTypeAnchor +) + +type ChainReaderProcedure chainDataProcedureFields + +type chainDataProcedureFields struct { + // IDLAccount refers to the account defined in the IDL. + IDLAccount string `json:"idlAccount"` + // Type describes the procedure type to use such as internal for static values, + // anchor-read for using an anchor generated IDL to read values from an account, + // or custom structure for reading from a native account. + Type ProcedureType `json:"type"` + // OutputModifications provides modifiers to convert chain data format to custom + // output formats. + OutputModifications codec.ModifiersConfig `json:"outputModifications,omitempty"` +}