diff --git a/go.mod b/go.mod index b9edfe5..f064a96 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( ) require ( + github.com/DataDog/zstd v1.5.2 // indirect github.com/KyberNetwork/blockchain-toolkit v0.8.2-0.20241123202223-0b77d465adc4 // indirect github.com/KyberNetwork/ethrpc v0.7.4 // indirect github.com/KyberNetwork/int256 v0.1.4 // indirect @@ -41,18 +42,27 @@ require ( github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/VictoriaMetrics/metrics v1.35.4 // indirect github.com/aws/smithy-go v1.15.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v1.1.5 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/gnark-crypto v0.19.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/daoleno/uniswap-sdk-core v0.1.7 // indirect github.com/daoleno/uniswapv3-sdk v0.4.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dchest/siphash v1.2.3 // indirect github.com/deckarep/golang-set/v2 v2.8.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -68,24 +78,34 @@ require ( github.com/go-resty/resty/v2 v2.17.2 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gofrs/flock v0.12.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/machinebox/graphql v0.2.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/metachris/flashbotsrpc v0.7.1 // indirect github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect @@ -98,9 +118,16 @@ require ( github.com/pion/transport/v3 v3.0.7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.10.1 // indirect github.com/redis/go-redis/v9 v9.12.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rs/cors v1.7.0 // indirect github.com/rs/zerolog v1.34.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/samber/lo v1.49.1 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -109,9 +136,11 @@ require ( github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect github.com/valyala/fastrand v1.1.0 // indirect github.com/valyala/histogram v1.2.0 // indirect github.com/wlynxg/anet v0.0.5 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/ybbus/jsonrpc/v3 v3.1.6 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/go.sum b/go.sum index a853813..054c84f 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= @@ -100,7 +102,6 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= @@ -109,6 +110,7 @@ github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/daoleno/uniswap-sdk-core v0.1.7 h1:PdZypLSzM5Mu2rFBjXK9XrHDppSt62GkxXjWLpuMAN4= github.com/daoleno/uniswap-sdk-core v0.1.7/go.mod h1:DPzL8zNicstPzvX74ZeeHsiIUquZRpwviceDHQ8+UQ4= github.com/daoleno/uniswapv3-sdk v0.4.0 h1:NiJndjRydAojYgi9Wd/291fB4Nwo4IQ24mLAldGuaZs= @@ -219,6 +221,7 @@ github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4 github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -313,6 +316,7 @@ github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4 github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -372,6 +376,7 @@ github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= @@ -442,6 +447,7 @@ github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -465,6 +471,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= @@ -472,7 +479,6 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -538,7 +544,6 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= @@ -570,6 +575,7 @@ github.com/ybbus/jsonrpc/v3 v3.1.6/go.mod h1:U1QbyNfL5Pvi2roT0OpRbJeyvGxfWYSgKJH github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -625,6 +631,7 @@ golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 h1:3yiSh9fhy5/RhCSntf4Sy0Tnx golang.org/x/exp v0.0.0-20250811191247-51f88131bc50/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -636,6 +643,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -651,7 +659,9 @@ golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -720,7 +730,9 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/dex-router-wrapper/DexRouterWrapper.abi.json b/pkg/dex-router-wrapper/DexRouterWrapper.abi.json new file mode 100644 index 0000000..7f1e75f --- /dev/null +++ b/pkg/dex-router-wrapper/DexRouterWrapper.abi.json @@ -0,0 +1,45 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "address", + "name": "outputToken", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "wrap", + "outputs": [ + { + "internalType": "uint256", + "name": "returnAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasUsed", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/pkg/dex-router-wrapper/DexRouterWrapper.bin b/pkg/dex-router-wrapper/DexRouterWrapper.bin new file mode 100644 index 0000000..b2025ea --- /dev/null +++ b/pkg/dex-router-wrapper/DexRouterWrapper.bin @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b506104778061001c5f395ff3fe608060405260043610610020575f3560e01c8063e581697d1461002b575f5ffd5b3661002757005b5f5ffd5b61003e610039366004610347565b610057565b6040805192835260208301919091520160405180910390f35b5f5f5f5a90505f6100688686610176565b90505f5f8a6001600160a01b0316348b8b6040516100879291906103e6565b5f6040518083038185875af1925050503d805f81146100c1576040519150601f19603f3d011682016040523d82523d5f602084013e6100c6565b606091505b5091509150816100d9576100d9816102c6565b5f6100e48989610176565b90508381101561014e5760405162461bcd60e51b815260206004820152602a60248201527f446578526f75746572577261707065723a206f75747075742062616c616e636560448201526908191958dc99585cd95960b21b60648201526084015b60405180910390fd5b61015884826103f5565b96505a61016590866103f5565b955050505050509550959350505050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b038416016101ad57506001600160a01b038116316102c0565b6040516001600160a01b0383811660248301525f91829186169060440160408051601f198184030181529181526020820180516001600160e01b03166370a0823160e01b179052516101ff9190610414565b5f60405180830381855afa9150503d805f8114610237576040519150601f19603f3d011682016040523d82523d5f602084013e61023c565b606091505b509150915081801561025057506020815110155b6102a75760405162461bcd60e51b815260206004820152602260248201527f446578526f75746572577261707065723a2062616c616e63654f66206661696c604482015261195960f21b6064820152608401610145565b808060200190518101906102bb919061042a565b925050505b92915050565b8051156102d557805160208201fd5b60405162461bcd60e51b815260206004820152602660248201527f446578526f75746572577261707065723a207461726765742063616c6c2072656044820152651d995c9d195960d21b6064820152608401610145565b80356001600160a01b0381168114610342575f5ffd5b919050565b5f5f5f5f5f6080868803121561035b575f5ffd5b6103648661032c565b9450602086013567ffffffffffffffff81111561037f575f5ffd5b8601601f8101881361038f575f5ffd5b803567ffffffffffffffff8111156103a5575f5ffd5b8860208284010111156103b6575f5ffd5b602091909101945092506103cc6040870161032c565b91506103da6060870161032c565b90509295509295909350565b818382375f9101908152919050565b818103818111156102c057634e487b7160e01b5f52601160045260245ffd5b5f82518060208501845e5f920191825250919050565b5f6020828403121561043a575f5ffd5b505191905056fea264697066735822122066da7c4b0f6a1d3678afaabf39d5edccf267b78e882b262bbca903e0c1c3b84064736f6c63430008250033 diff --git a/pkg/dex-router-wrapper/DexRouterWrapper.bin-runtime b/pkg/dex-router-wrapper/DexRouterWrapper.bin-runtime new file mode 100644 index 0000000..56f5021 --- /dev/null +++ b/pkg/dex-router-wrapper/DexRouterWrapper.bin-runtime @@ -0,0 +1 @@ +608060405260043610610020575f3560e01c8063e581697d1461002b575f5ffd5b3661002757005b5f5ffd5b61003e610039366004610347565b610057565b6040805192835260208301919091520160405180910390f35b5f5f5f5a90505f6100688686610176565b90505f5f8a6001600160a01b0316348b8b6040516100879291906103e6565b5f6040518083038185875af1925050503d805f81146100c1576040519150601f19603f3d011682016040523d82523d5f602084013e6100c6565b606091505b5091509150816100d9576100d9816102c6565b5f6100e48989610176565b90508381101561014e5760405162461bcd60e51b815260206004820152602a60248201527f446578526f75746572577261707065723a206f75747075742062616c616e636560448201526908191958dc99585cd95960b21b60648201526084015b60405180910390fd5b61015884826103f5565b96505a61016590866103f5565b955050505050509550959350505050565b5f73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b038416016101ad57506001600160a01b038116316102c0565b6040516001600160a01b0383811660248301525f91829186169060440160408051601f198184030181529181526020820180516001600160e01b03166370a0823160e01b179052516101ff9190610414565b5f60405180830381855afa9150503d805f8114610237576040519150601f19603f3d011682016040523d82523d5f602084013e61023c565b606091505b509150915081801561025057506020815110155b6102a75760405162461bcd60e51b815260206004820152602260248201527f446578526f75746572577261707065723a2062616c616e63654f66206661696c604482015261195960f21b6064820152608401610145565b808060200190518101906102bb919061042a565b925050505b92915050565b8051156102d557805160208201fd5b60405162461bcd60e51b815260206004820152602660248201527f446578526f75746572577261707065723a207461726765742063616c6c2072656044820152651d995c9d195960d21b6064820152608401610145565b80356001600160a01b0381168114610342575f5ffd5b919050565b5f5f5f5f5f6080868803121561035b575f5ffd5b6103648661032c565b9450602086013567ffffffffffffffff81111561037f575f5ffd5b8601601f8101881361038f575f5ffd5b803567ffffffffffffffff8111156103a5575f5ffd5b8860208284010111156103b6575f5ffd5b602091909101945092506103cc6040870161032c565b91506103da6060870161032c565b90509295509295909350565b818382375f9101908152919050565b818103818111156102c057634e487b7160e01b5f52601160045260245ffd5b5f82518060208501845e5f920191825250919050565b5f6020828403121561043a575f5ffd5b505191905056fea264697066735822122066da7c4b0f6a1d3678afaabf39d5edccf267b78e882b262bbca903e0c1c3b84064736f6c63430008250033 diff --git a/pkg/dex-router-wrapper/DexRouterWrapper.sol b/pkg/dex-router-wrapper/DexRouterWrapper.sol new file mode 100644 index 0000000..6c88753 --- /dev/null +++ b/pkg/dex-router-wrapper/DexRouterWrapper.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +// DexRouterWrapper forwards an arbitrary call to a DEX router and reports the +// resulting output-token amount and gas used. It is meant to be run via +// eth_call/eth_estimateGas with a state override (see the Go package's +// DeployedBytecode/StateOverride helpers) rather than deployed on-chain, so a +// single caller can quote any router's swap without holding an allowance or +// funding the wrapper itself. +// +// To regenerate the compiled artifacts checked in alongside this file: +// npx solc --optimize --abi --bin --bin-runtime DexRouterWrapper.sol -o build/ +// then copy build/DexRouterWrapper.abi to DexRouterWrapper.abi.json, +// build/DexRouterWrapper.bin to DexRouterWrapper.bin, and +// build/DexRouterWrapper.bin-runtime to DexRouterWrapper.bin-runtime. +contract DexRouterWrapper { + // NATIVE is the sentinel address used to mean "the native chain asset" + // wherever an ERC20 token address is otherwise expected, matching the + // convention used by 1inch, Paraswap, and most other DEX aggregators. + address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + // wrap calls target with data, forwarding any msg.value (needed when the + // router call itself takes native ETH as input), and returns the change + // in outputToken balance of recipient plus the gas the call consumed. + // + // recipient is taken explicitly rather than assumed to be address(this) + // or msg.sender: the router calldata built off-chain may already encode + // an arbitrary recipient independent of who calls target. + function wrap( + address target, + bytes calldata data, + address outputToken, + address recipient + ) external payable returns (uint256 returnAmount, uint256 gasUsed) { + uint256 gasStart = gasleft(); + uint256 balanceBefore = _balanceOf(outputToken, recipient); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = target.call{value: msg.value}(data); + if (!success) { + _bubbleRevert(result); + } + + uint256 balanceAfter = _balanceOf(outputToken, recipient); + // Solidity 0.8's checked arithmetic would otherwise turn this into an + // opaque Panic(0x11) if the call left recipient with less outputToken + // than it started with; a named revert reason is more useful to a + // caller trying to interpret a failed quote. + require(balanceAfter >= balanceBefore, "DexRouterWrapper: output balance decreased"); + returnAmount = balanceAfter - balanceBefore; + gasUsed = gasStart - gasleft(); + } + + function _balanceOf(address token, address account) private view returns (uint256) { + if (token == NATIVE) { + return account.balance; + } + (bool success, bytes memory result) = token.staticcall( + abi.encodeWithSignature("balanceOf(address)", account) + ); + require(success && result.length >= 32, "DexRouterWrapper: balanceOf failed"); + return abi.decode(result, (uint256)); + } + + function _bubbleRevert(bytes memory result) private pure { + if (result.length > 0) { + // solhint-disable-next-line no-inline-assembly + assembly { + revert(add(result, 32), mload(result)) + } + } + revert("DexRouterWrapper: target call reverted"); + } + + // Some routers pay swap output to msg.sender rather than an explicit + // recipient argument; since this contract is msg.sender of the call to + // target, that output lands here. Without this, such a payout would + // revert the whole simulated call for having no way to accept it. + receive() external payable {} +} diff --git a/pkg/dex-router-wrapper/abi.go b/pkg/dex-router-wrapper/abi.go new file mode 100644 index 0000000..5669f83 --- /dev/null +++ b/pkg/dex-router-wrapper/abi.go @@ -0,0 +1,36 @@ +package dexrouterwrapper + +import ( + "bytes" + "encoding/hex" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +var ( + // WrapperABI is the parsed ABI of DexRouterWrapper.sol. + WrapperABI abi.ABI + + wrapperCreationBytecode []byte + wrapperDeployedBytecode []byte +) + +//nolint:gochecknoinits +func init() { + var err error + WrapperABI, err = abi.JSON(bytes.NewReader(wrapperABIJSON)) + if err != nil { + panic(err) + } + + wrapperCreationBytecode, err = hex.DecodeString(strings.TrimSpace(wrapperCreationBytecodeHex)) + if err != nil { + panic(err) + } + + wrapperDeployedBytecode, err = hex.DecodeString(strings.TrimSpace(wrapperDeployedBytecodeHex)) + if err != nil { + panic(err) + } +} diff --git a/pkg/dex-router-wrapper/embed.go b/pkg/dex-router-wrapper/embed.go new file mode 100644 index 0000000..e818e2e --- /dev/null +++ b/pkg/dex-router-wrapper/embed.go @@ -0,0 +1,12 @@ +package dexrouterwrapper + +import _ "embed" + +//go:embed DexRouterWrapper.abi.json +var wrapperABIJSON []byte + +//go:embed DexRouterWrapper.bin +var wrapperCreationBytecodeHex string + +//go:embed DexRouterWrapper.bin-runtime +var wrapperDeployedBytecodeHex string diff --git a/pkg/dex-router-wrapper/testdata/MockRouter.abi.json b/pkg/dex-router-wrapper/testdata/MockRouter.abi.json new file mode 100644 index 0000000..9044dab --- /dev/null +++ b/pkg/dex-router-wrapper/testdata/MockRouter.abi.json @@ -0,0 +1,60 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "MockRouterFail", + "type": "error" + }, + { + "inputs": [], + "name": "fail", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "payOut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/pkg/dex-router-wrapper/testdata/MockRouter.bin b/pkg/dex-router-wrapper/testdata/MockRouter.bin new file mode 100644 index 0000000..3378930 --- /dev/null +++ b/pkg/dex-router-wrapper/testdata/MockRouter.bin @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b506102cb8061001c5f395ff3fe608060405260043610610036575f3560e01c80630221038a14610041578063a9cc471814610062578063d004f0f714610076575f5ffd5b3661003d57005b5f5ffd5b34801561004c575f5ffd5b5061006061005b366004610260565b610089565b005b34801561006d575f5ffd5b50610060610132565b610060610084366004610260565b610171565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f81146100d2576040519150601f19603f3d011682016040523d82523d5f602084013e6100d7565b606091505b505090508061012d5760405162461bcd60e51b815260206004820152601960248201527f4d6f636b526f757465723a207061796f7574206661696c65640000000000000060448201526064015b60405180910390fd5b505050565b60405163a39b924b60e01b81526020600482015260136024820152726d6f636b20726f75746572206661696c75726560681b6044820152606401610124565b803410156101c15760405162461bcd60e51b815260206004820152601e60248201527f4d6f636b526f757465723a20696e73756666696369656e742076616c756500006044820152606401610124565b5f826001600160a01b0316826040515f6040518083038185875af1925050503d805f811461020a576040519150601f19603f3d011682016040523d82523d5f602084013e61020f565b606091505b505090508061012d5760405162461bcd60e51b815260206004820152601b60248201527f4d6f636b526f757465723a207472616e73666572206661696c656400000000006044820152606401610124565b5f5f60408385031215610271575f5ffd5b82356001600160a01b0381168114610287575f5ffd5b94602093909301359350505056fea2646970667358221220cd14979222e8b64129975541961cb11949ca01b8501ddab1d3929f625f8134e864736f6c63430008250033 diff --git a/pkg/dex-router-wrapper/testdata/MockRouter.sol b/pkg/dex-router-wrapper/testdata/MockRouter.sol new file mode 100644 index 0000000..e74e2eb --- /dev/null +++ b/pkg/dex-router-wrapper/testdata/MockRouter.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +// MockRouter is a test-only fixture standing in for a real DEX router. It is +// compiled and embedded solely by DexRouterWrapper's Go tests, to exercise +// wrap() end-to-end against a real EVM (go-ethereum's simulated backend) +// without depending on any live network. +// +// To regenerate the compiled artifacts checked in alongside this file: +// npx solc --optimize --abi --bin --bin-runtime MockRouter.sol -o build/ +// then copy build/MockRouter.bin to MockRouter.bin (only the creation +// bytecode is needed here, since the mock is always deployed for real by the +// test, never injected via a state override). +contract MockRouter { + error MockRouterFail(string reason); + + // swap sends amount of the received native ETH on to recipient, + // simulating a router that swaps into a native-ETH output. + function swap(address payable recipient, uint256 amount) external payable { + require(msg.value >= amount, "MockRouter: insufficient value"); + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = recipient.call{value: amount}(""); + require(success, "MockRouter: transfer failed"); + } + + // fail always reverts, so tests can assert DexRouterWrapper.wrap bubbles + // up the inner revert reason instead of swallowing it. + function fail() external pure { + revert MockRouterFail("mock router failure"); + } + + // payOut sends amount of this contract's own native ETH balance to an + // address other than the caller-supplied recipient, so tests can drive + // DexRouterWrapper.wrap's recipient (= address(this)) balance down + // across the call instead of up. + function payOut(address payable to, uint256 amount) external { + // solhint-disable-next-line avoid-low-level-calls + (bool success, ) = to.call{value: amount}(""); + require(success, "MockRouter: payout failed"); + } + + receive() external payable {} +} diff --git a/pkg/dex-router-wrapper/wrapper.go b/pkg/dex-router-wrapper/wrapper.go new file mode 100644 index 0000000..a5b7cb8 --- /dev/null +++ b/pkg/dex-router-wrapper/wrapper.go @@ -0,0 +1,73 @@ +// Package dexrouterwrapper encodes calls to, and provides the deployed +// bytecode of, DexRouterWrapper.sol — a contract that forwards an arbitrary +// call to a DEX router and reports the resulting output-token amount and gas +// used. It is designed to be run via an eth_call/eth_estimateGas state +// override rather than deployed on-chain: see StateOverride. +package dexrouterwrapper + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient/gethclient" +) + +const methodWrap = "wrap" + +// NativeTokenAddress is the sentinel address meaning "the native chain +// asset" wherever an ERC20 token address is otherwise expected, matching +// DexRouterWrapper.sol's NATIVE constant. +var NativeTokenAddress = common.HexToAddress("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") + +// EncodeWrapCalldata packs a call to DexRouterWrapper.wrap(target, data, +// outputToken, recipient). If target's call needs native ETH as input, +// set the Value field on the caller's ethereum.CallMsg — it is forwarded by +// the wrapper as msg.value, not encoded in this calldata. +func EncodeWrapCalldata(target common.Address, data []byte, outputToken, recipient common.Address) ([]byte, error) { + packed, err := WrapperABI.Pack(methodWrap, target, data, outputToken, recipient) + if err != nil { + return nil, fmt.Errorf("pack wrap calldata: %w", err) + } + + return packed, nil +} + +// DecodeWrapOutput unpacks the (returnAmount, gasUsed) return values of a +// DexRouterWrapper.wrap call. +func DecodeWrapOutput(data []byte) (*big.Int, uint64, error) { + var res [2]any + if err := WrapperABI.UnpackIntoInterface(&res, methodWrap, data); err != nil { + return nil, 0, fmt.Errorf("unpack wrap output: %w", err) + } + + returnAmount := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) //nolint:forcetypeassert + gasUsed := (*abi.ConvertType(res[1], new(*big.Int)).(**big.Int)).Uint64() //nolint:forcetypeassert + + return returnAmount, gasUsed, nil +} + +// DeployedBytecode returns DexRouterWrapper's runtime (deployed) bytecode — +// the code that runs when the contract is called, as opposed to the +// constructor code that produces it. This is what belongs in a state +// override's Code field; see StateOverride. +func DeployedBytecode() []byte { + return append([]byte(nil), wrapperDeployedBytecode...) +} + +// CreationBytecode returns DexRouterWrapper's constructor (creation) +// bytecode, for actually deploying the contract — e.g. in tests against an +// in-process EVM. +func CreationBytecode() []byte { + return append([]byte(nil), wrapperCreationBytecode...) +} + +// StateOverride builds the eth_call/eth_estimateGas state override that +// makes overrideAddr behave as DexRouterWrapper for the duration of one +// simulated call, without any real deployment. +func StateOverride(overrideAddr common.Address) map[common.Address]gethclient.OverrideAccount { + return map[common.Address]gethclient.OverrideAccount{ + overrideAddr: {Code: DeployedBytecode()}, + } +} diff --git a/pkg/dex-router-wrapper/wrapper_evm_test.go b/pkg/dex-router-wrapper/wrapper_evm_test.go new file mode 100644 index 0000000..edf5ba5 --- /dev/null +++ b/pkg/dex-router-wrapper/wrapper_evm_test.go @@ -0,0 +1,308 @@ +//nolint:testpackage +package dexrouterwrapper + +import ( + "bytes" + "context" + "crypto/ecdsa" + _ "embed" + "encoding/hex" + "fmt" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// This file exercises DexRouterWrapper against a real, in-process EVM (via +// go-ethereum's simulated backend), instead of only round-tripping Go-side +// ABI encoding. It deploys the wrapper both for real and via a state +// override (through eth_simulateV1, since the simulated backend +// deliberately hides the raw rpc.Client that gethclient's override-aware +// eth_call needs), so a bug that mixed up creation vs. deployed bytecode +// would fail here. + +//go:embed testdata/MockRouter.abi.json +var mockRouterABIJSON []byte + +//go:embed testdata/MockRouter.bin +var mockRouterCreationBytecodeHex string + +type evmTestEnv struct { + backend *simulated.Backend + client simulated.Client + key *ecdsa.PrivateKey + from common.Address + chainID *big.Int +} + +func newEVMTestEnv(t *testing.T) *evmTestEnv { + t.Helper() + + key, err := crypto.GenerateKey() + require.NoError(t, err) + from := crypto.PubkeyToAddress(key.PublicKey) + + balance := new(big.Int).Mul(big.NewInt(1000), big.NewInt(params.Ether)) + backend := simulated.NewBackend(types.GenesisAlloc{ + from: {Balance: balance}, + }) + t.Cleanup(func() { require.NoError(t, backend.Close()) }) + + client := backend.Client() + + chainID, err := client.ChainID(context.Background()) + require.NoError(t, err) + + return &evmTestEnv{ + backend: backend, + client: client, + key: key, + from: from, + chainID: chainID, + } +} + +// deploy sends creationBytecode as a contract-creation transaction and +// returns the resulting contract address. +func (env *evmTestEnv) deploy(ctx context.Context, t *testing.T, creationBytecode []byte) common.Address { + t.Helper() + + nonce, err := env.client.PendingNonceAt(ctx, env.from) + require.NoError(t, err) + + head, err := env.client.HeaderByNumber(ctx, nil) + require.NoError(t, err) + + gasFeeCap := new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei)) + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: env.chainID, + Nonce: nonce, + GasTipCap: big.NewInt(params.GWei), + GasFeeCap: gasFeeCap, + Gas: 3_000_000, + Data: creationBytecode, + }) + + signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(env.chainID), env.key) + require.NoError(t, err) + + require.NoError(t, env.client.SendTransaction(ctx, signedTx)) + env.backend.Commit() + + receipt, err := env.client.TransactionReceipt(ctx, signedTx.Hash()) + require.NoError(t, err) + require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "contract deployment must succeed") + + return receipt.ContractAddress +} + +// sendValue sends amount of native ETH from env.from to addr as a plain +// transaction, e.g. to pre-fund a contract before a test calls it. +func (env *evmTestEnv) sendValue(ctx context.Context, t *testing.T, addr common.Address, amount *big.Int) { + t.Helper() + + nonce, err := env.client.PendingNonceAt(ctx, env.from) + require.NoError(t, err) + + head, err := env.client.HeaderByNumber(ctx, nil) + require.NoError(t, err) + + gasFeeCap := new(big.Int).Add(head.BaseFee, big.NewInt(params.GWei)) + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: env.chainID, + Nonce: nonce, + GasTipCap: big.NewInt(params.GWei), + GasFeeCap: gasFeeCap, + Gas: 100_000, + To: &addr, + Value: amount, + }) + + signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(env.chainID), env.key) + require.NoError(t, err) + + require.NoError(t, env.client.SendTransaction(ctx, signedTx)) + env.backend.Commit() + + receipt, err := env.client.TransactionReceipt(ctx, signedTx.Hash()) + require.NoError(t, err) + require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "pre-funding transfer must succeed") +} + +// simulateV1Caller is the subset of *ethclient.Client's SimulateV1 method +// that simulated.Client's interface doesn't expose statically, even though +// the backend's concrete client implements it. +type simulateV1Caller interface { + SimulateV1( + ctx context.Context, opts ethclient.SimulateOptions, blockNrOrHash *rpc.BlockNumberOrHash, + ) ([]ethclient.SimulateBlockResult, error) +} + +// callWithOverride runs msg through eth_simulateV1 with the given state +// overrides applied and returns the call's raw return data. +func (env *evmTestEnv) callWithOverride( + ctx context.Context, t *testing.T, msg ethereum.CallMsg, overrides map[common.Address]ethereum.OverrideAccount, +) ([]byte, error) { + t.Helper() + + caller, ok := env.client.(simulateV1Caller) + require.True(t, ok, "simulated client must support eth_simulateV1") + + results, err := caller.SimulateV1(ctx, ethclient.SimulateOptions{ + BlockStateCalls: []ethclient.SimulateBlock{ + { + StateOverrides: overrides, + Calls: []ethereum.CallMsg{msg}, + }, + }, + }, nil) + require.NoError(t, err) + require.Len(t, results, 1) + require.Len(t, results[0].Calls, 1) + + call := results[0].Calls[0] + if call.Error != nil { + return nil, fmt.Errorf("simulated call reverted: %s", call.Error.Message) + } + + return call.ReturnValue, nil +} + +func mustParseABI(t *testing.T, data []byte) abi.ABI { + t.Helper() + parsed, err := abi.JSON(bytes.NewReader(data)) + require.NoError(t, err) + + return parsed +} + +func mustDecodeHexBytecode(t *testing.T, hexStr string) []byte { + t.Helper() + code, err := hex.DecodeString(strings.TrimSpace(hexStr)) + require.NoError(t, err) + + return code +} + +func TestWrapEVM(t *testing.T) { + env := newEVMTestEnv(t) + ctx := context.Background() + + mockRouterABI := mustParseABI(t, mockRouterABIJSON) + mockRouterCreation := mustDecodeHexBytecode(t, mockRouterCreationBytecodeHex) + + mockRouterAddr := env.deploy(ctx, t, mockRouterCreation) + wrapperAddr := env.deploy(ctx, t, CreationBytecode()) + + recipient := common.HexToAddress("0x00000000000000000000000000000000000c0de") + amount := big.NewInt(1_000_000_000_000_000) // 0.001 ETH + + swapData, err := mockRouterABI.Pack("swap", recipient, amount) + require.NoError(t, err) + + wrapCalldata, err := EncodeWrapCalldata(mockRouterAddr, swapData, NativeTokenAddress, recipient) + require.NoError(t, err) + + t.Run("deployed for real", func(t *testing.T) { + msg := ethereum.CallMsg{From: env.from, To: &wrapperAddr, Data: wrapCalldata, Value: amount} + + result, err := env.client.CallContract(ctx, msg, nil) + require.NoError(t, err) + + returnAmount, gasUsed, err := DecodeWrapOutput(result) + require.NoError(t, err) + assert.Equal(t, 0, amount.Cmp(returnAmount)) + assert.Positive(t, gasUsed) + }) + + t.Run("injected via state override", func(t *testing.T) { + // overrideAddr never receives a real deployment; only DeployedBytecode + // (via StateOverride) makes it behave like DexRouterWrapper. + overrideAddr := common.HexToAddress("0x00000000000000000000000000000000badc0d") + msg := ethereum.CallMsg{From: env.from, To: &overrideAddr, Data: wrapCalldata, Value: amount} + + result, err := env.callWithOverride(ctx, t, msg, StateOverride(overrideAddr)) + require.NoError(t, err) + + returnAmount, gasUsed, err := DecodeWrapOutput(result) + require.NoError(t, err) + assert.Equal(t, 0, amount.Cmp(returnAmount)) + assert.Positive(t, gasUsed) + }) + + t.Run("bubbles target revert", func(t *testing.T) { + failData, err := mockRouterABI.Pack("fail") + require.NoError(t, err) + + failCalldata, err := EncodeWrapCalldata(mockRouterAddr, failData, NativeTokenAddress, recipient) + require.NoError(t, err) + + msg := ethereum.CallMsg{From: env.from, To: &wrapperAddr, Data: failCalldata} + + _, err = env.client.CallContract(ctx, msg, nil) + assert.Error(t, err) + }) + + t.Run("reverts when output balance decreases", func(t *testing.T) { + // Pre-fund mockRouterAddr, then have it pay part of its own balance + // out to a third party. With recipient == mockRouterAddr, wrap's + // balance-diff goes negative, which must be a clean revert rather + // than an underflow panic. + env.sendValue(ctx, t, mockRouterAddr, big.NewInt(1_000_000_000_000_000)) + + bystander := common.HexToAddress("0x0000000000000000000000000000000000b7a5") + payOutData, err := mockRouterABI.Pack("payOut", bystander, big.NewInt(1)) + require.NoError(t, err) + + payOutCalldata, err := EncodeWrapCalldata(mockRouterAddr, payOutData, NativeTokenAddress, mockRouterAddr) + require.NoError(t, err) + + msg := ethereum.CallMsg{From: env.from, To: &wrapperAddr, Data: payOutCalldata} + + _, err = env.client.CallContract(ctx, msg, nil) + assert.Error(t, err) + }) + + t.Run("wrapper itself receives native swap output", func(t *testing.T) { + // Some routers pay swap output to msg.sender rather than an explicit + // recipient argument. Since the wrapper is msg.sender of the call to + // target, that means the payout lands on the wrapper contract + // itself; without a receive() function the plain ETH transfer would + // revert and the whole simulated call would fail. + // + // mockRouterAddr pays out of its own pre-funded balance (rather than + // forwarding wrap's msg.value) so that wrapperAddr's balanceBefore + // snapshot isn't itself inflated by the value of this very call. + env.sendValue(ctx, t, mockRouterAddr, amount) + + payOutToWrapperData, err := mockRouterABI.Pack("payOut", wrapperAddr, amount) + require.NoError(t, err) + + selfWrapCalldata, err := EncodeWrapCalldata( + mockRouterAddr, payOutToWrapperData, NativeTokenAddress, wrapperAddr, + ) + require.NoError(t, err) + + msg := ethereum.CallMsg{From: env.from, To: &wrapperAddr, Data: selfWrapCalldata} + + result, err := env.client.CallContract(ctx, msg, nil) + require.NoError(t, err) + + returnAmount, gasUsed, err := DecodeWrapOutput(result) + require.NoError(t, err) + assert.Equal(t, 0, amount.Cmp(returnAmount)) + assert.Positive(t, gasUsed) + }) +} diff --git a/pkg/dex-router-wrapper/wrapper_test.go b/pkg/dex-router-wrapper/wrapper_test.go new file mode 100644 index 0000000..36558ad --- /dev/null +++ b/pkg/dex-router-wrapper/wrapper_test.go @@ -0,0 +1,126 @@ +//nolint:testpackage +package dexrouterwrapper + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEncodeWrapCalldata(t *testing.T) { + testCases := []struct { + name string + target common.Address + data []byte + outputToken common.Address + recipient common.Address + }{ + { + name: "erc20 output", + target: common.HexToAddress("0x1111111111111111111111111111111111111111"), + data: []byte{0xde, 0xad, 0xbe, 0xef}, + outputToken: common.HexToAddress("0x2222222222222222222222222222222222222222"), + recipient: common.HexToAddress("0x3333333333333333333333333333333333333333"), + }, + { + name: "native output", + target: common.HexToAddress("0x4444444444444444444444444444444444444444"), + data: []byte{}, + outputToken: NativeTokenAddress, + recipient: common.HexToAddress("0x5555555555555555555555555555555555555555"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + packed, err := EncodeWrapCalldata(tc.target, tc.data, tc.outputToken, tc.recipient) + require.NoError(t, err) + + method := WrapperABI.Methods[methodWrap] + require.Equal(t, method.ID, packed[:4]) + + unpacked, err := method.Inputs.Unpack(packed[4:]) + require.NoError(t, err) + + var args struct { + Target common.Address + Data []byte + OutputToken common.Address + Recipient common.Address + } + require.NoError(t, method.Inputs.Copy(&args, unpacked)) + + assert.Equal(t, tc.target, args.Target) + assert.Equal(t, tc.data, args.Data) + assert.Equal(t, tc.outputToken, args.OutputToken) + assert.Equal(t, tc.recipient, args.Recipient) + }) + } +} + +func TestDecodeWrapOutput(t *testing.T) { + testCases := []struct { + name string + wantReturnAmount *big.Int + wantGasUsed uint64 + }{ + {name: "zero output", wantReturnAmount: big.NewInt(0), wantGasUsed: 0}, + {name: "typical output", wantReturnAmount: big.NewInt(123456789), wantGasUsed: 87654}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + packedOutput, err := WrapperABI.Methods[methodWrap].Outputs.Pack( + tc.wantReturnAmount, new(big.Int).SetUint64(tc.wantGasUsed), + ) + require.NoError(t, err) + + gotReturnAmount, gotGasUsed, err := DecodeWrapOutput(packedOutput) + require.NoError(t, err) + + assert.Equal(t, 0, tc.wantReturnAmount.Cmp(gotReturnAmount)) + assert.Equal(t, tc.wantGasUsed, gotGasUsed) + }) + } +} + +func TestDecodeWrapOutput_InvalidData(t *testing.T) { + _, _, err := DecodeWrapOutput([]byte{0x01, 0x02}) + assert.Error(t, err) +} + +func TestNativeTokenAddress(t *testing.T) { + // NativeTokenAddress must be the EIP-55 checksum of the all-0xEE + // 20-byte address, not an arbitrary hardcoded string. + want := common.HexToAddress("0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") + assert.Equal(t, want, NativeTokenAddress) +} + +func TestDeployedBytecode(t *testing.T) { + code := DeployedBytecode() + require.NotEmpty(t, code) + + // The returned slice must be a copy: mutating it must not corrupt the + // package's cached bytecode used by subsequent calls. + code[0] ^= 0xFF + again := DeployedBytecode() + assert.NotEqual(t, code, again) +} + +func TestCreationBytecode(t *testing.T) { + code := CreationBytecode() + require.NotEmpty(t, code) + assert.NotEqual(t, code, DeployedBytecode()) +} + +func TestStateOverride(t *testing.T) { + overrideAddr := common.HexToAddress("0x9999999999999999999999999999999999999999") + + overrides := StateOverride(overrideAddr) + + require.Contains(t, overrides, overrideAddr) + assert.Equal(t, DeployedBytecode(), overrides[overrideAddr].Code) +}