diff --git a/contribs/gnobro/go.mod b/contribs/gnobro/go.mod index 436e7968553..36f19a0e919 100644 --- a/contribs/gnobro/go.mod +++ b/contribs/gnobro/go.mod @@ -98,10 +98,12 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/ryanuber/columnize v2.1.2+incompatible // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect diff --git a/contribs/gnobro/go.sum b/contribs/gnobro/go.sum index f1a2876ae25..fd5e9ee77dd 100644 --- a/contribs/gnobro/go.sum +++ b/contribs/gnobro/go.sum @@ -287,6 +287,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= @@ -307,6 +309,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index c1850d1ff7b..139c1e8f33d 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -76,9 +76,11 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/ryanuber/columnize v2.1.2+incompatible // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index 7a983abcfab..d3cf72fdba5 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -236,6 +236,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -254,6 +256,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/contribs/gnogenesis/go.mod b/contribs/gnogenesis/go.mod index 35560ca4ac9..edd18415b15 100644 --- a/contribs/gnogenesis/go.mod +++ b/contribs/gnogenesis/go.mod @@ -56,9 +56,11 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rs/cors v1.11.1 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/ryanuber/columnize v2.1.2+incompatible // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.11 // indirect diff --git a/contribs/gnogenesis/go.sum b/contribs/gnogenesis/go.sum index 4a62c7e0277..94428a99a62 100644 --- a/contribs/gnogenesis/go.sum +++ b/contribs/gnogenesis/go.sum @@ -179,6 +179,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -194,6 +196,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 0d35985058a..63b90d086c6 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -45,10 +45,12 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.23.0 // indirect github.com/rs/xid v1.6.0 // indirect + github.com/ryanuber/columnize v2.1.2+incompatible // indirect github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/xlab/treeprint v1.2.0 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index cc01ad5cf47..c2b302a7c10 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -171,6 +171,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -188,6 +190,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= diff --git a/contribs/gnomigrate/go.sum b/contribs/gnomigrate/go.sum index ef729bdf524..171ebfc5197 100644 --- a/contribs/gnomigrate/go.sum +++ b/contribs/gnomigrate/go.sum @@ -181,6 +181,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -196,6 +198,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= diff --git a/contribs/tx-archive/go.sum b/contribs/tx-archive/go.sum index 5a19e44339e..889a2f8dcf2 100644 --- a/contribs/tx-archive/go.sum +++ b/contribs/tx-archive/go.sum @@ -182,6 +182,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -197,6 +199,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index aba308094e2..30d9c5457a4 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -7,11 +7,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" abciErrors "github.com/gnolang/gno/tm2/pkg/bft/abci/example/errors" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gnolang/gno/gno.land/pkg/gnoland/ugnot" - "github.com/gnolang/gno/gno.land/pkg/keyscli" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/stdlibs/chain" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -19,6 +19,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/keys" + "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" "github.com/gnolang/gno/tm2/pkg/sdk/bank" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -1529,7 +1530,7 @@ func TestClient_EstimateGas(t *testing.T) { t.Parallel() var ( - gasUsed = int64(100000) + gasUsed = gas.GasDetail{Total: gas.Detail{OperationCount: 1, GasConsumed: 100000}} deliverResp = &abci.ResponseDeliverTx{ GasUsed: gasUsed, } @@ -1565,14 +1566,14 @@ func TestClient_EstimateGas(t *testing.T) { estimate, err := c.EstimateGas(&std.Tx{}) require.NoError(t, err) - assert.Equal(t, gasUsed, estimate) + assert.Equal(t, gasUsed.Total.GasConsumed, estimate) }) t.Run("valid simulate", func(t *testing.T) { t.Parallel() var ( - gasUsed = int64(100000) + gasUsed = gas.GasDetail{Total: gas.Detail{OperationCount: 1, GasConsumed: 100000}} deliverResp = &abci.ResponseDeliverTx{ GasUsed: gasUsed, ResponseBase: abci.ResponseBase{ @@ -1618,7 +1619,7 @@ func TestClient_EstimateGas(t *testing.T) { require.NoError(t, err) assert.Equal(t, gasUsed, deliverTx.GasUsed) - bytesDelta, coinsDelta, hasStorageEvents := keyscli.GetStorageInfo(deliverTx.Events) + bytesDelta, coinsDelta, hasStorageEvents := client.GetStorageInfo(deliverTx.Events) assert.Equal(t, true, hasStorageEvents) assert.Equal(t, int64(10), bytesDelta) assert.Equal(t, "1000ugnot", coinsDelta.String()) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 1bceb9657fb..31c0010bce1 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -309,7 +309,7 @@ func (c *Client) EstimateGas(tx *std.Tx) (int64, error) { // Return the actual value returned by the node // for executing the transaction - return deliverTx.GasUsed, nil + return deliverTx.GasUsed.Total.GasConsumed, nil } // Simulate the transaction and return the ResponseDeliverTx. diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index e2c76a72e00..f81344dba20 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -23,6 +23,7 @@ import ( dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" @@ -1099,7 +1100,7 @@ func newGasPriceTestApp(t *testing.T) abci.Application { count := getTotalCount(tx) - newCtx.GasMeter().ConsumeGas(count, "counter-ante") + newCtx.GasMeter().ConsumeGas(gas.OpTesting, float64(count)) res = sdk.Result{ GasWanted: getTotalCount(tx), } diff --git a/gno.land/pkg/integration/testdata/gnokey_gasdetail.txtar b/gno.land/pkg/integration/testdata/gnokey_gasdetail.txtar new file mode 100644 index 00000000000..d78556f65aa --- /dev/null +++ b/gno.land/pkg/integration/testdata/gnokey_gasdetail.txtar @@ -0,0 +1,269 @@ +# Diplay detailed gas usage with different verbosity levels. +# Golden files generated using: +# UPDATE_SCRIPTS=true go test -run 'TestTestdata/gnokey_gasdetail$' + +# Start a new node +gnoland start + +# Add the hello package +gnokey maketx addpkg -pkgdir $WORK/hello -pkgpath gno.land/r/hello -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -v 0 test1 + +# Display gas details with verbosity level 0 (default) +gnokey maketx call -pkgpath gno.land/r/hello -func Hello -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -v 0 test1 +cmp stdout gnokey-detail-verbosity-0.stdout.golden +cmp stderr gnokey-detail-verbosity-0.stderr.golden + +# Display gas details with verbosity level 1 +gnokey maketx call -pkgpath gno.land/r/hello -func Hello -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -v 1 test1 +cmp stdout gnokey-detail-verbosity-1.stdout.golden +cmp stderr gnokey-detail-verbosity-1.stderr.golden + +# Display gas details with verbosity level 2 +gnokey maketx call -pkgpath gno.land/r/hello -func Hello -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -v 2 test1 +cmp stdout gnokey-detail-verbosity-2.stdout.golden +cmp stderr gnokey-detail-verbosity-2.stderr.golden + +# Display gas details with verbosity level 3 +gnokey maketx call -pkgpath gno.land/r/hello -func Hello -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -v 3 test1 +cmp stdout gnokey-detail-verbosity-3.stdout.golden +cmp stderr gnokey-detail-verbosity-3.stderr.golden + +-- hello/gnomod.toml -- +module = "gno.land/r/hello" +gno = "0.9" + +-- hello/hello.gno -- +package hello + +var s = "hello" + +func Hello(cur realm) string { + return s +} + +-- gnokey-detail-verbosity-0.stdout.golden -- +("hello" string) + + +OK! +GAS WANTED: 2000000 +GAS USED: 113989 +HEIGHT: 4 +EVENTS: [] +INFO: +TX HASH: cURlWQhzE9z3ICQg9vWuQt6eoViHcvGMTvGGSACkcm4= +-- gnokey-detail-verbosity-0.stderr.golden -- +Enter password. +-- gnokey-detail-verbosity-1.stdout.golden -- +("hello" string) + + +OK! +GAS WANTED: 2000000 +GAS USED: Operation: 69 Gas: 113989 +├── CPU: Operation: 12 Gas: 822 +├── KVStore: Operation: 6 Gas: 67788 +├── Memory: Operation: 5 Gas: 1192 +├── Parsing: Operation: 14 Gas: 23 +├── Store: Operation: 30 Gas: 40844 +└── Transaction: Operation: 2 Gas: 3320 + +HEIGHT: 5 +EVENTS: [] +INFO: +TX HASH: L+37Nj9UOQU9jXiGICBoVIRAtqLHYXnzAhqltYjhaig= +-- gnokey-detail-verbosity-1.stderr.golden -- +Enter password. +-- gnokey-detail-verbosity-2.stdout.golden -- +("hello" string) + + +OK! +GAS WANTED: 2000000 +GAS USED: Operation: 69 Gas: 113989 +├── CPU: Operation: 12 Gas: 822 +│ ├── CPUHalt: Operation: 1 Gas: 1 +│ ├── CPUPrecall: Operation: 1 Gas: 207 +│ ├── CPUEnterCrossing: Operation: 1 Gas: 100 +│ ├── CPUCall: Operation: 1 Gas: 256 +│ ├── CPUReturn: Operation: 1 Gas: 38 +│ ├── CPUEval: Operation: 5 Gas: 145 +│ ├── CPUSelector: Operation: 1 Gas: 32 +│ └── CPUBody: Operation: 1 Gas: 43 +├── KVStore: Operation: 6 Gas: 67788 +│ ├── KVStoreGetObjectPerByte: Operation: 5 Gas: 31632 +│ └── KVStoreGetPackageRealmPerByte: Operation: 1 Gas: 36156 +├── Memory: Operation: 5 Gas: 1192 +│ └── MemoryAllocPerByte: Operation: 5 Gas: 1192 +├── Parsing: Operation: 14 Gas: 23 +│ ├── ParsingToken: Operation: 8 Gas: 8 +│ └── ParsingNesting: Operation: 6 Gas: 15 +├── Store: Operation: 30 Gas: 40844 +│ ├── StoreReadFlat: Operation: 10 Gas: 10000 +│ ├── StoreReadPerByte: Operation: 10 Gas: 3474 +│ ├── StoreWriteFlat: Operation: 5 Gas: 10000 +│ └── StoreWritePerByte: Operation: 5 Gas: 17370 +└── Transaction: Operation: 2 Gas: 3320 + ├── TransactionPerByte: Operation: 1 Gas: 2320 + └── TxansactionSigVerifySecp256k1: Operation: 1 Gas: 1000 + +HEIGHT: 6 +EVENTS: [] +INFO: +TX HASH: bfE6GTX84Xx06LYzhOVW5ZgX8pKEPYol148KhmwQqHg= +-- gnokey-detail-verbosity-2.stderr.golden -- +Enter password. +-- gnokey-detail-verbosity-3.stdout.golden -- +("hello" string) + + +OK! +GAS WANTED: 2000000 +GAS USED: Operation: 69 Gas: 113989 +├── BlockGas: Operation: 0 Gas: 0 +│ └── BlockGasSum: Operation: 0 Gas: 0 +├── CPU: Operation: 12 Gas: 822 +│ ├── CPUInvalid: Operation: 0 Gas: 0 +│ ├── CPUHalt: Operation: 1 Gas: 1 +│ ├── CPUNoop: Operation: 0 Gas: 0 +│ ├── CPUExec: Operation: 0 Gas: 0 +│ ├── CPUPrecall: Operation: 1 Gas: 207 +│ ├── CPUEnterCrossing: Operation: 1 Gas: 100 +│ ├── CPUCall: Operation: 1 Gas: 256 +│ ├── CPUCallNativeBody: Operation: 0 Gas: 0 +│ ├── CPUDefer: Operation: 0 Gas: 0 +│ ├── CPUCallDeferNativeBody: Operation: 0 Gas: 0 +│ ├── CPUGo: Operation: 0 Gas: 0 +│ ├── CPUSelect: Operation: 0 Gas: 0 +│ ├── CPUSwitchClause: Operation: 0 Gas: 0 +│ ├── CPUSwitchClauseCase: Operation: 0 Gas: 0 +│ ├── CPUTypeSwitch: Operation: 0 Gas: 0 +│ ├── CPUIfCond: Operation: 0 Gas: 0 +│ ├── CPUPopValue: Operation: 0 Gas: 0 +│ ├── CPUPopResults: Operation: 0 Gas: 0 +│ ├── CPUPopBlock: Operation: 0 Gas: 0 +│ ├── CPUPopFrameAndReset: Operation: 0 Gas: 0 +│ ├── CPUPanic1: Operation: 0 Gas: 0 +│ ├── CPUPanic2: Operation: 0 Gas: 0 +│ ├── CPUReturn: Operation: 1 Gas: 38 +│ ├── CPUReturnAfterCopy: Operation: 0 Gas: 0 +│ ├── CPUReturnFromBlock: Operation: 0 Gas: 0 +│ ├── CPUReturnToBlock: Operation: 0 Gas: 0 +│ ├── CPUUpos: Operation: 0 Gas: 0 +│ ├── CPUUneg: Operation: 0 Gas: 0 +│ ├── CPUUnot: Operation: 0 Gas: 0 +│ ├── CPUUxor: Operation: 0 Gas: 0 +│ ├── CPUUrecv: Operation: 0 Gas: 0 +│ ├── CPULor: Operation: 0 Gas: 0 +│ ├── CPULand: Operation: 0 Gas: 0 +│ ├── CPUEql: Operation: 0 Gas: 0 +│ ├── CPUNeq: Operation: 0 Gas: 0 +│ ├── CPULss: Operation: 0 Gas: 0 +│ ├── CPULeq: Operation: 0 Gas: 0 +│ ├── CPUGtr: Operation: 0 Gas: 0 +│ ├── CPUGeq: Operation: 0 Gas: 0 +│ ├── CPUAdd: Operation: 0 Gas: 0 +│ ├── CPUSub: Operation: 0 Gas: 0 +│ ├── CPUBor: Operation: 0 Gas: 0 +│ ├── CPUXor: Operation: 0 Gas: 0 +│ ├── CPUMul: Operation: 0 Gas: 0 +│ ├── CPUQuo: Operation: 0 Gas: 0 +│ ├── CPURem: Operation: 0 Gas: 0 +│ ├── CPUShl: Operation: 0 Gas: 0 +│ ├── CPUShr: Operation: 0 Gas: 0 +│ ├── CPUBand: Operation: 0 Gas: 0 +│ ├── CPUBandn: Operation: 0 Gas: 0 +│ ├── CPUEval: Operation: 5 Gas: 145 +│ ├── CPUBinary1: Operation: 0 Gas: 0 +│ ├── CPUIndex1: Operation: 0 Gas: 0 +│ ├── CPUIndex2: Operation: 0 Gas: 0 +│ ├── CPUSelector: Operation: 1 Gas: 32 +│ ├── CPUSlice: Operation: 0 Gas: 0 +│ ├── CPUStar: Operation: 0 Gas: 0 +│ ├── CPURef: Operation: 0 Gas: 0 +│ ├── CPUTypeAssert1: Operation: 0 Gas: 0 +│ ├── CPUTypeAssert2: Operation: 0 Gas: 0 +│ ├── CPUStaticTypeOf: Operation: 0 Gas: 0 +│ ├── CPUCompositeLit: Operation: 0 Gas: 0 +│ ├── CPUArrayLit: Operation: 0 Gas: 0 +│ ├── CPUSliceLit: Operation: 0 Gas: 0 +│ ├── CPUSliceLit2: Operation: 0 Gas: 0 +│ ├── CPUMapLit: Operation: 0 Gas: 0 +│ ├── CPUStructLit: Operation: 0 Gas: 0 +│ ├── CPUFuncLit: Operation: 0 Gas: 0 +│ ├── CPUConvert: Operation: 0 Gas: 0 +│ ├── CPUFieldType: Operation: 0 Gas: 0 +│ ├── CPUArrayType: Operation: 0 Gas: 0 +│ ├── CPUSliceType: Operation: 0 Gas: 0 +│ ├── CPUPointerType: Operation: 0 Gas: 0 +│ ├── CPUInterfaceType: Operation: 0 Gas: 0 +│ ├── CPUChanType: Operation: 0 Gas: 0 +│ ├── CPUFuncType: Operation: 0 Gas: 0 +│ ├── CPUMapType: Operation: 0 Gas: 0 +│ ├── CPUStructType: Operation: 0 Gas: 0 +│ ├── CPUAssign: Operation: 0 Gas: 0 +│ ├── CPUAddAssign: Operation: 0 Gas: 0 +│ ├── CPUSubAssign: Operation: 0 Gas: 0 +│ ├── CPUMulAssign: Operation: 0 Gas: 0 +│ ├── CPUQuoAssign: Operation: 0 Gas: 0 +│ ├── CPURemAssign: Operation: 0 Gas: 0 +│ ├── CPUBandAssign: Operation: 0 Gas: 0 +│ ├── CPUBandnAssign: Operation: 0 Gas: 0 +│ ├── CPUBorAssign: Operation: 0 Gas: 0 +│ ├── CPUXorAssign: Operation: 0 Gas: 0 +│ ├── CPUShlAssign: Operation: 0 Gas: 0 +│ ├── CPUShrAssign: Operation: 0 Gas: 0 +│ ├── CPUDefine: Operation: 0 Gas: 0 +│ ├── CPUInc: Operation: 0 Gas: 0 +│ ├── CPUDec: Operation: 0 Gas: 0 +│ ├── CPUValueDecl: Operation: 0 Gas: 0 +│ ├── CPUTypeDecl: Operation: 0 Gas: 0 +│ ├── CPUSticky: Operation: 0 Gas: 0 +│ ├── CPUBody: Operation: 1 Gas: 43 +│ ├── CPUForLoop: Operation: 0 Gas: 0 +│ ├── CPURangeIter: Operation: 0 Gas: 0 +│ ├── CPURangeIterString: Operation: 0 Gas: 0 +│ ├── CPURangeIterMap: Operation: 0 Gas: 0 +│ ├── CPURangeIterArrayPtr: Operation: 0 Gas: 0 +│ └── CPUReturnCallDefers: Operation: 0 Gas: 0 +├── KVStore: Operation: 6 Gas: 67788 +│ ├── KVStoreGetObjectPerByte: Operation: 5 Gas: 31632 +│ ├── KVStoreSetObjectPerByte: Operation: 0 Gas: 0 +│ ├── KVStoreGetTypePerByte: Operation: 0 Gas: 0 +│ ├── KVStoreSetTypePerByte: Operation: 0 Gas: 0 +│ ├── KVStoreGetPackageRealmPerByte: Operation: 1 Gas: 36156 +│ ├── KVStoreSetPackageRealmPerByte: Operation: 0 Gas: 0 +│ ├── KVStoreAddMemPackagePerByte: Operation: 0 Gas: 0 +│ ├── KVStoreGetMemPackagePerByte: Operation: 0 Gas: 0 +│ └── KVStoreDeleteObject: Operation: 0 Gas: 0 +├── Memory: Operation: 5 Gas: 1192 +│ ├── MemoryAllocPerByte: Operation: 5 Gas: 1192 +│ └── MemoryGarbageCollect: Operation: 0 Gas: 0 +├── Native: Operation: 0 Gas: 0 +│ ├── NativePrintFlat: Operation: 0 Gas: 0 +│ └── NativePrintPerByte: Operation: 0 Gas: 0 +├── Parsing: Operation: 14 Gas: 23 +│ ├── ParsingToken: Operation: 8 Gas: 8 +│ └── ParsingNesting: Operation: 6 Gas: 15 +├── Store: Operation: 30 Gas: 40844 +│ ├── StoreReadFlat: Operation: 10 Gas: 10000 +│ ├── StoreReadPerByte: Operation: 10 Gas: 3474 +│ ├── StoreWriteFlat: Operation: 5 Gas: 10000 +│ ├── StoreWritePerByte: Operation: 5 Gas: 17370 +│ ├── StoreHas: Operation: 0 Gas: 0 +│ ├── StoreDelete: Operation: 0 Gas: 0 +│ ├── StoreIterNextFlat: Operation: 0 Gas: 0 +│ └── StoreValuePerByte: Operation: 0 Gas: 0 +├── Testing: Operation: 0 Gas: 0 +│ └── Testing: Operation: 0 Gas: 0 +└── Transaction: Operation: 2 Gas: 3320 + ├── TransactionPerByte: Operation: 1 Gas: 2320 + ├── TransactionSigVerifyEd25519: Operation: 0 Gas: 0 + └── TxansactionSigVerifySecp256k1: Operation: 1 Gas: 1000 + +HEIGHT: 7 +EVENTS: [] +INFO: +TX HASH: c9Y1h7dcEm2GUhiqxOI317mwKGjzLW6dqDpu9WsSv2k= +-- gnokey-detail-verbosity-3.stderr.golden -- +Enter password. diff --git a/gno.land/pkg/integration/testdata/infinite_loop.txtar b/gno.land/pkg/integration/testdata/infinite_loop.txtar index 1023d980b3b..b2169073679 100644 --- a/gno.land/pkg/integration/testdata/infinite_loop.txtar +++ b/gno.land/pkg/integration/testdata/infinite_loop.txtar @@ -8,22 +8,22 @@ gnoland start # addpkg + -simulate skip ! gnokey maketx addpkg -pkgdir $WORK/r1 -pkgpath gno.land/r/demo/r1 -simulate skip -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 ! stdout OK! -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' # addpkg + -simulate only ! gnokey maketx addpkg -pkgdir $WORK/r1 -pkgpath gno.land/r/demo/r1 -simulate only -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 ! stdout OK! -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' # run + -simulate skip ! gnokey maketx run -simulate skip -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 $WORK/run.gno ! stdout OK! -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' # run + -simulate only ! gnokey maketx run -simulate only -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 $WORK/run.gno ! stdout OK! -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' # maketx addpkg on r2 (successful) gnokey maketx addpkg -pkgdir $WORK/r2 -pkgpath gno.land/r/demo/r2 -gas-fee 10000000ugnot -gas-wanted 10_000_000 -broadcast -chainid=tendermint_test test1 @@ -31,19 +31,19 @@ stdout OK! # qeval on the render function ! gnokey query vm/qeval --data "gno.land/r/demo/r2.Render(\"helloworld\")" -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' # qrender function ! gnokey query vm/qrender --data 'gno.land/r/demo/r2:noice' -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' # call on the render function ! gnokey query vm/qrender --data 'gno.land/r/demo/r2:' -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' # simulated call on the render function ! gnokey query vm/qrender --data 'gno.land/r/demo/r2:' -stderr 'out of gas.* location: CPUCycles' +stderr 'out of gas.* location: CPUForLoop' -- run.gno -- package main diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index cdc22b4eba5..8d4ff40ca05 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -141,7 +141,7 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { if cfg.RootCfg.Broadcast { cfg.RootCfg.RootCfg.OnTxSuccess = func(tx std.Tx, res *ctypes.ResultBroadcastTxCommit) { - PrintTxInfo(tx, res, io) + client.PrintTxInfo(tx, res, io, cfg.RootCfg.RootCfg.Verbosity) } err := client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) if err != nil { diff --git a/gno.land/pkg/keyscli/call.go b/gno.land/pkg/keyscli/call.go index 285f22be903..0d56e9d0ac8 100644 --- a/gno.land/pkg/keyscli/call.go +++ b/gno.land/pkg/keyscli/call.go @@ -148,7 +148,7 @@ func execMakeCall(cfg *MakeCallCfg, args []string, io commands.IO) error { if cfg.RootCfg.Broadcast { cfg.RootCfg.RootCfg.OnTxSuccess = func(tx std.Tx, res *ctypes.ResultBroadcastTxCommit) { - PrintTxInfo(tx, res, io) + client.PrintTxInfo(tx, res, io, cfg.RootCfg.RootCfg.Verbosity) } err := client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, io) if err != nil { diff --git a/gno.land/pkg/keyscli/root.go b/gno.land/pkg/keyscli/root.go index 5e8bde819bd..eadd5cb7c80 100644 --- a/gno.land/pkg/keyscli/root.go +++ b/gno.land/pkg/keyscli/root.go @@ -2,10 +2,6 @@ package keyscli import ( - "encoding/base64" - - "github.com/gnolang/gno/gnovm/stdlibs/chain" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys/client" @@ -35,7 +31,7 @@ func NewRootCmd(io commands.IO, base client.BaseOptions) *commands.Command { // OnTxSuccess is only used by NewBroadcastCmd cfg.OnTxSuccess = func(tx std.Tx, res *ctypes.ResultBroadcastTxCommit) { - PrintTxInfo(tx, res, io) + client.PrintTxInfo(tx, res, io, cfg.Verbosity) } cmd.AddSubCommands( client.NewAddCmd(cfg, io), @@ -58,55 +54,3 @@ func NewRootCmd(io commands.IO, base client.BaseOptions) *commands.Command { return cmd } - -// PrintTxInfo prints the transaction result to io. If the events has storage deposit -// info then also print it with the total transaction cost. -func PrintTxInfo(tx std.Tx, res *ctypes.ResultBroadcastTxCommit, io commands.IO) { - io.Println(string(res.DeliverTx.Data)) - io.Println("OK!") - io.Println("GAS WANTED:", res.DeliverTx.GasWanted) - io.Println("GAS USED: ", res.DeliverTx.GasUsed) - io.Println("HEIGHT: ", res.Height) - if bytesDelta, coinsDelta, hasStorageEvents := GetStorageInfo(res.DeliverTx.Events); hasStorageEvents { - io.Printfln("STORAGE DELTA: %d bytes", bytesDelta) - if coinsDelta.IsAllPositive() || coinsDelta.IsZero() { - io.Println("STORAGE FEE: ", coinsDelta) - } else { - // NOTE: there is edge cases where coinsDelta can be a mixture of positive and negative coins. - // For example if the keeper respects the storage price param denom and a tx contains a storage cost param change message sandwiched by storage movement messages. - // These will fall in this case and print confusing information but it's so rare that we don't - // really care about this possibility here. - io.Println("STORAGE REFUND:", std.Coins{}.SubUnsafe(coinsDelta)) - } - io.Printfln("TOTAL TX COST: %s", coinsDelta.AddUnsafe(std.Coins{tx.Fee.GasFee})) - } - io.Println("EVENTS: ", string(res.DeliverTx.EncodeEvents())) - io.Println("INFO: ", res.DeliverTx.Info) - io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(res.Hash)) -} - -// GetStorageInfo searches events for StorageDepositEvent or StorageUnlockEvent and returns the bytes delta and coins delta. The coins delta omits RefundWithheld. -func GetStorageInfo(events []abci.Event) (int64, std.Coins, bool) { - var ( - bytesDelta int64 - coinsDelta std.Coins - hasEvents bool - ) - - for _, event := range events { - switch storageEvent := event.(type) { - case chain.StorageDepositEvent: - bytesDelta += storageEvent.BytesDelta - coinsDelta = coinsDelta.AddUnsafe(std.Coins{storageEvent.FeeDelta}) - hasEvents = true - case chain.StorageUnlockEvent: - bytesDelta += storageEvent.BytesDelta - if !storageEvent.RefundWithheld { - coinsDelta = coinsDelta.SubUnsafe(std.Coins{storageEvent.FeeRefund}) - } - hasEvents = true - } - } - - return bytesDelta, coinsDelta, hasEvents -} diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 913df5a450f..5e3a44fca8e 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -144,7 +144,7 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { if cfg.RootCfg.Broadcast { cfg.RootCfg.RootCfg.OnTxSuccess = func(tx std.Tx, res *ctypes.ResultBroadcastTxCommit) { - PrintTxInfo(tx, res, cmdio) + client.PrintTxInfo(tx, res, cmdio, cfg.RootCfg.RootCfg.Verbosity) } err := client.ExecSignAndBroadcast(cfg.RootCfg, args, tx, cmdio) if err != nil { diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index bc86666958c..99c4c8cb83d 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -7,10 +7,10 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnolang" bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gnolang/gno/tm2/pkg/store" "github.com/stretchr/testify/assert" ) @@ -37,7 +37,7 @@ func TestAddPkgDeliverTxInsuffGas(t *testing.T) { defer func() { if r := recover(); r != nil { switch r.(type) { - case store.OutOfGasError: + case gas.OutOfGasError: res.Error = sdk.ABCIError(std.ErrOutOfGas("")) abort = true default: @@ -107,7 +107,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { defer func() { if r := recover(); r != nil { switch r.(type) { - case store.OutOfGasError: + case gas.OutOfGasError: res.Error = sdk.ABCIError(std.ErrOutOfGas("")) abort = true default: diff --git a/gno.land/pkg/sdk/vm/handler_test.go b/gno.land/pkg/sdk/vm/handler_test.go index 07f7ac0f6fa..d96c7b628f1 100644 --- a/gno.land/pkg/sdk/vm/handler_test.go +++ b/gno.land/pkg/sdk/vm/handler_test.go @@ -100,7 +100,7 @@ func TestVmHandlerQuery_Eval(t *testing.T) { {input: []byte(`gno.land/r/doesnotexist.Foo`), expectedErrorMatch: `^invalid package path$`}, {input: []byte(`gno.land/r/hello.Panic()`), expectedErrorMatch: `^foo$`}, {input: []byte(`gno.land/r/hello.sl[6]`), expectedErrorMatch: `^slice index out of bounds: 6 \(len=5\)$`}, - {input: []byte(`gno.land/r/hello.func(){ for {} }()`), expectedErrorMatch: `out of gas in location: CPUCycles`}, + {input: []byte(`gno.land/r/hello.func(){ for {} }()`), expectedErrorMatch: `out of gas in location: CPUForLoop`}, } for _, tc := range tt { diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index c644ee6b1b9..6bd11e32fcb 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -29,6 +29,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/db/memdb" "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/gas" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/overflow" "github.com/gnolang/gno/tm2/pkg/sdk" @@ -534,8 +535,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { } // Log the telemetry logTelemetry( - m2.GasMeter.GasConsumed(), - m2.Cycles, + m2.GasMeter.GasDetail(), attribute.KeyValue{ Key: "operation", Value: attribute.StringValue("m_addpkg"), @@ -643,8 +643,7 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) { } // Log the telemetry logTelemetry( - m.GasMeter.GasConsumed(), - m.Cycles, + m.GasMeter.GasDetail(), attribute.KeyValue{ Key: "operation", Value: attribute.StringValue("m_call"), @@ -677,7 +676,7 @@ func doRecoverInternal(m *gno.Machine, e *error, r any, repanicOutOfGas bool) { return } if err, ok := r.(error); ok { - var oog stypes.OutOfGasError + var oog gas.OutOfGasError if goerrors.As(err, &oog) { if repanicOutOfGas { panic(oog) @@ -819,8 +818,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Log the telemetry logTelemetry( - m2.GasMeter.GasConsumed(), - m2.Cycles, + m2.GasMeter.GasDetail(), attribute.KeyValue{ Key: "operation", Value: attribute.StringValue("m_run"), @@ -996,7 +994,7 @@ func (vm *VMKeeper) QueryEvalString(ctx sdk.Context, pkgPath string, expr string } func (vm *VMKeeper) queryEvalInternal(ctx sdk.Context, pkgPath string, expr string) (rtvs []gno.TypedValue, err error) { - ctx = ctx.WithGasMeter(store.NewGasMeter(maxGasQuery)) + ctx = ctx.WithGasMeter(gas.NewMeter(maxGasQuery, gas.DefaultConfig())) alloc := gno.NewAllocator(maxAllocQuery) gnostore := vm.newGnoTransactionStore(ctx) // throwaway (never committed) // Get Package. @@ -1228,8 +1226,7 @@ func (vm *VMKeeper) refundStorageDeposit(ctx sdk.Context, refundReceiver crypto. // logTelemetry logs the VM processing telemetry func logTelemetry( - gasUsed int64, - cpuCycles int64, + gasUsed gas.GasDetail, attributes ...attribute.KeyValue, ) { if !telemetry.MetricsEnabled() { @@ -1246,14 +1243,14 @@ func logTelemetry( // Record the CPU cycles metrics.VMCPUCycles.Record( context.Background(), - cpuCycles, + gasUsed.CategoryDetails()["CPU"].Total.GasConsumed, metric.WithAttributes(attributes...), ) // Record the gas used metrics.VMGasUsed.Record( context.Background(), - gasUsed, + gasUsed.Total.GasConsumed, metric.WithAttributes(attributes...), ) } diff --git a/gnovm/pkg/gnolang/alloc.go b/gnovm/pkg/gnolang/alloc.go index bae710c55d2..ac3b7581a0f 100644 --- a/gnovm/pkg/gnolang/alloc.go +++ b/gnovm/pkg/gnolang/alloc.go @@ -3,8 +3,8 @@ package gnolang import ( "fmt" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/overflow" - "github.com/gnolang/gno/tm2/pkg/store" ) // Keeps track of in-memory allocations. @@ -20,7 +20,7 @@ type Allocator struct { // It increases monotonically. peakBytes int64 collect func() (left int64, ok bool) // gc callback - gasMeter store.GasMeter + gasMeter gas.Meter } // for gonative, which doesn't consider the allocator. @@ -81,8 +81,6 @@ const ( allocTypedValue = _allocTypedValue ) -const GasCostPerByte = 1 // gas cost per byte allocated - func NewAllocator(maxBytes int64) *Allocator { if maxBytes == 0 { return nil @@ -96,7 +94,7 @@ func (alloc *Allocator) SetGCFn(f func() (int64, bool)) { alloc.collect = f } -func (alloc *Allocator) SetGasMeter(gasMeter store.GasMeter) { +func (alloc *Allocator) SetGasMeter(gasMeter gas.Meter) { alloc.gasMeter = gasMeter } @@ -156,7 +154,7 @@ func (alloc *Allocator) Allocate(size int64) { if alloc.bytes > alloc.peakBytes { if alloc.gasMeter != nil { change := alloc.bytes - alloc.peakBytes - alloc.gasMeter.ConsumeGas(overflow.Mulp(change, GasCostPerByte), "memory allocation") + alloc.gasMeter.ConsumeGas(gas.OpMemoryAllocPerByte, float64(change)) } alloc.peakBytes = alloc.bytes diff --git a/gnovm/pkg/gnolang/garbage_collector.go b/gnovm/pkg/gnolang/garbage_collector.go index d3d5cb3e7e3..5d23521d36e 100644 --- a/gnovm/pkg/gnolang/garbage_collector.go +++ b/gnovm/pkg/gnolang/garbage_collector.go @@ -3,17 +3,9 @@ package gnolang import ( "reflect" - "github.com/gnolang/gno/tm2/pkg/overflow" + "github.com/gnolang/gno/tm2/pkg/gas" ) -// Represents the "time unit" cost for -// a single garbage collection visit. -// It's similar to "CPU cycles" and is -// calculated based on a rough benchmarking -// results. -// TODO: more accurate benchmark. -const VisitCpuFactor = 8 - // Visit visits all reachable associated values. // It is used primarily for GC. // The caller must provide a callback visitor @@ -39,12 +31,12 @@ func (m *Machine) GarbageCollect() (left int64, ok bool) { var visitCount int64 defer func() { - gasCPU := overflow.Mulp(overflow.Mulp(visitCount, VisitCpuFactor), GasFactorCPU) - if debug { - debug.Printf("GasConsumed for GC: %v\n", gasCPU) - } if m.GasMeter != nil { - m.GasMeter.ConsumeGas(gasCPU, "GC") + m.GasMeter.ConsumeGas(gas.OpMemoryGarbageCollect, float64(visitCount)) + if debug { + gasConsumed := m.GasMeter.CalculateGasCost(gas.OpMemoryGarbageCollect, float64(visitCount)) + debug.Printf("Gas consumed for GC: %v\n", gasConsumed) + } } }() diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 755d7edd89f..d32bfd611c0 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -41,7 +41,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/gnovm/pkg/parser" "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/gnolang/gno/tm2/pkg/gas" ) func (m *Machine) MustReadFile(path string) *FileNode { @@ -180,17 +180,17 @@ func ParseFilePackageName(fname string) (string, error) { return f.Name.Name, nil } -const ( - tokenCostFactor = 1 // To be adjusted from benchmarks. - nestingCostFactor = 1 // To be adjusted from benchmarks. -) - func newParserCallback(m *Machine) parser.ParserCallback { if m == nil || m.GasMeter == nil { return nil } return func(tok token.Token, nestLev int) { - m.GasMeter.ConsumeGas(types.Gas(tokenCostFactor+nestLev*nestingCostFactor), "parsing") + // Consume gas for token + m.GasMeter.ConsumeGas(gas.OpParsingToken, 1) + // Consume gas for nesting level + if nestLev > 0 { + m.GasMeter.ConsumeGas(gas.OpParsingNesting, float64(nestLev)) + } } } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 1cc7a8f2936..1c32ee8bee2 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -14,9 +14,8 @@ import ( bm "github.com/gnolang/gno/gnovm/pkg/benchops" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/tm2/pkg/errors" - "github.com/gnolang/gno/tm2/pkg/overflow" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gnolang/gno/tm2/pkg/store" ) //---------------------------------------- @@ -35,7 +34,6 @@ type Machine struct { Alloc *Allocator // memory allocations Exception *Exception // last exception NumResults int // number of results returned - Cycles int64 // number of "cpu" cycles GCCycle int64 // number of "gc" cycles Stage Stage // pre for static eval, add for package init, run otherwise ReviveEnabled bool // true if revive() enabled (only in testing mode for now) @@ -46,7 +44,7 @@ type Machine struct { Output io.Writer Store Store Context any - GasMeter store.GasMeter + GasMeter gas.Meter } // NewMachine initializes a new gno virtual machine, acting as a shorthand @@ -77,7 +75,7 @@ type MachineOptions struct { Context any Alloc *Allocator // or see MaxAllocBytes. MaxAllocBytes int64 // or 0 for no limit. - GasMeter store.GasMeter + GasMeter gas.Meter ReviveEnabled bool SkipPackage bool // don't get/set package or realm. } @@ -1108,141 +1106,6 @@ const ( OpVoid Op = 0xFF // For profiling simple operation ) -const GasFactorCPU int64 = 1 - -//---------------------------------------- -// "CPU" steps. - -func (m *Machine) incrCPU(cycles int64) { - if m.GasMeter != nil { - gasCPU := overflow.Mulp(cycles, GasFactorCPU) - m.GasMeter.ConsumeGas(gasCPU, "CPUCycles") // May panic if out of gas. - } - m.Cycles += cycles -} - -const ( - // CPU cycles - /* Control operators */ - OpCPUInvalid = 1 - OpCPUHalt = 1 - OpCPUNoop = 1 - OpCPUExec = 25 - OpCPUPrecall = 207 - OpCPUEnterCrossing = 100 // XXX - OpCPUCall = 256 - OpCPUCallNativeBody = 424 // Todo benchmark this properly - OpCPUDefer = 64 - OpCPUCallDeferNativeBody = 33 - OpCPUGo = 1 // Not yet implemented - OpCPUSelect = 1 // Not yet implemented - OpCPUSwitchClause = 38 - OpCPUSwitchClauseCase = 143 - OpCPUTypeSwitch = 171 - OpCPUIfCond = 38 - OpCPUPopValue = 1 - OpCPUPopResults = 1 - OpCPUPopBlock = 3 - OpCPUPopFrameAndReset = 15 - OpCPUPanic1 = 121 - OpCPUPanic2 = 21 - OpCPUReturn = 38 - OpCPUReturnAfterCopy = 38 // XXX - OpCPUReturnFromBlock = 36 - OpCPUReturnToBlock = 23 - - /* Unary & binary operators */ - OpCPUUpos = 7 - OpCPUUneg = 25 - OpCPUUnot = 6 - OpCPUUxor = 14 - OpCPUUrecv = 1 // Not yet implemented - OpCPULor = 26 - OpCPULand = 24 - OpCPUEql = 160 - OpCPUNeq = 95 - OpCPULss = 13 - OpCPULeq = 19 - OpCPUGtr = 20 - OpCPUGeq = 26 - OpCPUAdd = 18 - OpCPUSub = 6 - OpCPUBor = 23 - OpCPUXor = 13 - OpCPUMul = 19 - OpCPUQuo = 16 - OpCPURem = 18 - OpCPUShl = 22 - OpCPUShr = 20 - OpCPUBand = 9 - OpCPUBandn = 15 - - /* Other expression operators */ - OpCPUEval = 29 - OpCPUBinary1 = 19 - OpCPUIndex1 = 77 - OpCPUIndex2 = 195 - OpCPUSelector = 32 - OpCPUSlice = 103 - OpCPUStar = 40 - OpCPURef = 125 - OpCPUTypeAssert1 = 30 - OpCPUTypeAssert2 = 25 - // TODO: OpCPUStaticTypeOf is an arbitrary number. - // A good way to benchmark this is yet to be determined. - OpCPUStaticTypeOf = 100 - OpCPUCompositeLit = 50 - OpCPUArrayLit = 137 - OpCPUSliceLit = 183 - OpCPUSliceLit2 = 467 - OpCPUMapLit = 475 - OpCPUStructLit = 179 - OpCPUFuncLit = 61 - OpCPUConvert = 16 - - /* Type operators */ - OpCPUFieldType = 59 - OpCPUArrayType = 57 - OpCPUSliceType = 55 - OpCPUPointerType = 1 // Not yet implemented - OpCPUInterfaceType = 75 - OpCPUChanType = 57 - OpCPUFuncType = 81 - OpCPUMapType = 59 - OpCPUStructType = 174 - - /* Statement operators */ - OpCPUAssign = 79 - OpCPUAddAssign = 85 - OpCPUSubAssign = 57 - OpCPUMulAssign = 55 - OpCPUQuoAssign = 50 - OpCPURemAssign = 46 - OpCPUBandAssign = 54 - OpCPUBandnAssign = 44 - OpCPUBorAssign = 55 - OpCPUXorAssign = 48 - OpCPUShlAssign = 68 - OpCPUShrAssign = 76 - OpCPUDefine = 111 - OpCPUInc = 76 - OpCPUDec = 46 - - /* Decl operators */ - OpCPUValueDecl = 113 - OpCPUTypeDecl = 100 - - /* Loop (sticky) operators (>= 0xD0) */ - OpCPUSticky = 1 // Not a real op - OpCPUBody = 43 - OpCPUForLoop = 27 - OpCPURangeIter = 105 - OpCPURangeIterString = 55 - OpCPURangeIterMap = 48 - OpCPURangeIterArrayPtr = 46 - OpCPUReturnCallDefers = 78 -) - //---------------------------------------- // main run loop. @@ -1291,317 +1154,325 @@ func (m *Machine) Run(st Stage) { bm.StartOpCode(byte(op)) } } + + // CPU gas consumption: 1 unit per op. + consumeCPUGas := func(op gas.Operation) { + if m.GasMeter != nil { + m.GasMeter.ConsumeGas(op, 1) // May panic if out of gas. + } + } + // TODO: this can be optimized manually, even into tiers. switch op { /* Control operators */ case OpHalt: - m.incrCPU(OpCPUHalt) + consumeCPUGas(gas.OpCPUHalt) if bm.OpsEnabled { bm.StopOpCode() } return case OpNoop: - m.incrCPU(OpCPUNoop) + consumeCPUGas(gas.OpCPUNoop) continue case OpExec: - m.incrCPU(OpCPUExec) + consumeCPUGas(gas.OpCPUExec) m.doOpExec(op) case OpPrecall: - m.incrCPU(OpCPUPrecall) + consumeCPUGas(gas.OpCPUPrecall) m.doOpPrecall() case OpEnterCrossing: - m.incrCPU(OpCPUEnterCrossing) + consumeCPUGas(gas.OpCPUEnterCrossing) m.doOpEnterCrossing() case OpCall: - m.incrCPU(OpCPUCall) + consumeCPUGas(gas.OpCPUCall) m.doOpCall() case OpCallNativeBody: - m.incrCPU(OpCPUCallNativeBody) + consumeCPUGas(gas.OpCPUCallNativeBody) m.doOpCallNativeBody() case OpReturn: - m.incrCPU(OpCPUReturn) + consumeCPUGas(gas.OpCPUReturn) m.doOpReturn() case OpReturnAfterCopy: - m.incrCPU(OpCPUReturnAfterCopy) + consumeCPUGas(gas.OpCPUReturnAfterCopy) m.doOpReturnAfterCopy() case OpReturnFromBlock: - m.incrCPU(OpCPUReturnFromBlock) + consumeCPUGas(gas.OpCPUReturnFromBlock) m.doOpReturnFromBlock() case OpReturnToBlock: - m.incrCPU(OpCPUReturnToBlock) + consumeCPUGas(gas.OpCPUReturnToBlock) m.doOpReturnToBlock() case OpDefer: - m.incrCPU(OpCPUDefer) + consumeCPUGas(gas.OpCPUDefer) m.doOpDefer() case OpPanic1: panic("deprecated") case OpPanic2: - m.incrCPU(OpCPUPanic2) + consumeCPUGas(gas.OpCPUPanic2) m.doOpPanic2() case OpCallDeferNativeBody: - m.incrCPU(OpCPUCallDeferNativeBody) + consumeCPUGas(gas.OpCPUCallDeferNativeBody) m.doOpCallDeferNativeBody() case OpGo: - m.incrCPU(OpCPUGo) + consumeCPUGas(gas.OpCPUGo) panic("not yet implemented") case OpSelect: - m.incrCPU(OpCPUSelect) + consumeCPUGas(gas.OpCPUSelect) panic("not yet implemented") case OpSwitchClause: - m.incrCPU(OpCPUSwitchClause) + consumeCPUGas(gas.OpCPUSwitchClause) m.doOpSwitchClause() case OpSwitchClauseCase: - m.incrCPU(OpCPUSwitchClauseCase) + consumeCPUGas(gas.OpCPUSwitchClauseCase) m.doOpSwitchClauseCase() case OpTypeSwitch: - m.incrCPU(OpCPUTypeSwitch) + consumeCPUGas(gas.OpCPUTypeSwitch) m.doOpTypeSwitch() case OpIfCond: - m.incrCPU(OpCPUIfCond) + consumeCPUGas(gas.OpCPUIfCond) m.doOpIfCond() case OpPopValue: - m.incrCPU(OpCPUPopValue) + consumeCPUGas(gas.OpCPUPopValue) m.PopValue() case OpPopResults: - m.incrCPU(OpCPUPopResults) + consumeCPUGas(gas.OpCPUPopResults) m.PopResults() case OpPopBlock: - m.incrCPU(OpCPUPopBlock) + consumeCPUGas(gas.OpCPUPopBlock) m.PopBlock() case OpPopFrameAndReset: - m.incrCPU(OpCPUPopFrameAndReset) + consumeCPUGas(gas.OpCPUPopFrameAndReset) m.PopFrameAndReset() /* Unary operators */ case OpUpos: - m.incrCPU(OpCPUUpos) + consumeCPUGas(gas.OpCPUUpos) m.doOpUpos() case OpUneg: - m.incrCPU(OpCPUUneg) + consumeCPUGas(gas.OpCPUUneg) m.doOpUneg() case OpUnot: - m.incrCPU(OpCPUUnot) + consumeCPUGas(gas.OpCPUUnot) m.doOpUnot() case OpUxor: - m.incrCPU(OpCPUUxor) + consumeCPUGas(gas.OpCPUUxor) m.doOpUxor() case OpUrecv: - m.incrCPU(OpCPUUrecv) + consumeCPUGas(gas.OpCPUUrecv) m.doOpUrecv() /* Binary operators */ case OpLor: - m.incrCPU(OpCPULor) + consumeCPUGas(gas.OpCPULor) m.doOpLor() case OpLand: - m.incrCPU(OpCPULand) + consumeCPUGas(gas.OpCPULand) m.doOpLand() case OpEql: - m.incrCPU(OpCPUEql) + consumeCPUGas(gas.OpCPUEql) m.doOpEql() case OpNeq: - m.incrCPU(OpCPUNeq) + consumeCPUGas(gas.OpCPUNeq) m.doOpNeq() case OpLss: - m.incrCPU(OpCPULss) + consumeCPUGas(gas.OpCPULss) m.doOpLss() case OpLeq: - m.incrCPU(OpCPULeq) + consumeCPUGas(gas.OpCPULeq) m.doOpLeq() case OpGtr: - m.incrCPU(OpCPUGtr) + consumeCPUGas(gas.OpCPUGtr) m.doOpGtr() case OpGeq: - m.incrCPU(OpCPUGeq) + consumeCPUGas(gas.OpCPUGeq) m.doOpGeq() case OpAdd: - m.incrCPU(OpCPUAdd) + consumeCPUGas(gas.OpCPUAdd) m.doOpAdd() case OpSub: - m.incrCPU(OpCPUSub) + consumeCPUGas(gas.OpCPUSub) m.doOpSub() case OpBor: - m.incrCPU(OpCPUBor) + consumeCPUGas(gas.OpCPUBor) m.doOpBor() case OpXor: - m.incrCPU(OpCPUXor) + consumeCPUGas(gas.OpCPUXor) m.doOpXor() case OpMul: - m.incrCPU(OpCPUMul) + consumeCPUGas(gas.OpCPUMul) m.doOpMul() case OpQuo: - m.incrCPU(OpCPUQuo) + consumeCPUGas(gas.OpCPUQuo) m.doOpQuo() case OpRem: - m.incrCPU(OpCPURem) + consumeCPUGas(gas.OpCPURem) m.doOpRem() case OpShl: - m.incrCPU(OpCPUShl) + consumeCPUGas(gas.OpCPUShl) m.doOpShl() case OpShr: - m.incrCPU(OpCPUShr) + consumeCPUGas(gas.OpCPUShr) m.doOpShr() case OpBand: - m.incrCPU(OpCPUBand) + consumeCPUGas(gas.OpCPUBand) m.doOpBand() case OpBandn: - m.incrCPU(OpCPUBandn) + consumeCPUGas(gas.OpCPUBandn) m.doOpBandn() /* Expression operators */ case OpEval: - m.incrCPU(OpCPUEval) + consumeCPUGas(gas.OpCPUEval) m.doOpEval() case OpBinary1: - m.incrCPU(OpCPUBinary1) + consumeCPUGas(gas.OpCPUBinary1) m.doOpBinary1() case OpIndex1: - m.incrCPU(OpCPUIndex1) + consumeCPUGas(gas.OpCPUIndex1) m.doOpIndex1() case OpIndex2: - m.incrCPU(OpCPUIndex2) + consumeCPUGas(gas.OpCPUIndex2) m.doOpIndex2() case OpSelector: - m.incrCPU(OpCPUSelector) + consumeCPUGas(gas.OpCPUSelector) m.doOpSelector() case OpSlice: - m.incrCPU(OpCPUSlice) + consumeCPUGas(gas.OpCPUSlice) m.doOpSlice() case OpStar: - m.incrCPU(OpCPUStar) + consumeCPUGas(gas.OpCPUStar) m.doOpStar() case OpRef: - m.incrCPU(OpCPURef) + consumeCPUGas(gas.OpCPURef) m.doOpRef() case OpTypeAssert1: - m.incrCPU(OpCPUTypeAssert1) + consumeCPUGas(gas.OpCPUTypeAssert1) m.doOpTypeAssert1() case OpTypeAssert2: - m.incrCPU(OpCPUTypeAssert2) + consumeCPUGas(gas.OpCPUTypeAssert2) m.doOpTypeAssert2() case OpStaticTypeOf: - m.incrCPU(OpCPUStaticTypeOf) + consumeCPUGas(gas.OpCPUStaticTypeOf) m.doOpStaticTypeOf() case OpCompositeLit: - m.incrCPU(OpCPUCompositeLit) + consumeCPUGas(gas.OpCPUCompositeLit) m.doOpCompositeLit() case OpArrayLit: - m.incrCPU(OpCPUArrayLit) + consumeCPUGas(gas.OpCPUArrayLit) m.doOpArrayLit() case OpSliceLit: - m.incrCPU(OpCPUSliceLit) + consumeCPUGas(gas.OpCPUSliceLit) m.doOpSliceLit() case OpSliceLit2: - m.incrCPU(OpCPUSliceLit2) + consumeCPUGas(gas.OpCPUSliceLit2) m.doOpSliceLit2() case OpFuncLit: - m.incrCPU(OpCPUFuncLit) + consumeCPUGas(gas.OpCPUFuncLit) m.doOpFuncLit() case OpMapLit: - m.incrCPU(OpCPUMapLit) + consumeCPUGas(gas.OpCPUMapLit) m.doOpMapLit() case OpStructLit: - m.incrCPU(OpCPUStructLit) + consumeCPUGas(gas.OpCPUStructLit) m.doOpStructLit() case OpConvert: - m.incrCPU(OpCPUConvert) + consumeCPUGas(gas.OpCPUConvert) m.doOpConvert() /* Type operators */ case OpFieldType: - m.incrCPU(OpCPUFieldType) + consumeCPUGas(gas.OpCPUFieldType) m.doOpFieldType() case OpArrayType: - m.incrCPU(OpCPUArrayType) + consumeCPUGas(gas.OpCPUArrayType) m.doOpArrayType() case OpSliceType: - m.incrCPU(OpCPUSliceType) + consumeCPUGas(gas.OpCPUSliceType) m.doOpSliceType() case OpChanType: - m.incrCPU(OpCPUChanType) + consumeCPUGas(gas.OpCPUChanType) m.doOpChanType() case OpFuncType: - m.incrCPU(OpCPUFuncType) + consumeCPUGas(gas.OpCPUFuncType) m.doOpFuncType() case OpMapType: - m.incrCPU(OpCPUMapType) + consumeCPUGas(gas.OpCPUMapType) m.doOpMapType() case OpStructType: - m.incrCPU(OpCPUStructType) + consumeCPUGas(gas.OpCPUStructType) m.doOpStructType() case OpInterfaceType: - m.incrCPU(OpCPUInterfaceType) + consumeCPUGas(gas.OpCPUInterfaceType) m.doOpInterfaceType() /* Statement operators */ case OpAssign: - m.incrCPU(OpCPUAssign) + consumeCPUGas(gas.OpCPUAssign) m.doOpAssign() case OpAddAssign: - m.incrCPU(OpCPUAddAssign) + consumeCPUGas(gas.OpCPUAddAssign) m.doOpAddAssign() case OpSubAssign: - m.incrCPU(OpCPUSubAssign) + consumeCPUGas(gas.OpCPUSubAssign) m.doOpSubAssign() case OpMulAssign: - m.incrCPU(OpCPUMulAssign) + consumeCPUGas(gas.OpCPUMulAssign) m.doOpMulAssign() case OpQuoAssign: - m.incrCPU(OpCPUQuoAssign) + consumeCPUGas(gas.OpCPUQuoAssign) m.doOpQuoAssign() case OpRemAssign: - m.incrCPU(OpCPURemAssign) + consumeCPUGas(gas.OpCPURemAssign) m.doOpRemAssign() case OpBandAssign: - m.incrCPU(OpCPUBandAssign) + consumeCPUGas(gas.OpCPUBandAssign) m.doOpBandAssign() case OpBandnAssign: - m.incrCPU(OpCPUBandnAssign) + consumeCPUGas(gas.OpCPUBandnAssign) m.doOpBandnAssign() case OpBorAssign: - m.incrCPU(OpCPUBorAssign) + consumeCPUGas(gas.OpCPUBorAssign) m.doOpBorAssign() case OpXorAssign: - m.incrCPU(OpCPUXorAssign) + consumeCPUGas(gas.OpCPUXorAssign) m.doOpXorAssign() case OpShlAssign: - m.incrCPU(OpCPUShlAssign) + consumeCPUGas(gas.OpCPUShlAssign) m.doOpShlAssign() case OpShrAssign: - m.incrCPU(OpCPUShrAssign) + consumeCPUGas(gas.OpCPUShrAssign) m.doOpShrAssign() case OpDefine: - m.incrCPU(OpCPUDefine) + consumeCPUGas(gas.OpCPUDefine) m.doOpDefine() case OpInc: - m.incrCPU(OpCPUInc) + consumeCPUGas(gas.OpCPUInc) m.doOpInc() case OpDec: - m.incrCPU(OpCPUDec) + consumeCPUGas(gas.OpCPUDec) m.doOpDec() /* Decl operators */ case OpValueDecl: - m.incrCPU(OpCPUValueDecl) + consumeCPUGas(gas.OpCPUValueDecl) m.doOpValueDecl() case OpTypeDecl: - m.incrCPU(OpCPUTypeDecl) + consumeCPUGas(gas.OpCPUTypeDecl) m.doOpTypeDecl() /* Loop (sticky) operators */ case OpBody: - m.incrCPU(OpCPUBody) + consumeCPUGas(gas.OpCPUBody) m.doOpExec(op) case OpForLoop: - m.incrCPU(OpCPUForLoop) + consumeCPUGas(gas.OpCPUForLoop) m.doOpExec(op) case OpRangeIter: - m.incrCPU(OpCPURangeIter) + consumeCPUGas(gas.OpCPURangeIter) m.doOpExec(op) case OpRangeIterArrayPtr: - m.incrCPU(OpCPURangeIterArrayPtr) + consumeCPUGas(gas.OpCPURangeIterArrayPtr) m.doOpExec(op) case OpRangeIterString: - m.incrCPU(OpCPURangeIterString) + consumeCPUGas(gas.OpCPURangeIterString) m.doOpExec(op) case OpRangeIterMap: - m.incrCPU(OpCPURangeIterMap) + consumeCPUGas(gas.OpCPURangeIterMap) m.doOpExec(op) case OpReturnCallDefers: - m.incrCPU(OpCPUReturnCallDefers) + consumeCPUGas(gas.OpCPUReturnCallDefers) m.doOpReturnCallDefers() default: panic(fmt.Sprintf("unexpected opcode %s", op.String())) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 6cfa8f442e3..ba4e9d58f99 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -13,7 +13,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/txlog" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/colors" - "github.com/gnolang/gno/tm2/pkg/overflow" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" "github.com/gnolang/gno/tm2/pkg/store/utils" @@ -38,7 +38,7 @@ type NativeResolver func(pkgName string, name Name) func(m *Machine) // blockchain, or the file system. type Store interface { // STABLE - BeginTransaction(baseStore, iavlStore store.Store, gasMeter store.GasMeter) TransactionStore + BeginTransaction(baseStore, iavlStore store.Store, gasMeter gas.Meter) TransactionStore GetPackageGetter() PackageGetter SetPackageGetter(PackageGetter) GetPackage(pkgPath string, isImport bool) *PackageValue @@ -93,47 +93,6 @@ type TransactionStore interface { Write() } -// Gas consumption descriptors. -const ( - GasGetObjectDesc = "GetObjectPerByte" - GasSetObjectDesc = "SetObjectPerByte" - GasGetTypeDesc = "GetTypePerByte" - GasSetTypeDesc = "SetTypePerByte" - GasGetPackageRealmDesc = "GetPackageRealmPerByte" - GasSetPackageRealmDesc = "SetPackageRealmPerByte" - GasAddMemPackageDesc = "AddMemPackagePerByte" - GasGetMemPackageDesc = "GetMemPackagePerByte" - GasDeleteObjectDesc = "DeleteObjectFlat" -) - -// GasConfig defines gas cost for each operation on KVStores -type GasConfig struct { - GasGetObject int64 - GasSetObject int64 - GasGetType int64 - GasSetType int64 - GasGetPackageRealm int64 - GasSetPackageRealm int64 - GasAddMemPackage int64 - GasGetMemPackage int64 - GasDeleteObject int64 -} - -// DefaultGasConfig returns a default gas config for KVStores. -func DefaultGasConfig() GasConfig { - return GasConfig{ - GasGetObject: 16, // per byte cost - GasSetObject: 16, // per byte cost - GasGetType: 52, // per byte cost - GasSetType: 52, // per byte cost - GasGetPackageRealm: 524, // per byte cost - GasSetPackageRealm: 524, // per byte cost - GasAddMemPackage: 8, // per byte cost - GasGetMemPackage: 8, // per byte cost - GasDeleteObject: 3715, // flat cost - } -} - type defaultStore struct { // underlying stores used to keep data baseStore store.Store // for objects, types, nodes @@ -158,8 +117,7 @@ type defaultStore struct { current []string // for detecting import cycles. // gas - gasMeter store.GasMeter - gasConfig GasConfig + gasMeter gas.Meter // realm storage changes on message level. realmStorageDiffs map[string]int64 // maps realm path to size diff @@ -182,14 +140,13 @@ func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore // store configuration pkgGetter: nil, nativeResolver: nil, - gasConfig: DefaultGasConfig(), } InitStoreCaches(ds) return ds } // If nil baseStore and iavlStore, the baseStores are re-used. -func (ds *defaultStore) BeginTransaction(baseStore, iavlStore store.Store, gasMeter store.GasMeter) TransactionStore { +func (ds *defaultStore) BeginTransaction(baseStore, iavlStore store.Store, gasMeter gas.Meter) TransactionStore { if baseStore == nil { baseStore = ds.baseStore } @@ -212,8 +169,7 @@ func (ds *defaultStore) BeginTransaction(baseStore, iavlStore store.Store, gasMe nativeResolver: ds.nativeResolver, // gas meter - gasMeter: gasMeter, - gasConfig: ds.gasConfig, + gasMeter: gasMeter, // transient current: nil, @@ -375,8 +331,7 @@ func (ds *defaultStore) GetPackageRealm(pkgPath string) (rlm *Realm) { if bz == nil { return nil } - gas := overflow.Mulp(ds.gasConfig.GasGetPackageRealm, store.Gas(len(bz))) - ds.consumeGas(gas, GasGetPackageRealmDesc) + ds.consumeGas(gas.OpKVStoreGetPackageRealmPerByte, float64(len(bz))) amino.MustUnmarshal(bz, &rlm) size = len(bz) if debug { @@ -405,8 +360,7 @@ func (ds *defaultStore) SetPackageRealm(rlm *Realm) { oid := ObjectIDFromPkgPath(rlm.Path) key := backendRealmKey(oid) bz := amino.MustMarshal(rlm) - gas := overflow.Mulp(ds.gasConfig.GasSetPackageRealm, store.Gas(len(bz))) - ds.consumeGas(gas, GasSetPackageRealmDesc) + ds.consumeGas(gas.OpKVStoreSetPackageRealmPerByte, float64(len(bz))) ds.baseStore.Set([]byte(key), bz) size = len(bz) } @@ -469,8 +423,7 @@ func (ds *defaultStore) loadObjectSafe(oid ObjectID) Object { hash := hashbz[:HashSize] bz := hashbz[HashSize:] var oo Object - gas := overflow.Mulp(ds.gasConfig.GasGetObject, store.Gas(len(bz))) - ds.consumeGas(gas, GasGetObjectDesc) + ds.consumeGas(gas.OpKVStoreGetObjectPerByte, float64(len(bz))) amino.MustUnmarshal(bz, &oo) if debug { debug.Printf("loadObjectSafe by oid: %v, type of oo: %v\n", oid, reflect.TypeOf(oo)) @@ -625,8 +578,7 @@ func (ds *defaultStore) SetObject(oo Object) int64 { o2 := copyValueWithRefs(oo) // marshal to binary. bz := amino.MustMarshalAny(o2) - gas := overflow.Mulp(ds.gasConfig.GasSetObject, store.Gas(len(bz))) - ds.consumeGas(gas, GasSetObjectDesc) + ds.consumeGas(gas.OpKVStoreSetObjectPerByte, float64(len(bz))) // set hash. hash := HashBytes(bz) // XXX objectHash(bz)??? if len(hash) != HashSize { @@ -730,7 +682,7 @@ func (ds *defaultStore) DelObject(oo Object) int64 { bm.StopStore(0) }() } - ds.consumeGas(ds.gasConfig.GasDeleteObject, GasDeleteObjectDesc) + ds.consumeGas(gas.OpKVStoreDeleteObject, 1) oid := oo.GetObjectID() size := oo.GetObjectInfo().LastObjectSize // delete from cache. @@ -774,8 +726,7 @@ func (ds *defaultStore) GetTypeSafe(tid TypeID) Type { key := backendTypeKey(tid) bz := ds.baseStore.Get([]byte(key)) if bz != nil { - gas := overflow.Mulp(ds.gasConfig.GasGetType, store.Gas(len(bz))) - ds.consumeGas(gas, GasGetTypeDesc) + ds.consumeGas(gas.OpKVStoreGetTypePerByte, float64(len(bz))) var tt Type amino.MustUnmarshal(bz, &tt) if debug { @@ -833,8 +784,7 @@ func (ds *defaultStore) SetType(tt Type) { key := backendTypeKey(tid) tcopy := copyTypeWithRefs(tt) bz := amino.MustMarshalAny(tcopy) - gas := overflow.Mulp(ds.gasConfig.GasSetType, store.Gas(len(bz))) - ds.consumeGas(gas, GasSetTypeDesc) + ds.consumeGas(gas.OpKVStoreSetTypePerByte, float64(len(bz))) ds.baseStore.Set([]byte(key), bz) size = len(bz) } @@ -977,8 +927,7 @@ func (ds *defaultStore) AddMemPackage(mpkg *std.MemPackage, mptype MemPackageTyp ctr := ds.incGetPackageIndexCounter() idxkey := []byte(backendPackageIndexKey(ctr)) bz := amino.MustMarshal(mpkg) - gas := overflow.Mulp(ds.gasConfig.GasAddMemPackage, store.Gas(len(bz))) - ds.consumeGas(gas, GasAddMemPackageDesc) + ds.consumeGas(gas.OpKVStoreAddMemPackagePerByte, float64(len(bz))) ds.baseStore.Set(idxkey, []byte(mpkg.Path)) pathkey := []byte(backendPackagePathKey(mpkg.Path)) ds.iavlStore.Set(pathkey, bz) @@ -1020,8 +969,7 @@ func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage } return nil } - gas := overflow.Mulp(ds.gasConfig.GasGetMemPackage, store.Gas(len(bz))) - ds.consumeGas(gas, GasGetMemPackageDesc) + ds.consumeGas(gas.OpKVStoreGetMemPackagePerByte, float64(len(bz))) var mpkg *std.MemPackage amino.MustUnmarshal(bz, &mpkg) @@ -1094,10 +1042,10 @@ func (ds *defaultStore) IterMemPackage() <-chan *std.MemPackage { } } -func (ds *defaultStore) consumeGas(gas int64, descriptor string) { +func (ds *defaultStore) consumeGas(operation gas.Operation, multiplier float64) { // In the tests, the defaultStore may not set the gas meter. if ds.gasMeter != nil { - ds.gasMeter.ConsumeGas(gas, descriptor) + ds.gasMeter.ConsumeGas(operation, multiplier) } } diff --git a/gnovm/pkg/gnolang/uverse.go b/gnovm/pkg/gnolang/uverse.go index 7798d4065d9..467ea58d3f9 100644 --- a/gnovm/pkg/gnolang/uverse.go +++ b/gnovm/pkg/gnolang/uverse.go @@ -9,16 +9,7 @@ import ( bm "github.com/gnolang/gno/gnovm/pkg/benchops" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/overflow" - "github.com/gnolang/gno/tm2/pkg/store/types" -) - -const ( - // NativeCPUUversePrintInit is the base gas cost for the Print function. - // The actual cost is 1800, but we subtract OpCPUCallNativeBody (424), resulting in 1376. - NativeCPUUversePrintInit = 1376 - // NativeCPUUversePrintPerChar is now chars per gas unit. - NativeCPUUversePrintCharsPerGas = 10 + "github.com/gnolang/gno/tm2/pkg/gas" ) // ---------------------------------------- @@ -1146,22 +1137,19 @@ func copyListToRunes(dst []rune, tvs []TypedValue) { } } -func consumeGas(m *Machine, amount types.Gas) { - if m.GasMeter != nil { - m.GasMeter.ConsumeGas(amount, "CPUCycles") - } -} - // uversePrint is used for the print and println functions. // println passes newline = true. // xv contains the variadic argument passed to the function. func uversePrint(m *Machine, xv PointerValue, newline bool) { - consumeGas(m, NativeCPUUversePrintInit) output := formatUverseOutput(m, xv, newline) - consumeGas(m, overflow.Divp(types.Gas(len(output)), NativeCPUUversePrintCharsPerGas)) // For debugging: // fmt.Println(colors.Cyan(string(output))) m.Output.Write(output) + + if m.GasMeter != nil { + m.GasMeter.ConsumeGas(gas.OpNativePrintFlat, 1) + m.GasMeter.ConsumeGas(gas.OpNativePrintPerByte, float64(len(output))) + } } func formatUverseOutput(m *Machine, xv PointerValue, newline bool) []byte { diff --git a/gnovm/pkg/test/filetest.go b/gnovm/pkg/test/filetest.go index dbaa1b306bd..82383c2b2a2 100644 --- a/gnovm/pkg/test/filetest.go +++ b/gnovm/pkg/test/filetest.go @@ -16,8 +16,8 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gnolang/gno/tm2/pkg/store" "github.com/pmezard/go-difflib/difflib" "go.uber.org/multierr" ) @@ -66,7 +66,7 @@ func (opts *TestOptions) runFiletest(fname string, source []byte, tgs gno.Store, if dirs.First(DirectiveRealm) != nil { opslog = new(bytes.Buffer) } - gasMeter := store.NewInfiniteGasMeter() + gasMeter := gas.NewInfiniteMeter(gas.DefaultConfig()) // Create machine for execution and run test tcw := opts.BaseStore.CacheWrap() m := gno.NewMachineWithOptions(gno.MachineOptions{ diff --git a/gnovm/pkg/test/test.go b/gnovm/pkg/test/test.go index d90d6a17710..910870b4b37 100644 --- a/gnovm/pkg/test/test.go +++ b/gnovm/pkg/test/test.go @@ -20,6 +20,7 @@ import ( "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/gnovm/tests/stdlibs/chain/runtime" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" storetypes "github.com/gnolang/gno/tm2/pkg/store/types" @@ -400,6 +401,7 @@ func (opts *TestOptions) runTestFiles( // - Wrap here. m = Machine(tgs, opts.WriterForStore(), mpkg.Path, opts.Debug) m.Alloc = alloc.Reset() + m.GasMeter = gas.NewInfiniteMeter(gas.DefaultConfig()) m.SetActivePackage(pv) testingpv := m.Store.GetPackage("testing", false) @@ -527,10 +529,13 @@ func (opts *TestOptions) runTestFiles( float64(allocs)/float64(maxAllocs)*100, ) } - fmt.Fprintf(opts.Error, "--- runtime: cycle=%s allocs=%s\n", - prettySize(m.Cycles), - allocsVal, - ) + + if m.GasMeter != nil { + fmt.Fprintf(opts.Error, "--- runtime: cycle=%d allocs=%s\n", + m.GasMeter.GasDetail().CategoryDetails()["CPU"].Total.GasConsumed, + allocsVal, + ) + } } } diff --git a/go.mod b/go.mod index 5037dd1db7b..123d8ab34ab 100644 --- a/go.mod +++ b/go.mod @@ -30,10 +30,12 @@ require ( github.com/rogpeppe/go-internal v1.14.1 github.com/rs/cors v1.11.1 github.com/rs/xid v1.6.0 + github.com/ryanuber/columnize v2.1.2+incompatible github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/valyala/bytebufferpool v1.0.0 + github.com/xlab/treeprint v1.2.0 github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc go.etcd.io/bbolt v1.3.11 diff --git a/go.sum b/go.sum index ab39e34afbb..7fd2245a301 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -210,6 +212,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= diff --git a/misc/autocounterd/go.sum b/misc/autocounterd/go.sum index c34609d099f..677f90152e9 100644 --- a/misc/autocounterd/go.sum +++ b/misc/autocounterd/go.sum @@ -167,6 +167,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -182,6 +184,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= diff --git a/misc/loop/go.sum b/misc/loop/go.sum index c16d1a479a7..adb56b86994 100644 --- a/misc/loop/go.sum +++ b/misc/loop/go.sum @@ -207,6 +207,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -224,6 +226,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= diff --git a/misc/stress-test/stress-test-many-posts/go.sum b/misc/stress-test/stress-test-many-posts/go.sum index 319505bff4a..137c895e18a 100644 --- a/misc/stress-test/stress-test-many-posts/go.sum +++ b/misc/stress-test/stress-test-many-posts/go.sum @@ -181,6 +181,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b h1:oV47z+jotrLVvhiLRNzACVe7/qZ8DcRlMlDucR/FARo= github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b/go.mod h1:JprPCeMgYyLKJoAy9nxpVScm7NwFSwpibdrUKm4kcw0= @@ -200,6 +202,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45 github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= diff --git a/tm2/pkg/bft/abci/types/types.go b/tm2/pkg/bft/abci/types/types.go index 9350f85f68d..634e15934a2 100644 --- a/tm2/pkg/bft/abci/types/types.go +++ b/tm2/pkg/bft/abci/types/types.go @@ -6,6 +6,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/merkle" + "github.com/gnolang/gno/tm2/pkg/gas" ) // ---------------------------------------- @@ -177,13 +178,13 @@ type ResponseBeginBlock struct { type ResponseCheckTx struct { ResponseBase GasWanted int64 // nondeterministic - GasUsed int64 + GasUsed gas.GasDetail } type ResponseDeliverTx struct { ResponseBase GasWanted int64 - GasUsed int64 + GasUsed gas.GasDetail } type ResponseEndBlock struct { diff --git a/tm2/pkg/crypto/keys/client/broadcast.go b/tm2/pkg/crypto/keys/client/broadcast.go index 0040fcad17d..e4d1d0bb4f0 100644 --- a/tm2/pkg/crypto/keys/client/broadcast.go +++ b/tm2/pkg/crypto/keys/client/broadcast.go @@ -88,13 +88,7 @@ func execBroadcast(cfg *BroadcastCfg, args []string, io commands.IO) error { if cfg.RootCfg.OnTxSuccess != nil { cfg.RootCfg.OnTxSuccess(tx, res) } else { - io.Println(string(res.DeliverTx.Data)) - io.Println("OK!") - io.Println("GAS WANTED:", res.DeliverTx.GasWanted) - io.Println("GAS USED: ", res.DeliverTx.GasUsed) - io.Println("HEIGHT: ", res.Height) - io.Println("EVENTS: ", string(res.DeliverTx.EncodeEvents())) - io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(res.Hash)) + PrintTxInfo(tx, res, io, cfg.RootCfg.Verbosity) } } return nil @@ -158,12 +152,12 @@ func estimateGasFee(cli client.ABCIClient, bres *ctypes.ResultBroadcastTxCommit) return nil } - fee := bres.DeliverTx.GasUsed/gp.Gas + 1 + fee := bres.DeliverTx.GasUsed.Total.GasConsumed/gp.Gas + 1 fee = overflow.Mulp(fee, gp.Price.Amount) // 5% fee buffer to cover the suden change of gas price feeBuffer := overflow.Mulp(fee, 5) / 100 fee = overflow.Addp(fee, feeBuffer) - s := fmt.Sprintf("estimated gas usage: %d, gas fee: %d%s, current gas price: %s\n", bres.DeliverTx.GasUsed, fee, gp.Price.Denom, gp.String()) + s := fmt.Sprintf("estimated gas usage: %d, gas fee: %d%s, current gas price: %s\n", bres.DeliverTx.GasUsed.Total.GasConsumed, fee, gp.Price.Denom, gp.String()) bres.DeliverTx.Info = s return nil } diff --git a/tm2/pkg/crypto/keys/client/common.go b/tm2/pkg/crypto/keys/client/common.go index 901eca1a200..98fe4c43787 100644 --- a/tm2/pkg/crypto/keys/client/common.go +++ b/tm2/pkg/crypto/keys/client/common.go @@ -9,6 +9,7 @@ type BaseOptions struct { Home string Remote string Quiet bool + Verbosity int // Verbosity level for gas detail: 0=none, 1=categories, 2=non-zero ops, 3=all ops InsecurePasswordStdin bool Config string // OnTxSuccess is called when the transaction tx succeeds. It can, for example, diff --git a/tm2/pkg/crypto/keys/client/info.go b/tm2/pkg/crypto/keys/client/info.go new file mode 100644 index 00000000000..1e75e65461f --- /dev/null +++ b/tm2/pkg/crypto/keys/client/info.go @@ -0,0 +1,141 @@ +package client + +import ( + "encoding/base64" + "fmt" + "sort" + "strings" + + "github.com/gnolang/gno/gnovm/stdlibs/chain" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/gas" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/ryanuber/columnize" + "github.com/xlab/treeprint" +) + +// PrintTxInfo prints the transaction result to io. If the events has storage deposit +// info then also print it with the total transaction cost. +func PrintTxInfo(tx std.Tx, res *ctypes.ResultBroadcastTxCommit, io commands.IO, verbosity int) { + io.Println(string(res.DeliverTx.Data)) + io.Println("OK!") + io.Println("GAS WANTED:", res.DeliverTx.GasWanted) + + if verbosity == 0 { + io.Println("GAS USED: ", res.DeliverTx.GasUsed.Total.GasConsumed) + } else { + printGasDetail(res.DeliverTx.GasUsed, io, verbosity) + } + + io.Println("HEIGHT: ", res.Height) + if bytesDelta, coinsDelta, hasStorageEvents := GetStorageInfo(res.DeliverTx.Events); hasStorageEvents { + io.Printfln("STORAGE DELTA: %d bytes", bytesDelta) + if coinsDelta.IsAllPositive() || coinsDelta.IsZero() { + io.Println("STORAGE FEE: ", coinsDelta) + } else { + // NOTE: there is edge cases where coinsDelta can be a mixture of positive and negative coins. + // For example if the keeper respects the storage price param denom and a tx contains a storage cost param change message sandwiched by storage movement messages. + // These will fall in this case and print confusing information but it's so rare that we don't + // really care about this possibility here. + io.Println("STORAGE REFUND:", std.Coins{}.SubUnsafe(coinsDelta)) + } + io.Printfln("TOTAL TX COST: %s", coinsDelta.AddUnsafe(std.Coins{tx.Fee.GasFee})) + } + io.Println("EVENTS: ", string(res.DeliverTx.EncodeEvents())) + io.Println("INFO: ", res.DeliverTx.Info) + io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(res.Hash)) +} + +// printGasDetail prints detailed gas usage information based on the verbosity level. +func printGasDetail(detail gas.GasDetail, io commands.IO, verbosity int) { + // Helper function to format each line with proper columns. + delimitColumns := func(name string, value gas.Detail) string { + return fmt.Sprintf( + "%s: | Operation: %d | Gas: %d", + name, + value.OperationCount, + value.GasConsumed, + ) + } + + tree := treeprint.NewWithRoot(delimitColumns("GAS USED", detail.Total)) + categoryDetails := detail.CategoryDetails() + + // Get sorted category map keys to iterate in order. + categoryKeys := make([]string, 0, len(categoryDetails)) + for key := range categoryDetails { + categoryKeys = append(categoryKeys, key) + } + sort.Strings(categoryKeys) + + for _, categoryKey := range categoryKeys { + category := categoryDetails[categoryKey] + + // Add operation details only if verbosity is 2 or higher. + if verbosity >= 2 { + // Skip categories with zero total operation count unless verbosity is 3. + if category.Total.OperationCount == 0 && verbosity != 3 { + continue + } + + branch := tree.AddBranch(delimitColumns(categoryKey, category.Total)) + + // Get sorted operation map keys to iterate in order. + operationKeys := make([]int, 0, len(category.Operations)) + for key := range category.Operations { + operationKeys = append(operationKeys, int(key)) + } + sort.Ints(operationKeys) + + for _, operationKey := range operationKeys { + operation := gas.Operation(operationKey) + operationDetail := category.Operations[operation] + + // Skip operations with zero operation count unless verbosity is 3. + if operationDetail.OperationCount == 0 && verbosity != 3 { + continue + } + branch.AddNode(delimitColumns(operation.String(), operationDetail)) + } + // Else add only category total if there were any operations. + } else if category.Total.OperationCount > 0 { + tree.AddBranch(delimitColumns(categoryKey, category.Total)) + } + } + + // Render the tree as separated lines. + lines := strings.Split(tree.String(), "\n") + + // Format the lines into aligned columns and print. + config := columnize.DefaultConfig() + config.NoTrim = true + io.Println(columnize.Format(lines, config)) +} + +// GetStorageInfo searches events for StorageDepositEvent or StorageUnlockEvent and returns the bytes delta and coins delta. The coins delta omits RefundWithheld. +func GetStorageInfo(events []abci.Event) (int64, std.Coins, bool) { + var ( + bytesDelta int64 + coinsDelta std.Coins + hasEvents bool + ) + + for _, event := range events { + switch storageEvent := event.(type) { + case chain.StorageDepositEvent: + bytesDelta += storageEvent.BytesDelta + coinsDelta = coinsDelta.AddUnsafe(std.Coins{storageEvent.FeeDelta}) + hasEvents = true + case chain.StorageUnlockEvent: + bytesDelta += storageEvent.BytesDelta + if !storageEvent.RefundWithheld { + coinsDelta = coinsDelta.SubUnsafe(std.Coins{storageEvent.FeeRefund}) + } + hasEvents = true + } + } + + return bytesDelta, coinsDelta, hasEvents +} diff --git a/tm2/pkg/crypto/keys/client/info_test.go b/tm2/pkg/crypto/keys/client/info_test.go new file mode 100644 index 00000000000..ab0275a81cc --- /dev/null +++ b/tm2/pkg/crypto/keys/client/info_test.go @@ -0,0 +1,469 @@ +package client + +import ( + "bytes" + "testing" + + "github.com/gnolang/gno/gnovm/stdlibs/chain" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" + "github.com/gnolang/gno/tm2/pkg/commands" + "github.com/gnolang/gno/tm2/pkg/gas" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" +) + +func TestGetStorageInfo(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + events []abci.Event + expectedBytes int64 + expectedCoins std.Coins + expectedHasEvents bool + }{ + { + name: "no storage events", + events: []abci.Event{}, + expectedBytes: 0, + expectedCoins: nil, + expectedHasEvents: false, + }, + { + name: "single deposit event", + events: []abci.Event{ + chain.StorageDepositEvent{ + BytesDelta: 100, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 1000}, + PkgPath: "gno.land/r/demo", + }, + }, + expectedBytes: 100, + expectedCoins: std.Coins{std.Coin{Denom: "ugnot", Amount: 1000}}, + expectedHasEvents: true, + }, + { + name: "single unlock event without withholding", + events: []abci.Event{ + chain.StorageUnlockEvent{ + BytesDelta: -50, + FeeRefund: std.Coin{Denom: "ugnot", Amount: 500}, + PkgPath: "gno.land/r/demo", + RefundWithheld: false, + }, + }, + expectedBytes: -50, + expectedCoins: std.Coins{std.Coin{Denom: "ugnot", Amount: -500}}, + expectedHasEvents: true, + }, + { + name: "single unlock event with withholding", + events: []abci.Event{ + chain.StorageUnlockEvent{ + BytesDelta: -50, + FeeRefund: std.Coin{Denom: "ugnot", Amount: 500}, + PkgPath: "gno.land/r/demo", + RefundWithheld: true, + }, + }, + expectedBytes: -50, + expectedCoins: nil, + expectedHasEvents: true, + }, + { + name: "multiple deposit events", + events: []abci.Event{ + chain.StorageDepositEvent{ + BytesDelta: 100, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 1000}, + PkgPath: "gno.land/r/demo1", + }, + chain.StorageDepositEvent{ + BytesDelta: 200, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 2000}, + PkgPath: "gno.land/r/demo2", + }, + }, + expectedBytes: 300, + expectedCoins: std.Coins{std.Coin{Denom: "ugnot", Amount: 3000}}, + expectedHasEvents: true, + }, + { + name: "mixed deposit and unlock events", + events: []abci.Event{ + chain.StorageDepositEvent{ + BytesDelta: 100, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 1000}, + PkgPath: "gno.land/r/demo1", + }, + chain.StorageUnlockEvent{ + BytesDelta: -30, + FeeRefund: std.Coin{Denom: "ugnot", Amount: 300}, + PkgPath: "gno.land/r/demo2", + RefundWithheld: false, + }, + }, + expectedBytes: 70, + expectedCoins: std.Coins{std.Coin{Denom: "ugnot", Amount: 700}}, + expectedHasEvents: true, + }, + { + name: "mixed events with non-storage events", + events: []abci.Event{ + chain.Event{ + Type: "custom", + Attributes: []chain.EventAttribute{ + {Key: "key", Value: "value"}, + }, + PkgPath: "gno.land/r/demo", + }, + chain.StorageDepositEvent{ + BytesDelta: 100, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 1000}, + PkgPath: "gno.land/r/demo", + }, + }, + expectedBytes: 100, + expectedCoins: std.Coins{std.Coin{Denom: "ugnot", Amount: 1000}}, + expectedHasEvents: true, + }, + { + name: "nil events", + events: nil, + expectedBytes: 0, + expectedCoins: nil, + expectedHasEvents: false, + }, + { + name: "zero bytes delta deposit", + events: []abci.Event{ + chain.StorageDepositEvent{ + BytesDelta: 0, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 0}, + PkgPath: "gno.land/r/demo", + }, + }, + expectedBytes: 0, + expectedCoins: nil, // zero-value coins are not added to the slice + expectedHasEvents: true, + }, + { + name: "mixed withheld and non-withheld unlocks", + events: []abci.Event{ + chain.StorageUnlockEvent{ + BytesDelta: -30, + FeeRefund: std.Coin{Denom: "ugnot", Amount: 300}, + PkgPath: "gno.land/r/demo1", + RefundWithheld: false, + }, + chain.StorageUnlockEvent{ + BytesDelta: -20, + FeeRefund: std.Coin{Denom: "ugnot", Amount: 200}, + PkgPath: "gno.land/r/demo2", + RefundWithheld: true, + }, + }, + expectedBytes: -50, + expectedCoins: std.Coins{std.Coin{Denom: "ugnot", Amount: -300}}, + expectedHasEvents: true, + }, + { + name: "large byte deltas", + events: []abci.Event{ + chain.StorageDepositEvent{ + BytesDelta: 1000000, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 1000000000}, + PkgPath: "gno.land/r/demo", + }, + }, + expectedBytes: 1000000, + expectedCoins: std.Coins{std.Coin{Denom: "ugnot", Amount: 1000000000}}, + expectedHasEvents: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + bytesDelta, coinsDelta, hasEvents := GetStorageInfo(tt.events) + + assert.Equal(t, tt.expectedBytes, bytesDelta, "bytes delta mismatch") + assert.Equal(t, tt.expectedCoins, coinsDelta, "coins delta mismatch") + assert.Equal(t, tt.expectedHasEvents, hasEvents, "hasEvents mismatch") + }) + } +} + +func TestPrintTxInfo(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + tx std.Tx + res *ctypes.ResultBroadcastTxCommit + verbosity int + expectOutput []string // strings that should appear in output + }{ + { + name: "basic transaction with verbosity 0", + tx: std.Tx{ + Fee: std.Fee{ + GasFee: std.Coin{Denom: "ugnot", Amount: 1000}, + }, + }, + res: &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("transaction successful"), + }, + GasWanted: 100000, + GasUsed: gas.GasDetail{ + Total: gas.Detail{ + OperationCount: 10, + GasConsumed: 50000, + }, + }, + }, + Height: 12345, + Hash: []byte("test_hash"), + }, + verbosity: 0, + expectOutput: []string{ + "transaction successful", + "OK!", + "GAS WANTED: 100000", + "GAS USED: 50000", + "HEIGHT: 12345", + }, + }, + { + name: "transaction with storage deposit", + tx: std.Tx{ + Fee: std.Fee{ + GasFee: std.Coin{Denom: "ugnot", Amount: 1000}, + }, + }, + res: &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("success"), + Events: []abci.Event{ + chain.StorageDepositEvent{ + BytesDelta: 100, + FeeDelta: std.Coin{Denom: "ugnot", Amount: 500}, + PkgPath: "gno.land/r/demo", + }, + }, + }, + GasWanted: 100000, + GasUsed: gas.GasDetail{ + Total: gas.Detail{ + OperationCount: 10, + GasConsumed: 50000, + }, + }, + }, + Height: 12345, + Hash: []byte("test_hash"), + }, + verbosity: 0, + expectOutput: []string{ + "success", + "OK!", + "STORAGE DELTA: 100 bytes", + "STORAGE FEE:", + "500ugnot", + "TOTAL TX COST:", + "1500ugnot", + }, + }, + { + name: "transaction with storage refund", + tx: std.Tx{ + Fee: std.Fee{ + GasFee: std.Coin{Denom: "ugnot", Amount: 1000}, + }, + }, + res: &ctypes.ResultBroadcastTxCommit{ + DeliverTx: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Data: []byte("success"), + Events: []abci.Event{ + chain.StorageUnlockEvent{ + BytesDelta: -50, + FeeRefund: std.Coin{Denom: "ugnot", Amount: 300}, + PkgPath: "gno.land/r/demo", + RefundWithheld: false, + }, + }, + }, + GasWanted: 100000, + GasUsed: gas.GasDetail{ + Total: gas.Detail{ + OperationCount: 10, + GasConsumed: 50000, + }, + }, + }, + Height: 12345, + Hash: []byte("test_hash"), + }, + verbosity: 0, + expectOutput: []string{ + "success", + "OK!", + "STORAGE DELTA: -50 bytes", + "STORAGE REFUND:", + "300ugnot", + "TOTAL TX COST:", + "700ugnot", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Create test IO + io := commands.NewTestIO() + var outBuf bytes.Buffer + io.SetOut(commands.WriteNopCloser(&outBuf)) + + // Call PrintTxInfo + PrintTxInfo(tt.tx, tt.res, io, tt.verbosity) + + // Check output contains expected strings + output := outBuf.String() + for _, expected := range tt.expectOutput { + assert.Contains(t, output, expected, "output should contain %q", expected) + } + }) + } +} + +func TestPrintGasDetail(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + detail gas.GasDetail + verbosity int + expectOutput []string + notExpected []string + }{ + { + name: "verbosity 1 - shows only totals", + detail: gas.GasDetail{ + Total: gas.Detail{ + OperationCount: 100, + GasConsumed: 50000, + }, + Operations: [gas.OperationListMaxSize]gas.Detail{ + gas.OpCPUAdd: { + OperationCount: 50, + GasConsumed: 25000, + }, + gas.OpMemoryAllocPerByte: { + OperationCount: 30, + GasConsumed: 15000, + }, + gas.OpStoreReadFlat: { + OperationCount: 20, + GasConsumed: 10000, + }, + }, + }, + verbosity: 1, + expectOutput: []string{ + "GAS USED", + "Operation: 100", + "Gas: 50000", + }, + notExpected: []string{ + "CPUAdd", + "MemoryAllocPerByte", + }, + }, + { + name: "verbosity 2 - shows categories and operations", + detail: gas.GasDetail{ + Total: gas.Detail{ + OperationCount: 100, + GasConsumed: 50000, + }, + Operations: [gas.OperationListMaxSize]gas.Detail{ + gas.OpCPUAdd: { + OperationCount: 50, + GasConsumed: 25000, + }, + gas.OpMemoryAllocPerByte: { + OperationCount: 30, + GasConsumed: 15000, + }, + gas.OpStoreReadFlat: { + OperationCount: 20, + GasConsumed: 10000, + }, + }, + }, + verbosity: 2, + expectOutput: []string{ + "GAS USED", + "Operation: 100", + "Gas: 50000", + }, + }, + { + name: "verbosity 3 - shows all including zero operations", + detail: gas.GasDetail{ + Total: gas.Detail{ + OperationCount: 50, + GasConsumed: 25000, + }, + Operations: [gas.OperationListMaxSize]gas.Detail{ + gas.OpCPUAdd: { + OperationCount: 50, + GasConsumed: 25000, + }, + // Other operations have zero counts + }, + }, + verbosity: 3, + expectOutput: []string{ + "GAS USED", + "Operation: 50", + "Gas: 25000", + "Operation: 0", + "Gas: 0", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Create test IO + io := commands.NewTestIO() + var outBuf bytes.Buffer + io.SetOut(commands.WriteNopCloser(&outBuf)) + + // Call printGasDetail + printGasDetail(tt.detail, io, tt.verbosity) + + // Check output contains expected strings + output := outBuf.String() + for _, expected := range tt.expectOutput { + assert.Contains(t, output, expected, "output should contain %q", expected) + } + + // Check output does not contain unexpected strings + for _, notExpected := range tt.notExpected { + assert.NotContains(t, output, notExpected, "output should not contain %q", notExpected) + } + }) + } +} diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 6c11ed7ac6c..07ec1c3661c 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/gnolang/gno/tm2/pkg/amino" - types "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" + ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/errors" @@ -116,7 +116,7 @@ func SignAndBroadcastHandler( nameOrBech32 string, tx std.Tx, pass string, -) (*types.ResultBroadcastTxCommit, error) { +) (*ctypes.ResultBroadcastTxCommit, error) { baseopts := cfg.RootCfg txopts := cfg @@ -226,14 +226,7 @@ func ExecSignAndBroadcast( if cfg.RootCfg.OnTxSuccess != nil { cfg.RootCfg.OnTxSuccess(tx, bres) } else { - io.Println(string(bres.DeliverTx.Data)) - io.Println("OK!") - io.Println("GAS WANTED:", bres.DeliverTx.GasWanted) - io.Println("GAS USED: ", bres.DeliverTx.GasUsed) - io.Println("HEIGHT: ", bres.Height) - io.Println("EVENTS: ", string(bres.DeliverTx.EncodeEvents())) - io.Println("INFO: ", bres.DeliverTx.Info) - io.Println("TX HASH: ", base64.StdEncoding.EncodeToString(bres.Hash)) + PrintTxInfo(tx, bres, io, cfg.RootCfg.Verbosity) } return nil diff --git a/tm2/pkg/crypto/keys/client/root.go b/tm2/pkg/crypto/keys/client/root.go index 4f9521d6380..c0b724cfd3d 100644 --- a/tm2/pkg/crypto/keys/client/root.go +++ b/tm2/pkg/crypto/keys/client/root.go @@ -78,6 +78,17 @@ func (c *BaseCfg) RegisterFlags(fs *flag.FlagSet) { "suppress output during execution", ) + fs.IntVar( + &c.Verbosity, + "v", + 0, + `verbosity level for gas detail: + - 0: no detail (default) + - 1: gas by category + - 2: gas by category + operations (excluding zero count) + - 3: gas by category + all operations (including zero count)`, + ) + fs.BoolVar( &c.InsecurePasswordStdin, "insecure-password-stdin", diff --git a/tm2/pkg/gas/config.go b/tm2/pkg/gas/config.go new file mode 100644 index 00000000000..fe9ee54ed03 --- /dev/null +++ b/tm2/pkg/gas/config.go @@ -0,0 +1,200 @@ +package gas + +// Cost represents the gas cost for an operation. +type Cost float64 + +// Config defines gas costs for all operations. +type Config struct { + // Global multiplier applied to all gas consumption (default 1.0). + // Allows fractional adjustments (e.g., 0.5 to halve gas, 2.0 to double). + GlobalMultiplier float64 + + // Fixed-size array of gas costs indexed by Operation. + // Each Operation constant maps to its cost in this array. + Costs [OperationListMaxSize]Cost +} + +// GetCostForOperation returns the base cost for an operation. +func (c *Config) GetCostForOperation(op Operation) Cost { + return c.Costs[op] +} + +// defaultConfig is the default gas configuration with predefined costs. +var defaultConfig = Config{ + GlobalMultiplier: 1, + Costs: [OperationListMaxSize]Cost{ + // Store operations + OpStoreReadFlat: 1000, + OpStoreReadPerByte: 3, + OpStoreWriteFlat: 2000, + OpStoreWritePerByte: 30, + OpStoreHas: 1000, + OpStoreDelete: 1000, + OpStoreIterNextFlat: 30, + OpStoreValuePerByte: 3, + + // KVStore operations + OpKVStoreGetObjectPerByte: 16, + OpKVStoreSetObjectPerByte: 16, + OpKVStoreGetTypePerByte: 52, + OpKVStoreSetTypePerByte: 52, + OpKVStoreGetPackageRealmPerByte: 524, + OpKVStoreSetPackageRealmPerByte: 524, + OpKVStoreAddMemPackagePerByte: 8, + OpKVStoreGetMemPackagePerByte: 8, + OpKVStoreDeleteObject: 3715, + + // VM Memory operations + OpMemoryAllocPerByte: 1, + // Represents the "time unit" cost for a single garbage collection visit. + // It's similar to "CPU cycles" and is calculated based on a rough + // benchmarking results. + // TODO: more accurate benchmark. + OpMemoryGarbageCollect: 8, + + // VM CPU operations - Control operators + OpCPUInvalid: 1, + OpCPUHalt: 1, + OpCPUNoop: 1, + OpCPUExec: 25, + OpCPUPrecall: 207, + OpCPUEnterCrossing: 100, + OpCPUCall: 256, + OpCPUCallNativeBody: 424, + OpCPUDefer: 64, + OpCPUCallDeferNativeBody: 33, + OpCPUGo: 1, + OpCPUSelect: 1, + OpCPUSwitchClause: 38, + OpCPUSwitchClauseCase: 143, + OpCPUTypeSwitch: 171, + OpCPUIfCond: 38, + OpCPUPopValue: 1, + OpCPUPopResults: 1, + OpCPUPopBlock: 3, + OpCPUPopFrameAndReset: 15, + OpCPUPanic1: 121, + OpCPUPanic2: 21, + OpCPUReturn: 38, + OpCPUReturnAfterCopy: 38, + OpCPUReturnFromBlock: 36, + OpCPUReturnToBlock: 23, + + // VM CPU operations - Unary & binary operators + OpCPUUpos: 7, + OpCPUUneg: 25, + OpCPUUnot: 6, + OpCPUUxor: 14, + OpCPUUrecv: 1, + OpCPULor: 26, + OpCPULand: 24, + OpCPUEql: 160, + OpCPUNeq: 95, + OpCPULss: 13, + OpCPULeq: 19, + OpCPUGtr: 20, + OpCPUGeq: 26, + OpCPUAdd: 18, + OpCPUSub: 6, + OpCPUBor: 23, + OpCPUXor: 13, + OpCPUMul: 19, + OpCPUQuo: 16, + OpCPURem: 18, + OpCPUShl: 22, + OpCPUShr: 20, + OpCPUBand: 9, + OpCPUBandn: 15, + + // VM CPU operations - Other expression operators + OpCPUEval: 29, + OpCPUBinary1: 19, + OpCPUIndex1: 77, + OpCPUIndex2: 195, + OpCPUSelector: 32, + OpCPUSlice: 103, + OpCPUStar: 40, + OpCPURef: 125, + OpCPUTypeAssert1: 30, + OpCPUTypeAssert2: 25, + OpCPUStaticTypeOf: 100, + OpCPUCompositeLit: 50, + OpCPUArrayLit: 137, + OpCPUSliceLit: 183, + OpCPUSliceLit2: 467, + OpCPUMapLit: 475, + OpCPUStructLit: 179, + OpCPUFuncLit: 61, + OpCPUConvert: 16, + + // VM CPU operations - Type operators + OpCPUFieldType: 59, + OpCPUArrayType: 57, + OpCPUSliceType: 55, + OpCPUPointerType: 1, + OpCPUInterfaceType: 75, + OpCPUChanType: 57, + OpCPUFuncType: 81, + OpCPUMapType: 59, + OpCPUStructType: 174, + + // VM CPU operations - Statement operators + OpCPUAssign: 79, + OpCPUAddAssign: 85, + OpCPUSubAssign: 57, + OpCPUMulAssign: 55, + OpCPUQuoAssign: 50, + OpCPURemAssign: 46, + OpCPUBandAssign: 54, + OpCPUBandnAssign: 44, + OpCPUBorAssign: 55, + OpCPUXorAssign: 48, + OpCPUShlAssign: 68, + OpCPUShrAssign: 76, + OpCPUDefine: 111, + OpCPUInc: 76, + OpCPUDec: 46, + + // VM CPU operations - Decl operators + OpCPUValueDecl: 113, + OpCPUTypeDecl: 100, + + // VM CPU operations - Loop operators + OpCPUSticky: 1, + OpCPUBody: 43, + OpCPUForLoop: 27, + OpCPURangeIter: 105, + OpCPURangeIterString: 55, + OpCPURangeIterMap: 48, + OpCPURangeIterArrayPtr: 46, + OpCPUReturnCallDefers: 78, + + // Parsing operations + OpParsingToken: 1, // TODO: adjust this with benchmarks + OpParsingNesting: 1, // TODO: adjust this with benchmarks + + // Transaction operations + OpTransactionPerByte: 10, + OpTransactionSigVerifyEd25519: 590, + OpTransactionSigVerifySecp256k1: 1000, + + // Print operations + // OpNativePrintFlat is the base gas cost for the Print function. + // The actual cost is 1800, but we subtract OpCPUCallNativeBody (424), resulting in 1376. + OpNativePrintFlat: 1376, + OpNativePrintPerByte: 0.1, // 10 bytes per gas + + // Block summation operations + OpBlockGasSum: 1, + + // Test operation + OpTesting: 1, + + // All other operations default to 0 + }, +} + +// DefaultConfig returns a copy of the default configuration. +func DefaultConfig() Config { + return defaultConfig +} diff --git a/tm2/pkg/gas/errors.go b/tm2/pkg/gas/errors.go new file mode 100644 index 00000000000..f6f3ed60813 --- /dev/null +++ b/tm2/pkg/gas/errors.go @@ -0,0 +1,30 @@ +package gas + +// OutOfGasError defines an error thrown when an action results in out of gas. +type OutOfGasError struct { + Descriptor string +} + +func (oog OutOfGasError) Error() string { + return "out of gas in location: " + oog.Descriptor +} + +// OverflowError defines an error thrown when an action results in an +// integer/float overflow in gas calculation. +type OverflowError struct { + Descriptor string +} + +func (og OverflowError) Error() string { + return "gas overflow in location: " + og.Descriptor +} + +// PrecisionError defines an error thrown when an action results in a +// precision loss in gas calculation. +type PrecisionError struct { + Descriptor string +} + +func (pe PrecisionError) Error() string { + return "gas precision loss in location: " + pe.Descriptor +} diff --git a/tm2/pkg/gas/gas.go b/tm2/pkg/gas/gas.go new file mode 100644 index 00000000000..c884bb4bc5d --- /dev/null +++ b/tm2/pkg/gas/gas.go @@ -0,0 +1,88 @@ +// Package gas provides gas metering functionality for the SDK. +// +// This package centralizes: +// - The list of all operations that cost gas +// - The categorization of operations +// - The default configuration for operation costs +// +// Performance considerations: +// Gas metering occurs during critical VM operations (CPU, memory, storage access, etc.), +// so the code is optimized for write performance. To achieve maximum speed, it uses +// the simplest possible types: a fixed-size array of structs containing two int64 values. +// Category sorting and aggregation only happens when explicitly requested via +// CategoryDetails(), which occurs at far less performance-critical moments (typically +// during result display or logging). +// +// This performance optimization requires manually keeping different values in sync +// across operation.go, particularly the operation list, category definitions, and +// their relationships. +package gas + +import "fmt" + +// Gas measured by the SDK. +type Gas = int64 + +// Detail tracks both the count of operations and the total gas consumed. +type Detail struct { + OperationCount int64 + GasConsumed Gas +} + +// Add increments the operation count and adds the gas to the total. +func (d *Detail) Add(gas Gas) { + d.OperationCount++ + d.GasConsumed += gas +} + +// String returns a string representation of the Detail. +func (d Detail) String() string { + return fmt.Sprintf("Operation count: %d, Gas consumed: %d", d.OperationCount, d.GasConsumed) +} + +// GasDetail contains detailed gas consumption information. +type GasDetail struct { + // Total gas consumed globally. + Total Detail + + // Gas consumption detail per operation. + Operations [OperationListMaxSize]Detail +} + +// CategoryDetail contains gas consumption details for a specific category. +type CategoryDetail struct { + // Total gas consumed in this category. + Total Detail + + // Operation-wise gas consumption details within this category. + Operations map[Operation]Detail +} + +// CategoryDetails returns a map of CategoryDetail indexed by category name. +// NOTE: This mapping is constructed on access rather than during gas consumption +// to keep the gas metering code as fast as possible. Gas metering is called for +// every VM CPU, memory, and store operation and must be optimized for speed, +// while the access time for category details is not as critical. +func (gd GasDetail) CategoryDetails() map[string]CategoryDetail { + categoryDetails := make(map[string]CategoryDetail, len(Categories())) + + // Iterate over all defined categories. + for name, category := range Categories() { + categoryDetail := CategoryDetail{Operations: make(map[Operation]Detail, category.Size())} + + // Iterate over all operations measured in the gas detail. + for op, opDetail := range gd.Operations { + operation := Operation(op) + // If the operation belongs to the current category, accumulate its details. + if category.Contains(operation) { + categoryDetail.Total.OperationCount += opDetail.OperationCount + categoryDetail.Total.GasConsumed += opDetail.GasConsumed + categoryDetail.Operations[operation] = opDetail + } + } + + categoryDetails[name] = categoryDetail + } + + return categoryDetails +} diff --git a/tm2/pkg/gas/meter.go b/tm2/pkg/gas/meter.go new file mode 100644 index 00000000000..daeabbd42c8 --- /dev/null +++ b/tm2/pkg/gas/meter.go @@ -0,0 +1,269 @@ +package gas + +import ( + "math" + + "github.com/gnolang/gno/tm2/pkg/overflow" +) + +// Meter interface to track gas consumption. +type Meter interface { + GasConsumed() Gas + GasConsumedToLimit() Gas + GasDetail() GasDetail + Limit() Gas + Remaining() Gas + ConsumeGas(operation Operation, multiplier float64) + CalculateGasCost(operation Operation, multiplier float64) Gas + IsPastLimit() bool + IsOutOfGas() bool + Config() Config +} + +// calculateGasCost calculates the gas cost for a given operation, multiplier +// and global multiplier from the config. +func calculateGasCost(config *Config, operation Operation, multiplier float64) Gas { + // Get the operation cost from the config. + operationCost := config.Costs[operation] + + // Calculate base cost with multiplier. + basecost, ok := overflow.Mul(float64(operationCost), multiplier) + if !ok { + panic(OverflowError{operation.String()}) + } + + // Calculate total cost with global multiplier. + totalCost, ok := overflow.Mul(basecost, config.GlobalMultiplier) + if !ok { + panic(OverflowError{operation.String()}) + } + + // Since float64 can only precisely represent integers up to 2^53, + // and totalCost is calculated using 3 float64 multiplications, we need to + // check for precision loss. We consider that equal to 2^53 is already too + // large, since we can't distinguish 2^53 and 2^53 + 1. + // So the total cost of an operation (operation base cost * mult * global mult) + // must be strictly less than 2^53. + const float64PrecisionLimit = 1 << 53 + if math.Abs(totalCost) >= float64PrecisionLimit { + panic(PrecisionError{operation.String()}) + } + + // Round to the nearest whole number if there's any fractional part. + roundedCost := math.Round(totalCost) + + return Gas(roundedCost) +} + +//---------------------------------------- +// basicMeter + +type basicMeter struct { + limit Gas + config Config + consumed GasDetail +} + +// NewMeter returns a reference to a new basicMeter with the provided configuration. +func NewMeter(limit Gas, config Config) *basicMeter { + if limit < 0 { + panic("gas must not be negative") + } + if config.GlobalMultiplier <= 0 { + panic("config multiplier must be positive") + } + return &basicMeter{ + limit: limit, + config: config, + consumed: GasDetail{}, + } +} + +func (g *basicMeter) GasConsumed() Gas { + return g.consumed.Total.GasConsumed +} + +func (g *basicMeter) GasConsumedToLimit() Gas { + if g.IsPastLimit() { + return g.limit + } + return g.consumed.Total.GasConsumed +} + +func (g *basicMeter) GasDetail() GasDetail { + return g.consumed +} + +func (g *basicMeter) Limit() Gas { + return g.limit +} + +func (g *basicMeter) Remaining() Gas { + return overflow.Subp(g.Limit(), g.GasConsumedToLimit()) +} + +func (g *basicMeter) ConsumeGas(operation Operation, multiplier float64) { + gasCost := g.CalculateGasCost(operation, multiplier) + + consumed, ok := overflow.Add(g.consumed.Total.GasConsumed, gasCost) + if !ok { + panic(OverflowError{operation.String()}) + } + + // Consume gas even if out of gas. + // Corollary, call ConsumeGas after consumption. + g.consumed.Total.Add(gasCost) + g.consumed.Operations[operation].Add(gasCost) // Per-operation detail + + if consumed > g.limit { + panic(OutOfGasError{operation.String()}) + } +} + +func (g *basicMeter) CalculateGasCost(operation Operation, multiplier float64) Gas { + return calculateGasCost(&g.config, operation, multiplier) +} + +func (g *basicMeter) IsPastLimit() bool { + return g.consumed.Total.GasConsumed > g.limit +} + +func (g *basicMeter) IsOutOfGas() bool { + return g.consumed.Total.GasConsumed >= g.limit +} + +func (g *basicMeter) Config() Config { + return g.config +} + +//---------------------------------------- +// infiniteMeter + +type infiniteMeter struct { + config Config + consumed GasDetail +} + +// NewInfiniteMeter returns a reference to a new infiniteMeter with the provided configuration. +func NewInfiniteMeter(config Config) Meter { + if config.GlobalMultiplier <= 0 { + panic("config multiplier must be positive") + } + return &infiniteMeter{ + consumed: GasDetail{}, + config: config, + } +} + +func (g *infiniteMeter) GasConsumed() Gas { + return g.consumed.Total.GasConsumed +} + +func (g *infiniteMeter) GasConsumedToLimit() Gas { + return g.GasConsumed() +} + +func (g *infiniteMeter) GasDetail() GasDetail { + return g.consumed +} + +func (g *infiniteMeter) Limit() Gas { + return 0 +} + +func (g *infiniteMeter) Remaining() Gas { + return math.MaxInt64 +} + +func (g *infiniteMeter) ConsumeGas(operation Operation, multiplier float64) { + gasCost := g.CalculateGasCost(operation, multiplier) + + _, ok := overflow.Add(g.consumed.Total.GasConsumed, gasCost) + if !ok { + panic(OverflowError{operation.String()}) + } + + // Update gas detail + g.consumed.Total.Add(gasCost) + g.consumed.Operations[operation].Add(gasCost) +} + +func (g *infiniteMeter) CalculateGasCost(operation Operation, multiplier float64) Gas { + return calculateGasCost(&g.config, operation, multiplier) +} + +func (g *infiniteMeter) IsPastLimit() bool { + return false +} + +func (g *infiniteMeter) IsOutOfGas() bool { + return false +} + +func (g *infiniteMeter) Config() Config { + return g.config +} + +//---------------------------------------- +// passthroughMeter + +type passthroughMeter struct { + Base Meter + Head *basicMeter +} + +// NewPassthroughMeter has a head basicMeter, but also passes through +// consumption to a base basicMeter. Limit must be less than +// base.Remaining(). +func NewPassthroughMeter(base Meter, limit int64, config Config) passthroughMeter { + if limit < 0 { + panic("gas must not be negative") + } + // limit > base.Remaining() is not checked; so that a panic happens when + // gas is actually consumed. + return passthroughMeter{ + Base: base, + Head: NewMeter(limit, config), + } +} + +func (g passthroughMeter) GasConsumed() Gas { + return g.Head.GasConsumed() +} + +func (g passthroughMeter) GasConsumedToLimit() Gas { + return g.Head.GasConsumedToLimit() +} + +func (g passthroughMeter) GasDetail() GasDetail { + return g.Head.GasDetail() +} + +func (g passthroughMeter) Limit() Gas { + return g.Head.Limit() +} + +func (g passthroughMeter) Remaining() Gas { + return g.Head.Remaining() +} + +func (g passthroughMeter) ConsumeGas(operation Operation, multiplier float64) { + g.Base.ConsumeGas(operation, multiplier) + g.Head.ConsumeGas(operation, multiplier) +} + +func (g passthroughMeter) CalculateGasCost(operation Operation, multiplier float64) Gas { + return g.Head.CalculateGasCost(operation, multiplier) +} + +func (g passthroughMeter) IsPastLimit() bool { + return g.Head.IsPastLimit() +} + +func (g passthroughMeter) IsOutOfGas() bool { + return g.Head.IsOutOfGas() +} + +func (g passthroughMeter) Config() Config { + return g.Head.Config() +} diff --git a/tm2/pkg/gas/meter_test.go b/tm2/pkg/gas/meter_test.go new file mode 100644 index 00000000000..d4f3bd6b2aa --- /dev/null +++ b/tm2/pkg/gas/meter_test.go @@ -0,0 +1,649 @@ +package gas + +import ( + "math" + "testing" + + "github.com/gnolang/gno/tm2/pkg/overflow" + "github.com/stretchr/testify/require" +) + +func TestGasMeter(t *testing.T) { + t.Parallel() + + cases := []struct { + limit Gas + usage []Gas + }{ + {10, []Gas{1, 2, 3, 4}}, + {1000, []Gas{40, 30, 20, 10, 900}}, + {100000, []Gas{99999, 1}}, + {100000000, []Gas{50000000, 40000000, 10000000}}, + {65535, []Gas{32768, 32767}}, + {65536, []Gas{32768, 32767, 1}}, + } + + for tcnum, tc := range cases { + meter := NewMeter(tc.limit, DefaultConfig()) + used := int64(0) + + for unum, usage := range tc.usage { + used += usage + require.NotPanics(t, func() { meter.ConsumeGas(OpTesting, float64(usage)) }, "Not exceeded limit but panicked. tc #%d, usage #%d", tcnum, unum) + require.Equal(t, used, meter.GasConsumed(), "Gas consumption not match. tc #%d, usage #%d", tcnum, unum) + require.Equal(t, used, meter.GasConsumedToLimit(), "Gas consumption (to limit) not match. tc #%d, usage #%d", tcnum, unum) + require.False(t, meter.IsPastLimit(), "Not exceeded limit but got IsPastLimit() true") + if unum < len(tc.usage)-1 { + require.False(t, meter.IsOutOfGas(), "Not yet at limit but got IsOutOfGas() true") + } else { + require.True(t, meter.IsOutOfGas(), "At limit but got IsOutOfGas() false") + } + } + + require.Panics(t, func() { meter.ConsumeGas(OpTesting, 1) }, "Exceeded but not panicked. tc #%d", tcnum) + require.Equal(t, meter.GasConsumedToLimit(), meter.Limit(), "Gas consumption (to limit) not match limit") + require.Equal(t, meter.GasConsumed(), meter.Limit()+1, "Gas consumption not match limit+1") + } +} + +func TestAddUint64Overflow(t *testing.T) { + t.Parallel() + + testCases := []struct { + a, b int64 + result int64 + overflow bool + }{ + {0, 0, 0, false}, + {100, 100, 200, false}, + {math.MaxInt64 / 2, math.MaxInt64/2 + 1, math.MaxInt64, false}, + {math.MaxInt64 / 2, math.MaxInt64/2 + 2, math.MinInt64, true}, + } + + for i, tc := range testCases { + res, ok := overflow.Add(tc.a, tc.b) + overflow := !ok + require.Equal( + t, tc.overflow, overflow, + "invalid overflow result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, + ) + require.Equal( + t, tc.result, res, + "invalid int64 result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, + ) + } +} + +func TestGasDetailTracking(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + meter := NewMeter(1000000, config) + + // Consume gas for different operations + // Store operations + meter.ConsumeGas(OpStoreReadFlat, 1) + meter.ConsumeGas(OpStoreReadFlat, 1) + meter.ConsumeGas(OpStoreWriteFlat, 1) + + // CPU operations + meter.ConsumeGas(OpCPUAdd, 1) + meter.ConsumeGas(OpCPUAdd, 1) + meter.ConsumeGas(OpCPUMul, 1) + + // Memory operations + meter.ConsumeGas(OpMemoryAllocPerByte, 10) + + // Get gas detail + detail := meter.GasDetail() + + // Verify total consumed + require.Equal(t, meter.GasConsumed(), detail.Total.GasConsumed, "Total consumed should match gas consumed") + + // Verify operation counts + require.Equal(t, int64(2), detail.Operations[OpStoreReadFlat].OperationCount, "OpStoreReadFlat should be called 2 times") + require.Equal(t, int64(1), detail.Operations[OpStoreWriteFlat].OperationCount, "OpStoreWriteFlat should be called 1 time") + require.Equal(t, int64(2), detail.Operations[OpCPUAdd].OperationCount, "OpCPUAdd should be called 2 times") + require.Equal(t, int64(1), detail.Operations[OpCPUMul].OperationCount, "OpCPUMul should be called 1 time") + require.Equal(t, int64(1), detail.Operations[OpMemoryAllocPerByte].OperationCount, "OpMemoryAllocPerByte should be called 1 time") + + // Verify operation gas totals + expectedStoreReadFlatGas := config.Costs[OpStoreReadFlat] * 2 + require.Equal(t, Gas(expectedStoreReadFlatGas), detail.Operations[OpStoreReadFlat].GasConsumed, "OpStoreReadFlat total gas should match") + + expectedCPUAddGas := config.Costs[OpCPUAdd] * 2 + require.Equal(t, Gas(expectedCPUAddGas), detail.Operations[OpCPUAdd].GasConsumed, "OpCPUAdd total gas should match") + + // Get category details + categoryDetails := detail.CategoryDetails() + + // Verify category totals + require.Greater(t, categoryDetails["Store"].Total.GasConsumed, Gas(0), "Store category should have gas consumed") + require.Greater(t, categoryDetails["CPU"].Total.GasConsumed, Gas(0), "CPU category should have gas consumed") + require.Greater(t, categoryDetails["Memory"].Total.GasConsumed, Gas(0), "Memory category should have gas consumed") + + // Verify category gas equals sum of operations in that category + expectedStoreGas := detail.Operations[OpStoreReadFlat].GasConsumed + detail.Operations[OpStoreWriteFlat].GasConsumed + require.Equal(t, expectedStoreGas, categoryDetails["Store"].Total.GasConsumed, "Store category gas should match sum of store operations") + + expectedCPUGas := detail.Operations[OpCPUAdd].GasConsumed + detail.Operations[OpCPUMul].GasConsumed + require.Equal(t, expectedCPUGas, categoryDetails["CPU"].Total.GasConsumed, "CPU category gas should match sum of CPU operations") + + expectedMemoryGas := detail.Operations[OpMemoryAllocPerByte].GasConsumed + require.Equal(t, expectedMemoryGas, categoryDetails["Memory"].Total.GasConsumed, "Memory category gas should match memory operation") + + // Verify total equals sum of all categories + totalFromCategories := Gas(0) + for _, category := range categoryDetails { + totalFromCategories += category.Total.GasConsumed + } + require.Equal(t, detail.Total.GasConsumed, totalFromCategories, "Total consumed should equal sum of all categories") +} + +func TestGasDetailInfiniteMeter(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + meter := NewInfiniteMeter(config) + + // Consume gas for different operations + meter.ConsumeGas(OpStoreReadFlat, 1) + meter.ConsumeGas(OpCPUAdd, 1) + + // Get gas detail + detail := meter.GasDetail() + + // Verify total consumed + require.Equal(t, meter.GasConsumed(), detail.Total.GasConsumed, "Total consumed should match gas consumed") + + // Verify operation counts + require.Equal(t, int64(1), detail.Operations[OpStoreReadFlat].OperationCount, "OpStoreReadFlat should be called 1 time") + require.Equal(t, int64(1), detail.Operations[OpCPUAdd].OperationCount, "OpCPUAdd should be called 1 time") + + // Get category details + categoryDetails := detail.CategoryDetails() + + // Verify category totals + require.Greater(t, categoryDetails["Store"].Total.GasConsumed, Gas(0), "Store category should have gas consumed") + require.Greater(t, categoryDetails["CPU"].Total.GasConsumed, Gas(0), "CPU category should have gas consumed") +} + +func TestGasDetailPassthroughMeter(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + baseMeter := NewMeter(1000000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 500000, config) + + // Consume gas through passthrough meter + passthroughMeter.ConsumeGas(OpStoreReadFlat, 1) + passthroughMeter.ConsumeGas(OpCPUAdd, 1) + + // Get gas detail from passthrough meter (should return Head's detail) + detail := passthroughMeter.GasDetail() + + // Verify total consumed + require.Equal(t, passthroughMeter.GasConsumed(), detail.Total.GasConsumed, "Total consumed should match gas consumed") + + // Verify operation counts + require.Equal(t, int64(1), detail.Operations[OpStoreReadFlat].OperationCount, "OpStoreReadFlat should be called 1 time") + require.Equal(t, int64(1), detail.Operations[OpCPUAdd].OperationCount, "OpCPUAdd should be called 1 time") + + // Get category details + categoryDetails := detail.CategoryDetails() + + // Verify category totals + require.Greater(t, categoryDetails["Store"].Total.GasConsumed, Gas(0), "Store category should have gas consumed") + require.Greater(t, categoryDetails["CPU"].Total.GasConsumed, Gas(0), "CPU category should have gas consumed") +} + +func TestMeterPanics(t *testing.T) { + t.Parallel() + + const maxSafeFloat64 float64 = (1 << 53) - 1.0 + config := DefaultConfig() + + t.Run("negative gas limit", func(t *testing.T) { + t.Parallel() + + require.Panics(t, func() { + NewMeter(-1, config) + }, "Should panic with negative gas limit") + }) + + t.Run("zero multiplier", func(t *testing.T) { + t.Parallel() + + invalidConfig := config + invalidConfig.GlobalMultiplier = 0 + require.Panics(t, func() { + NewMeter(1000, invalidConfig) + }, "Should panic with zero multiplier") + }) + + t.Run("negative multiplier", func(t *testing.T) { + t.Parallel() + + invalidConfig := config + invalidConfig.GlobalMultiplier = -1 + require.Panics(t, func() { + NewMeter(1000, invalidConfig) + }, "Should panic with negative multiplier") + }) + + t.Run("infinite meter zero multiplier", func(t *testing.T) { + t.Parallel() + + invalidConfig := config + invalidConfig.GlobalMultiplier = 0 + require.Panics(t, func() { + NewInfiniteMeter(invalidConfig) + }, "Should panic with zero multiplier") + }) + + t.Run("infinite meter negative multiplier", func(t *testing.T) { + t.Parallel() + + invalidConfig := config + invalidConfig.GlobalMultiplier = -1 + require.Panics(t, func() { + NewInfiniteMeter(invalidConfig) + }, "Should panic with negative multiplier") + }) + + t.Run("passthrough negative limit", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(1000, config) + require.Panics(t, func() { + NewPassthroughMeter(baseMeter, -1, config) + }, "Should panic with negative limit") + }) + + t.Run("overflow in gas consumption", func(t *testing.T) { + t.Parallel() + + meter := NewMeter(math.MaxInt64, config) + + // Consume gas multiple times to get close to MaxInt64 + iterations := math.MaxInt64 / int64(maxSafeFloat64) + for range iterations { + meter.ConsumeGas(OpTesting, maxSafeFloat64) + } + + // Now we're close enough to MaxInt64, consuming another time will overflow. + require.Panics(t, func() { + meter.ConsumeGas(OpTesting, maxSafeFloat64) + }, "Should panic on overflow") + }) + + t.Run("overflow in gas consumption (infinite meter)", func(t *testing.T) { + t.Parallel() + + meter := NewInfiniteMeter(config) + + // Consume gas multiple times to get close to MaxInt64 + iterations := math.MaxInt64 / int64(maxSafeFloat64) + for range iterations { + meter.ConsumeGas(OpTesting, maxSafeFloat64) + } + + // Now we're close enough to MaxInt64, consuming another time will overflow. + require.Panics(t, func() { + meter.ConsumeGas(OpTesting, maxSafeFloat64) + }, "Should panic on overflow") + }) + + t.Run("overflow in calculateGasCost", func(t *testing.T) { + t.Parallel() + + hugeMultiplierConfig := config + hugeMultiplierConfig.GlobalMultiplier = math.MaxFloat64 + hugeMultiplierConfig.Costs[OpTesting] = math.MaxFloat64 + meter := NewMeter(math.MaxInt64, hugeMultiplierConfig) + require.Panics(t, func() { + meter.ConsumeGas(OpTesting, math.MaxFloat64) + }, "Should panic on gas calculation overflow") + }) + + t.Run("precision error in calculateGasCost", func(t *testing.T) { + t.Parallel() + + meter := NewMeter(math.MaxInt64, config) + + require.Panics(t, func() { + meter.ConsumeGas(OpTesting, maxSafeFloat64+1.0) + }, "Should panic on gas calculation precision loss") + + require.NotPanics(t, func() { + meter.ConsumeGas(OpTesting, maxSafeFloat64) + }, "Should not panic on gas calculation without precision loss") + }) +} + +func TestErrorMessages(t *testing.T) { + t.Parallel() + + t.Run("OutOfGasError", func(t *testing.T) { + t.Parallel() + + err := OutOfGasError{"test-operation"} + expected := "out of gas in location: test-operation" + require.Equal(t, expected, err.Error(), "OutOfGasError message should match") + }) + + t.Run("OverflowError", func(t *testing.T) { + t.Parallel() + + err := OverflowError{"test-overflow"} + expected := "gas overflow in location: test-overflow" + require.Equal(t, expected, err.Error(), "OverflowError message should match") + }) + + t.Run("PrecisionError", func(t *testing.T) { + t.Parallel() + + err := PrecisionError{"test-precision"} + expected := "gas precision loss in location: test-precision" + require.Equal(t, expected, err.Error(), "PrecisionError message should match") + }) +} + +func TestUtilityMethods(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + + t.Run("Config getter", func(t *testing.T) { + t.Parallel() + + meter := NewMeter(1000, config) + retrievedConfig := meter.Config() + require.Equal(t, config.GlobalMultiplier, retrievedConfig.GlobalMultiplier, "Config should match") + }) + + t.Run("Remaining gas", func(t *testing.T) { + t.Parallel() + + meter := NewMeter(1000, config) + require.Equal(t, Gas(1000), meter.Remaining(), "Initial remaining should equal limit") + + meter.ConsumeGas(OpTesting, 100) + require.Equal(t, Gas(900), meter.Remaining(), "Remaining should decrease after consumption") + }) + + t.Run("GetCostForOperation", func(t *testing.T) { + t.Parallel() + + cost := config.GetCostForOperation(OpStoreReadFlat) + require.Equal(t, config.Costs[OpStoreReadFlat], cost, "GetCostForOperation should return correct cost") + }) + + t.Run("Detail String", func(t *testing.T) { + t.Parallel() + + detail := Detail{OperationCount: 5, GasConsumed: 100} + str := detail.String() + require.Contains(t, str, "5", "String should contain operation count") + require.Contains(t, str, "100", "String should contain gas consumed") + }) + + t.Run("Operation String", func(t *testing.T) { + t.Parallel() + + require.Equal(t, "StoreReadFlat", OpStoreReadFlat.String(), "Operation name should match") + require.Equal(t, "CPUAdd", OpCPUAdd.String(), "Operation name should match") + }) + + t.Run("Unknown operation String", func(t *testing.T) { + t.Parallel() + + // Create an operation with a value that has no name + unknownOp := Operation(100) // Assuming 100 has no name defined + // First verify it has no name in the array + if operationNames[unknownOp] == "" { + require.Equal(t, "UnknownOperation", unknownOp.String(), "Unknown operation should return 'UnknownOperation'") + } + }) +} + +func TestInfiniteMeterBehavior(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + meter := NewInfiniteMeter(config) + + t.Run("never runs out of gas", func(t *testing.T) { + t.Parallel() + + // Consume a huge amount of gas + for i := 0; i < 1000; i++ { + require.NotPanics(t, func() { + meter.ConsumeGas(OpTesting, 1000000) + }, "Infinite meter should never panic") + } + }) + + t.Run("IsPastLimit always false", func(t *testing.T) { + t.Parallel() + + meter.ConsumeGas(OpTesting, 999999999) + require.False(t, meter.IsPastLimit(), "Infinite meter should never be past limit") + }) + + t.Run("IsOutOfGas always false", func(t *testing.T) { + t.Parallel() + + require.False(t, meter.IsOutOfGas(), "Infinite meter should never be out of gas") + }) + + t.Run("Limit is zero", func(t *testing.T) { + t.Parallel() + + require.Equal(t, Gas(0), meter.Limit(), "Infinite meter limit should be 0") + }) + + t.Run("Remaining is MaxInt64", func(t *testing.T) { + t.Parallel() + + require.Equal(t, Gas(math.MaxInt64), meter.Remaining(), "Infinite meter remaining should be MaxInt64") + }) + + t.Run("GasConsumedToLimit equals GasConsumed", func(t *testing.T) { + t.Parallel() + + consumed := meter.GasConsumed() + require.Equal(t, consumed, meter.GasConsumedToLimit(), "GasConsumedToLimit should equal GasConsumed") + }) + + t.Run("Config getter", func(t *testing.T) { + t.Parallel() + + retrievedConfig := meter.Config() + require.Equal(t, config.GlobalMultiplier, retrievedConfig.GlobalMultiplier, "Config should match") + }) +} + +func TestPassthroughMeterLimits(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + + t.Run("respects head limit", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + // Consume up to head limit + passthroughMeter.ConsumeGas(OpTesting, 5000) + require.True(t, passthroughMeter.IsOutOfGas(), "Should be out of gas at head limit") + + // Should panic on next consume + require.Panics(t, func() { + passthroughMeter.ConsumeGas(OpTesting, 1) + }, "Should panic when exceeding head limit") + }) + + t.Run("delegates to base meter", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + passthroughMeter.ConsumeGas(OpTesting, 1000) + + // Both meters should have consumed the same gas + require.Equal(t, Gas(1000), passthroughMeter.GasConsumed(), "Passthrough should track consumption") + require.Equal(t, Gas(1000), baseMeter.GasConsumed(), "Base should track consumption") + }) + + t.Run("GasConsumedToLimit", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + passthroughMeter.ConsumeGas(OpTesting, 3000) + require.Equal(t, Gas(3000), passthroughMeter.GasConsumedToLimit(), "GasConsumedToLimit should match") + }) + + t.Run("Limit returns head limit", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + require.Equal(t, Gas(5000), passthroughMeter.Limit(), "Should return head limit") + }) + + t.Run("Remaining", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + require.Equal(t, Gas(5000), passthroughMeter.Remaining(), "Initial remaining should equal head limit") + passthroughMeter.ConsumeGas(OpTesting, 2000) + require.Equal(t, Gas(3000), passthroughMeter.Remaining(), "Remaining should decrease") + }) + + t.Run("CalculateGasCost", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + cost := passthroughMeter.CalculateGasCost(OpTesting, 10) + expectedCost := Gas(10) // OpTesting has cost 1 in default config + require.Equal(t, expectedCost, cost, "CalculateGasCost should work correctly") + }) + + t.Run("IsPastLimit", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + require.False(t, passthroughMeter.IsPastLimit(), "Should not be past limit initially") + passthroughMeter.ConsumeGas(OpTesting, 5000) + require.False(t, passthroughMeter.IsPastLimit(), "Should not be past limit at exact limit") + + require.Panics(t, func() { + passthroughMeter.ConsumeGas(OpTesting, 1) + }) + require.True(t, passthroughMeter.IsPastLimit(), "Should be past limit after exceeding") + }) + + t.Run("IsOutOfGas", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + require.False(t, passthroughMeter.IsOutOfGas(), "Should not be out of gas initially") + passthroughMeter.ConsumeGas(OpTesting, 5000) + require.True(t, passthroughMeter.IsOutOfGas(), "Should be out of gas at limit") + }) + + t.Run("Config getter", func(t *testing.T) { + t.Parallel() + + baseMeter := NewMeter(10000, config) + passthroughMeter := NewPassthroughMeter(baseMeter, 5000, config) + + retrievedConfig := passthroughMeter.Config() + require.Equal(t, config.GlobalMultiplier, retrievedConfig.GlobalMultiplier, "Config should match") + }) +} + +func TestGlobalMultiplier(t *testing.T) { + t.Parallel() + + t.Run("multiplier 1.0", func(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + config.GlobalMultiplier = 1.0 + config.Costs[OpTesting] = 100 + meter := NewMeter(10000, config) + + meter.ConsumeGas(OpTesting, 1) + require.Equal(t, Gas(100), meter.GasConsumed(), "Should consume exact cost with multiplier 1.0") + }) + + t.Run("multiplier 0.5", func(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + config.GlobalMultiplier = 0.5 + config.Costs[OpTesting] = 100 + meter := NewMeter(10000, config) + + meter.ConsumeGas(OpTesting, 1) + require.Equal(t, Gas(50), meter.GasConsumed(), "Should halve gas with multiplier 0.5") + }) + + t.Run("multiplier 2.0", func(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + config.GlobalMultiplier = 2.0 + config.Costs[OpTesting] = 100 + meter := NewMeter(10000, config) + + meter.ConsumeGas(OpTesting, 1) + require.Equal(t, Gas(200), meter.GasConsumed(), "Should double gas with multiplier 2.0") + }) + + t.Run("multiplier with operation multiplier", func(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + config.GlobalMultiplier = 2.0 + config.Costs[OpTesting] = 100 + meter := NewMeter(10000, config) + + // Operation multiplier 3, global multiplier 2 + // Expected: 100 * 3 * 2 = 600 + meter.ConsumeGas(OpTesting, 3) + require.Equal(t, Gas(600), meter.GasConsumed(), "Should apply both multipliers") + }) + + t.Run("fractional result rounding", func(t *testing.T) { + t.Parallel() + + config := DefaultConfig() + config.GlobalMultiplier = 1.5 + config.Costs[OpTesting] = 10 + meter := NewMeter(10000, config) + + // 10 * 1 * 1.5 = 15.0 (rounds to 15) + meter.ConsumeGas(OpTesting, 1) + require.Equal(t, Gas(15), meter.GasConsumed(), "Should round correctly") + + // 10 * 1 * 1.5 = 15.0, so total is 30 + meter.ConsumeGas(OpTesting, 1) + require.Equal(t, Gas(30), meter.GasConsumed(), "Should round correctly on second call") + }) +} diff --git a/tm2/pkg/gas/operation.go b/tm2/pkg/gas/operation.go new file mode 100644 index 00000000000..af5a8b36ca7 --- /dev/null +++ b/tm2/pkg/gas/operation.go @@ -0,0 +1,385 @@ +package gas + +import "math" + +// Operation is a typed identifier for gas operations. +type Operation uint8 + +// Bounds for Operation type. +const ( + OperationListMaxSize = math.MaxUint8 + 1 + OperationMaxValue = math.MaxUint8 +) + +// Operation enumeration. +// NOTE: Keep in sync with operationNames map and Category definitions. +const ( + // Store operations + OpStoreReadFlat Operation = iota + OpStoreReadPerByte + OpStoreWriteFlat + OpStoreWritePerByte + OpStoreHas + OpStoreDelete + OpStoreIterNextFlat + OpStoreValuePerByte + + // KVStore operations + OpKVStoreGetObjectPerByte + OpKVStoreSetObjectPerByte + OpKVStoreGetTypePerByte + OpKVStoreSetTypePerByte + OpKVStoreGetPackageRealmPerByte + OpKVStoreSetPackageRealmPerByte + OpKVStoreAddMemPackagePerByte + OpKVStoreGetMemPackagePerByte + OpKVStoreDeleteObject + + // VM Memory operations + OpMemoryAllocPerByte + OpMemoryGarbageCollect + + // VM CPU operations - Control operators + OpCPUInvalid + OpCPUHalt + OpCPUNoop + OpCPUExec + OpCPUPrecall + OpCPUEnterCrossing + OpCPUCall + OpCPUCallNativeBody + OpCPUDefer + OpCPUCallDeferNativeBody + OpCPUGo + OpCPUSelect + OpCPUSwitchClause + OpCPUSwitchClauseCase + OpCPUTypeSwitch + OpCPUIfCond + OpCPUPopValue + OpCPUPopResults + OpCPUPopBlock + OpCPUPopFrameAndReset + OpCPUPanic1 + OpCPUPanic2 + OpCPUReturn + OpCPUReturnAfterCopy + OpCPUReturnFromBlock + OpCPUReturnToBlock + + // VM CPU operations - Unary & binary operators + OpCPUUpos + OpCPUUneg + OpCPUUnot + OpCPUUxor + OpCPUUrecv + OpCPULor + OpCPULand + OpCPUEql + OpCPUNeq + OpCPULss + OpCPULeq + OpCPUGtr + OpCPUGeq + OpCPUAdd + OpCPUSub + OpCPUBor + OpCPUXor + OpCPUMul + OpCPUQuo + OpCPURem + OpCPUShl + OpCPUShr + OpCPUBand + OpCPUBandn + + // VM CPU operations - Other expression operators + OpCPUEval + OpCPUBinary1 + OpCPUIndex1 + OpCPUIndex2 + OpCPUSelector + OpCPUSlice + OpCPUStar + OpCPURef + OpCPUTypeAssert1 + OpCPUTypeAssert2 + OpCPUStaticTypeOf + OpCPUCompositeLit + OpCPUArrayLit + OpCPUSliceLit + OpCPUSliceLit2 + OpCPUMapLit + OpCPUStructLit + OpCPUFuncLit + OpCPUConvert + + // VM CPU operations - Type operators + OpCPUFieldType + OpCPUArrayType + OpCPUSliceType + OpCPUPointerType + OpCPUInterfaceType + OpCPUChanType + OpCPUFuncType + OpCPUMapType + OpCPUStructType + + // VM CPU operations - Statement operators + OpCPUAssign + OpCPUAddAssign + OpCPUSubAssign + OpCPUMulAssign + OpCPUQuoAssign + OpCPURemAssign + OpCPUBandAssign + OpCPUBandnAssign + OpCPUBorAssign + OpCPUXorAssign + OpCPUShlAssign + OpCPUShrAssign + OpCPUDefine + OpCPUInc + OpCPUDec + + // VM CPU operations - Decl operators + OpCPUValueDecl + OpCPUTypeDecl + + // VM CPU operations - Loop operators + OpCPUSticky + OpCPUBody + OpCPUForLoop + OpCPURangeIter + OpCPURangeIterString + OpCPURangeIterMap + OpCPURangeIterArrayPtr + OpCPUReturnCallDefers + + // Parsing operations + OpParsingToken + OpParsingNesting + + // Transaction operations + OpTransactionPerByte + OpTransactionSigVerifyEd25519 + OpTransactionSigVerifySecp256k1 + + // Native operations + OpNativePrintFlat + OpNativePrintPerByte + + // Block summation operations + OpBlockGasSum + + // Test operation + OpTesting = OperationMaxValue // Last value reserved for testing purposes +) + +// operationNames maps Operation values to their string names. +// NOTE: Keep in sync with Operation enumeration and Category definitions. +var operationNames = [OperationListMaxSize]string{ + // Store operations + OpStoreReadFlat: "StoreReadFlat", + OpStoreReadPerByte: "StoreReadPerByte", + OpStoreWriteFlat: "StoreWriteFlat", + OpStoreWritePerByte: "StoreWritePerByte", + OpStoreHas: "StoreHas", + OpStoreDelete: "StoreDelete", + OpStoreIterNextFlat: "StoreIterNextFlat", + OpStoreValuePerByte: "StoreValuePerByte", + + // KVStore operations + OpKVStoreGetObjectPerByte: "KVStoreGetObjectPerByte", + OpKVStoreSetObjectPerByte: "KVStoreSetObjectPerByte", + OpKVStoreGetTypePerByte: "KVStoreGetTypePerByte", + OpKVStoreSetTypePerByte: "KVStoreSetTypePerByte", + OpKVStoreGetPackageRealmPerByte: "KVStoreGetPackageRealmPerByte", + OpKVStoreSetPackageRealmPerByte: "KVStoreSetPackageRealmPerByte", + OpKVStoreAddMemPackagePerByte: "KVStoreAddMemPackagePerByte", + OpKVStoreGetMemPackagePerByte: "KVStoreGetMemPackagePerByte", + OpKVStoreDeleteObject: "KVStoreDeleteObject", + + // VM Memory operations + OpMemoryAllocPerByte: "MemoryAllocPerByte", + OpMemoryGarbageCollect: "MemoryGarbageCollect", + + // VM CPU operations - Control operators + OpCPUInvalid: "CPUInvalid", + OpCPUHalt: "CPUHalt", + OpCPUNoop: "CPUNoop", + OpCPUExec: "CPUExec", + OpCPUPrecall: "CPUPrecall", + OpCPUEnterCrossing: "CPUEnterCrossing", + OpCPUCall: "CPUCall", + OpCPUCallNativeBody: "CPUCallNativeBody", + OpCPUDefer: "CPUDefer", + OpCPUCallDeferNativeBody: "CPUCallDeferNativeBody", + OpCPUGo: "CPUGo", + OpCPUSelect: "CPUSelect", + OpCPUSwitchClause: "CPUSwitchClause", + OpCPUSwitchClauseCase: "CPUSwitchClauseCase", + OpCPUTypeSwitch: "CPUTypeSwitch", + OpCPUIfCond: "CPUIfCond", + OpCPUPopValue: "CPUPopValue", + OpCPUPopResults: "CPUPopResults", + OpCPUPopBlock: "CPUPopBlock", + OpCPUPopFrameAndReset: "CPUPopFrameAndReset", + OpCPUPanic1: "CPUPanic1", + OpCPUPanic2: "CPUPanic2", + OpCPUReturn: "CPUReturn", + OpCPUReturnAfterCopy: "CPUReturnAfterCopy", + OpCPUReturnFromBlock: "CPUReturnFromBlock", + OpCPUReturnToBlock: "CPUReturnToBlock", + + // VM CPU operations - Unary & binary operators + OpCPUUpos: "CPUUpos", + OpCPUUneg: "CPUUneg", + OpCPUUnot: "CPUUnot", + OpCPUUxor: "CPUUxor", + OpCPUUrecv: "CPUUrecv", + OpCPULor: "CPULor", + OpCPULand: "CPULand", + OpCPUEql: "CPUEql", + OpCPUNeq: "CPUNeq", + OpCPULss: "CPULss", + OpCPULeq: "CPULeq", + OpCPUGtr: "CPUGtr", + OpCPUGeq: "CPUGeq", + OpCPUAdd: "CPUAdd", + OpCPUSub: "CPUSub", + OpCPUBor: "CPUBor", + OpCPUXor: "CPUXor", + OpCPUMul: "CPUMul", + OpCPUQuo: "CPUQuo", + OpCPURem: "CPURem", + OpCPUShl: "CPUShl", + OpCPUShr: "CPUShr", + OpCPUBand: "CPUBand", + OpCPUBandn: "CPUBandn", + + // VM CPU operations - Other expression operators + OpCPUEval: "CPUEval", + OpCPUBinary1: "CPUBinary1", + OpCPUIndex1: "CPUIndex1", + OpCPUIndex2: "CPUIndex2", + OpCPUSelector: "CPUSelector", + OpCPUSlice: "CPUSlice", + OpCPUStar: "CPUStar", + OpCPURef: "CPURef", + OpCPUTypeAssert1: "CPUTypeAssert1", + OpCPUTypeAssert2: "CPUTypeAssert2", + OpCPUStaticTypeOf: "CPUStaticTypeOf", + OpCPUCompositeLit: "CPUCompositeLit", + OpCPUArrayLit: "CPUArrayLit", + OpCPUSliceLit: "CPUSliceLit", + OpCPUSliceLit2: "CPUSliceLit2", + OpCPUMapLit: "CPUMapLit", + OpCPUStructLit: "CPUStructLit", + OpCPUFuncLit: "CPUFuncLit", + OpCPUConvert: "CPUConvert", + + // VM CPU operations - Type operators + OpCPUFieldType: "CPUFieldType", + OpCPUArrayType: "CPUArrayType", + OpCPUSliceType: "CPUSliceType", + OpCPUPointerType: "CPUPointerType", + OpCPUInterfaceType: "CPUInterfaceType", + OpCPUChanType: "CPUChanType", + OpCPUFuncType: "CPUFuncType", + OpCPUMapType: "CPUMapType", + OpCPUStructType: "CPUStructType", + + // VM CPU operations - Statement operators + OpCPUAssign: "CPUAssign", + OpCPUAddAssign: "CPUAddAssign", + OpCPUSubAssign: "CPUSubAssign", + OpCPUMulAssign: "CPUMulAssign", + OpCPUQuoAssign: "CPUQuoAssign", + OpCPURemAssign: "CPURemAssign", + OpCPUBandAssign: "CPUBandAssign", + OpCPUBandnAssign: "CPUBandnAssign", + OpCPUBorAssign: "CPUBorAssign", + OpCPUXorAssign: "CPUXorAssign", + OpCPUShlAssign: "CPUShlAssign", + OpCPUShrAssign: "CPUShrAssign", + OpCPUDefine: "CPUDefine", + OpCPUInc: "CPUInc", + OpCPUDec: "CPUDec", + + // VM CPU operations - Decl operators + OpCPUValueDecl: "CPUValueDecl", + OpCPUTypeDecl: "CPUTypeDecl", + + // VM CPU operations - Loop operators + OpCPUSticky: "CPUSticky", + OpCPUBody: "CPUBody", + OpCPUForLoop: "CPUForLoop", + OpCPURangeIter: "CPURangeIter", + OpCPURangeIterString: "CPURangeIterString", + OpCPURangeIterMap: "CPURangeIterMap", + OpCPURangeIterArrayPtr: "CPURangeIterArrayPtr", + OpCPUReturnCallDefers: "CPUReturnCallDefers", + + // Parsing operations + OpParsingToken: "ParsingToken", + OpParsingNesting: "ParsingNesting", + + // Transaction operations + OpTransactionPerByte: "TransactionPerByte", + OpTransactionSigVerifyEd25519: "TransactionSigVerifyEd25519", + OpTransactionSigVerifySecp256k1: "TxansactionSigVerifySecp256k1", + + // Native operations + OpNativePrintFlat: "NativePrintFlat", + OpNativePrintPerByte: "NativePrintPerByte", + + // Block summation operations + OpBlockGasSum: "BlockGasSum", + + // Test operation + OpTesting: "Testing", +} + +// String returns the name of the operation for logging purposes. +func (o Operation) String() string { + if name := operationNames[o]; name != "" { + return name + } + return "UnknownOperation" +} + +// Category represents a range of operations grouped under a common category. +type Category struct { + From Operation + To Operation +} + +// Contains checks if the given operation belongs to the category. +func (c Category) Contains(op Operation) bool { + return op >= c.From && op <= c.To +} + +// Size returns the number of operations in the category. +func (c Category) Size() uint8 { + return uint8(c.To - c.From + 1) +} + +// categories is the map of all operation categories. +// NOTE: Keep in sync with Operation enumeration and operationNames map. +var categories = map[string]Category{ + "Store": {OpStoreReadFlat, OpStoreValuePerByte}, + "KVStore": {OpKVStoreGetObjectPerByte, OpKVStoreDeleteObject}, + "Memory": {OpMemoryAllocPerByte, OpMemoryGarbageCollect}, + "CPU": {OpCPUInvalid, OpCPUReturnCallDefers}, + "Parsing": {OpParsingToken, OpParsingNesting}, + "Transaction": {OpTransactionPerByte, OpTransactionSigVerifySecp256k1}, + "Native": {OpNativePrintFlat, OpNativePrintPerByte}, + "BlockGas": {OpBlockGasSum, OpBlockGasSum}, + "Testing": {OpTesting, OpTesting}, +} + +// Categories returns the map of all defined operation categories. +func Categories() map[string]Category { + return categories +} diff --git a/tm2/pkg/overflow/overflow.go b/tm2/pkg/overflow/overflow.go index 2322a7b20e8..d61a3b16606 100644 --- a/tm2/pkg/overflow/overflow.go +++ b/tm2/pkg/overflow/overflow.go @@ -1,49 +1,92 @@ -// Package overflow offers overflow-checked integer arithmetic operations -// for all signed and unsigned integer types. Each of the operations returns a -// result,bool combination. +// Package overflow offers overflow-checked arithmetic operations +// for all signed and unsigned integer types, as well as floating-point types. +// Each of the operations returns a result,bool combination. // // The functions support all types convertible to unsigned or signed integer -// types. The modulo % operation is not present, as it is always safe. +// types, and float32/float64 types. The modulo % operation is not present, +// as it is always safe. package overflow -// Number is a type constraint for all integer values, signed and unsigned. +import "math" + +// Number is a type constraint for all integer and floating-point values. type Number interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | - ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~float32 | ~float64 +} + +// isValidFloat checks if a float value is finite (not Inf or NaN). +// This is used to detect float overflow, which behaves differently from integer overflow: +// integers wrap around on overflow, while floats become ±Inf or NaN. +func isValidFloat[N Number](n N) bool { + switch v := any(n).(type) { + case float32: + return !math.IsInf(float64(v), 0) && !math.IsNaN(float64(v)) + case float64: + return !math.IsInf(v, 0) && !math.IsNaN(v) + default: + // For integers, always return true as they use different overflow detection + return true + } } // Add sums two numbers, returning the result and a boolean status. +// For integers, detects wrap-around overflow. +// For floats, detects when the result becomes Inf or NaN. func Add[N Number](a, b N) (N, bool) { c := a + b - return c, (c > a) == (b > 0) + switch any(c).(type) { + case float32, float64: + return c, isValidFloat(c) + default: + return c, (c > a) == (b > 0) + } } // Sub returns the difference of two numbers and a boolean status. +// For integers, detects wrap-around overflow. +// For floats, detects when the result becomes Inf or NaN. func Sub[N Number](a, b N) (N, bool) { c := a - b - return c, (c < a) == (b > 0) + switch any(c).(type) { + case float32, float64: + return c, isValidFloat(c) + default: + return c, (c < a) == (b > 0) + } } // Mul returns the multiplication of two numbers and a boolean status. +// For integers, detects wrap-around overflow. +// For floats, detects when the result becomes Inf or NaN. func Mul[N Number](a, b N) (N, bool) { - if a == 0 || b == 0 { - return 0, true - } c := a * b - return c, (c < 0) == ((a < 0) != (b < 0)) && (c/b == a) + switch any(c).(type) { + case float32, float64: + return c, isValidFloat(c) + default: + if a == 0 || b == 0 { + return 0, true + } + return c, (c < 0) == ((a < 0) != (b < 0)) && (c/b == a) + } } // Div returns the quotient of two numbers and a boolean status. +// For integers, detects wrap-around overflow. +// For floats, detects when the result becomes Inf or NaN. func Div[N Number](a, b N) (N, bool) { if b == 0 { return 0, false } - // The only overflow case is 2^(bits-1)/-1, but we cannot use -1 with - // generics which accept uints. - // Thus, use another property: c == a can be the same for 0/N == 0 or - // N/1 == N. The overflow operation above also results in c == a c := a / b - return c, c != a || b == 1 || a == 0 + switch any(c).(type) { + case float32, float64: + return c, isValidFloat(c) + default: + return c, c != a || b == 1 || a == 0 + } } // Addp returns the sum of two numbers, panicking on overflow. diff --git a/tm2/pkg/overflow/overflow_test.go b/tm2/pkg/overflow/overflow_test.go index 319b796e9d8..bd3b6e0d33b 100644 --- a/tm2/pkg/overflow/overflow_test.go +++ b/tm2/pkg/overflow/overflow_test.go @@ -7,7 +7,7 @@ import ( "testing" ) -func TestSigned(t *testing.T) { +func TestSignedInt(t *testing.T) { var ( nr = big.NewInt(0) // returned when dividing by zero - not representable by int64, @@ -85,7 +85,7 @@ func TestSigned(t *testing.T) { { i8, j8 := int8(i>>56), int8(j>>56) r8, ok := tc.fn8(i8, j8) - errors += checkResult( + errors += checkResultSignedInt( t, tc.opName, i8, j8, r8, ok, tc.bigFn(bi.SetInt64(int64(i8)), bj.SetInt64(int64(j8))), @@ -94,7 +94,7 @@ func TestSigned(t *testing.T) { { i16, j16 := int16(i>>48), int16(j>>48) r16, ok := tc.fn16(i16, j16) - errors += checkResult( + errors += checkResultSignedInt( t, tc.opName, i16, j16, r16, ok, tc.bigFn(bi.SetInt64(int64(i16)), bj.SetInt64(int64(j16))), @@ -103,7 +103,7 @@ func TestSigned(t *testing.T) { { i32, j32 := int32(i>>32), int32(j>>32) r32, ok := tc.fn32(i32, j32) - errors += checkResult( + errors += checkResultSignedInt( t, tc.opName, i32, j32, r32, ok, tc.bigFn(bi.SetInt64(int64(i32)), bj.SetInt64(int64(j32))), @@ -111,7 +111,7 @@ func TestSigned(t *testing.T) { } { r64, ok := tc.fn64(i, j) - errors += checkResult( + errors += checkResultSignedInt( t, tc.opName, i, j, r64, ok, tc.bigFn(bi.SetInt64(i), bj.SetInt64(j)), @@ -128,7 +128,7 @@ func TestSigned(t *testing.T) { } //nolint:thelper // t.Helper significantly slows this down, and the errors already contain enough information. -func checkResult[T int8 | int16 | int32 | int64]( +func checkResultSignedInt[T int8 | int16 | int32 | int64]( t *testing.T, op string, i, j, r T, @@ -149,7 +149,7 @@ func checkResult[T int8 | int16 | int32 | int64]( return 0 } -func TestUnsigned(t *testing.T) { +func TestUnsignedInt(t *testing.T) { var ( nr = big.NewInt(0) // returned when dividing by zero - not representable by uint64, @@ -220,7 +220,7 @@ func TestUnsigned(t *testing.T) { { i8, j8 := uint8(i>>56), uint8(j>>56) r8, ok := tc.fn8(i8, j8) - errors += checkResultUnsigned( + errors += checkResultUnsignedInt( t, tc.opName, i8, j8, r8, ok, tc.bigFn(bi.SetUint64(uint64(i8)), bj.SetUint64(uint64(j8))), @@ -229,7 +229,7 @@ func TestUnsigned(t *testing.T) { { i16, j16 := uint16(i>>48), uint16(j>>48) r16, ok := tc.fn16(i16, j16) - errors += checkResultUnsigned( + errors += checkResultUnsignedInt( t, tc.opName, i16, j16, r16, ok, tc.bigFn(bi.SetUint64(uint64(i16)), bj.SetUint64(uint64(j16))), @@ -238,7 +238,7 @@ func TestUnsigned(t *testing.T) { { i32, j32 := uint32(i>>32), uint32(j>>32) r32, ok := tc.fn32(i32, j32) - errors += checkResultUnsigned( + errors += checkResultUnsignedInt( t, tc.opName, i32, j32, r32, ok, tc.bigFn(bi.SetUint64(uint64(i32)), bj.SetUint64(uint64(j32))), @@ -246,7 +246,7 @@ func TestUnsigned(t *testing.T) { } { r64, ok := tc.fn64(i, j) - errors += checkResultUnsigned( + errors += checkResultUnsignedInt( t, tc.opName, i, j, r64, ok, tc.bigFn(bi.SetUint64(i), bj.SetUint64(j)), @@ -263,7 +263,7 @@ func TestUnsigned(t *testing.T) { } //nolint:thelper // t.Helper significantly slows this down, and the errors already contain enough information. -func checkResultUnsigned[T uint8 | uint16 | uint32 | uint64]( +func checkResultUnsignedInt[T uint8 | uint16 | uint32 | uint64]( t *testing.T, op string, i, j, r T, @@ -286,3 +286,165 @@ func checkResultUnsigned[T uint8 | uint16 | uint32 | uint64]( } return 0 } + +func TestFloat(t *testing.T) { + tt := []struct { + opName string + fn32 func(a, b float32) (float32, bool) + fn64 func(a, b float64) (float64, bool) + // For floats, we validate differently than integers: + // the operation succeeds if result is finite (not Inf/NaN) + shouldSucceed func(a, b, result float64) bool + }{ + { + "Add", + Add[float32], Add[float64], + func(a, b, result float64) bool { + // Addition succeeds if result is finite + return !math.IsInf(result, 0) && !math.IsNaN(result) + }, + }, + { + "Sub", + Sub[float32], Sub[float64], + func(a, b, result float64) bool { + // Subtraction succeeds if result is finite + return !math.IsInf(result, 0) && !math.IsNaN(result) + }, + }, + { + "Mul", + Mul[float32], Mul[float64], + func(a, b, result float64) bool { + // Multiplication succeeds if result is finite + return !math.IsInf(result, 0) && !math.IsNaN(result) + }, + }, + { + "Div", + Div[float32], Div[float64], + func(a, b, result float64) bool { + // Division succeeds if divisor is non-zero and result is finite + if b == 0 { + return false + } + return !math.IsInf(result, 0) && !math.IsNaN(result) + }, + }, + } + + // Edge case values for float testing + mk := func(i int) float64 { + switch i { + case 0: + return 0 + case 1: + return 1 + case 2: + return -1 + case 3: + // Very small positive + return 1e-38 + case 4: + // Very small negative + return -1e-38 + case 5: + // Large positive (close to float32 max) + return 3e38 + case 6: + // Large negative + return -3e38 + case 7: + // Very large (close to float64 max) + return 1e308 + case 8: + // Very large negative + return -1e308 + case 9: + // Max float32 + return math.MaxFloat32 + case 10: + // Max float64 + return math.MaxFloat64 + default: + // Random value scaled by magnitude + magnitude := float64(i % 100) + return (rand.Float64()*2 - 1) * math.Pow(10, magnitude) + } + } + + for _, tc := range tt { + t.Run(tc.opName, func(t *testing.T) { + errors := 0 + // Test fewer iterations than integer tests since float validation is simpler + for i := range 1000 { + a, b := mk(i), mk((i*7)%256) + + // Test float32 + { + a32, b32 := float32(a), float32(b) + // Compute result using float32 precision + var r32Float float32 + switch tc.opName { + case "Add": + r32Float = a32 + b32 + case "Sub": + r32Float = a32 - b32 + case "Mul": + r32Float = a32 * b32 + case "Div": + if b32 != 0 { + r32Float = a32 / b32 + } + } + + r32, ok := tc.fn32(a32, b32) + expOk := tc.shouldSucceed(float64(a32), float64(b32), float64(r32Float)) + + if r32 != r32Float && !(math.IsNaN(float64(r32)) && math.IsNaN(float64(r32Float))) { + t.Errorf("float32: %v %s %v: expected %v got %v", a32, tc.opName, b32, r32Float, r32) + errors++ + } + if ok != expOk { + t.Errorf("float32: %v %s %v: expected ok=%t got ok=%t", a32, tc.opName, b32, expOk, ok) + errors++ + } + } + + // Test float64 + { + var r64Float float64 + switch tc.opName { + case "Add": + r64Float = a + b + case "Sub": + r64Float = a - b + case "Mul": + r64Float = a * b + case "Div": + if b != 0 { + r64Float = a / b + } + } + + r64, ok := tc.fn64(a, b) + expOk := tc.shouldSucceed(a, b, r64Float) + + if r64 != r64Float && !(math.IsNaN(r64) && math.IsNaN(r64Float)) { + t.Errorf("float64: %v %s %v: expected %v got %v", a, tc.opName, b, r64Float, r64) + errors++ + } + if ok != expOk { + t.Errorf("float64: %v %s %v: expected ok=%t got ok=%t", a, tc.opName, b, expOk, ok) + errors++ + } + } + + if errors > 100 { + t.Error("too many errors") + return + } + } + }) + } +} diff --git a/tm2/pkg/sdk/auth/ante.go b/tm2/pkg/sdk/auth/ante.go index 1e2327f0f53..7adc39ec6ee 100644 --- a/tm2/pkg/sdk/auth/ante.go +++ b/tm2/pkg/sdk/auth/ante.go @@ -10,9 +10,9 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/crypto/multisig" "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gnolang/gno/tm2/pkg/store" ) // simulation signature values used to estimate gas consumption @@ -26,7 +26,7 @@ func init() { // SignatureVerificationGasConsumer is the type of function that is used to both consume gas when verifying signatures // and also to accept or reject different types of PubKey's. This is where apps can define their own PubKey -type SignatureVerificationGasConsumer = func(meter store.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) sdk.Result +type SignatureVerificationGasConsumer = func(meter gas.Meter, sig []byte, pubkey crypto.PubKey) sdk.Result type AnteOptions struct { // If verifyGenesisSignatures is false, does not check signatures when Height==0. @@ -76,7 +76,7 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature defer func() { if r := recover(); r != nil { switch ex := r.(type) { - case store.OutOfGasError: + case gas.OutOfGasError: log := fmt.Sprintf( "out of gas in location: %v; gasWanted: %d, gasUsed: %d", ex.Descriptor, tx.Fee.GasWanted, newCtx.GasMeter().GasConsumed(), @@ -84,7 +84,7 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature res = abciResult(std.ErrOutOfGas(log)) res.GasWanted = tx.Fee.GasWanted - res.GasUsed = newCtx.GasMeter().GasConsumed() + res.GasUsed = newCtx.GasMeter().GasDetail() abort = true default: panic(r) @@ -102,7 +102,7 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature return newCtx, abciResult(err), true } - newCtx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*store.Gas(len(newCtx.TxBytes())), "txSize") + newCtx.GasMeter().ConsumeGas(gas.OpTransactionPerByte, float64(len(newCtx.TxBytes()))) if res := ValidateMemo(tx, params); !res.IsOK() { return newCtx, res, true @@ -154,7 +154,7 @@ func NewAnteHandler(ak AccountKeeper, bank BankKeeperI, sigGasConsumer Signature if err != nil { return newCtx, res, true } - signerAccs[i], res = processSig(newCtx, sacc, stdSigs[i], signBytes, simulate, params, sigGasConsumer) + signerAccs[i], res = processSig(newCtx, sacc, stdSigs[i], signBytes, simulate, sigGasConsumer) if !res.IsOK() { return newCtx, res, true } @@ -212,7 +212,7 @@ func ValidateMemo(tx std.Tx, params Params) sdk.Result { // verify the signature and increment the sequence. If the account doesn't // have a pubkey, set it. func processSig( - ctx sdk.Context, acc std.Account, sig std.Signature, signBytes []byte, simulate bool, params Params, + ctx sdk.Context, acc std.Account, sig std.Signature, signBytes []byte, simulate bool, sigGasConsumer SignatureVerificationGasConsumer, ) (updatedAcc std.Account, res sdk.Result) { pubKey, res := ProcessPubKey(acc, sig) @@ -225,7 +225,7 @@ func processSig( return nil, abciResult(std.ErrInternal("setting PubKey on signer's account")) } - if res := sigGasConsumer(ctx.GasMeter(), sig.Signature, pubKey, params); !res.IsOK() { + if res := sigGasConsumer(ctx.GasMeter(), sig.Signature, pubKey); !res.IsOK() { return nil, res } @@ -266,22 +266,22 @@ func ProcessPubKey(acc std.Account, sig std.Signature) (crypto.PubKey, sdk.Resul // based upon the public key type. The cost is fetched from the given params // and is matched by the concrete type. func DefaultSigVerificationGasConsumer( - meter store.GasMeter, sig []byte, pubkey crypto.PubKey, params Params, + meter gas.Meter, sig []byte, pubkey crypto.PubKey, ) sdk.Result { switch pubkey := pubkey.(type) { case ed25519.PubKeyEd25519: - meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + meter.ConsumeGas(gas.OpTransactionSigVerifyEd25519, 1) return sdk.Result{} case secp256k1.PubKeySecp256k1: - meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1") + meter.ConsumeGas(gas.OpTransactionSigVerifySecp256k1, 1) return sdk.Result{} case multisig.PubKeyMultisigThreshold: var multisignature multisig.Multisignature amino.MustUnmarshal(sig, &multisignature) - consumeMultisignatureVerificationGas(meter, multisignature, pubkey, params) + consumeMultisignatureVerificationGas(meter, multisignature, pubkey) return sdk.Result{} default: @@ -289,15 +289,14 @@ func DefaultSigVerificationGasConsumer( } } -func consumeMultisignatureVerificationGas(meter store.GasMeter, +func consumeMultisignatureVerificationGas(meter gas.Meter, sig multisig.Multisignature, pubkey multisig.PubKeyMultisigThreshold, - params Params, ) { size := sig.BitArray.Size() sigIndex := 0 for i := range size { if sig.BitArray.GetIndex(i) { - DefaultSigVerificationGasConsumer(meter, sig.Sigs[sigIndex], pubkey.PubKeys[i], params) + DefaultSigVerificationGasConsumer(meter, sig.Sigs[sigIndex], pubkey.PubKeys[i]) sigIndex++ } } @@ -411,10 +410,10 @@ func SetGasMeter(ctx sdk.Context, gasLimit int64) sdk.Context { // In various cases such as simulation and during the genesis block, we do not // meter any gas utilization. if ctx.BlockHeight() == 0 { - return ctx.WithGasMeter(store.NewInfiniteGasMeter()) + return ctx.WithGasMeter(gas.NewInfiniteMeter(gas.DefaultConfig())) } - return ctx.WithGasMeter(store.NewGasMeter(gasLimit)) + return ctx.WithGasMeter(gas.NewMeter(gasLimit, gas.DefaultConfig())) } // GetSignBytes returns a slice of bytes to sign over for a given transaction diff --git a/tm2/pkg/sdk/auth/ante_test.go b/tm2/pkg/sdk/auth/ante_test.go index ef56885dc3c..1506dcb9e7a 100644 --- a/tm2/pkg/sdk/auth/ante_test.go +++ b/tm2/pkg/sdk/auth/ante_test.go @@ -17,10 +17,10 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/crypto/multisig" "github.com/gnolang/gno/tm2/pkg/crypto/secp256k1" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/sdk" tu "github.com/gnolang/gno/tm2/pkg/sdk/testutils" "github.com/gnolang/gno/tm2/pkg/std" - "github.com/gnolang/gno/tm2/pkg/store" ) // run the tx through the anteHandler and ensure its valid @@ -46,9 +46,9 @@ func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, if reflect.TypeOf(err) == reflect.TypeOf(std.OutOfGasError{}) { // GasWanted set correctly require.Equal(t, tx.Fee.GasWanted, result.GasWanted, "Gas wanted not set correctly") - require.True(t, result.GasUsed > result.GasWanted, "GasUsed not greated than GasWanted") + require.True(t, result.GasUsed.Total.GasConsumed > result.GasWanted, "GasUsed not greated than GasWanted") // Check that context is set correctly - require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly") + require.Equal(t, result.GasUsed.Total.GasConsumed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly") } } @@ -645,34 +645,34 @@ func TestConsumeSignatureVerificationGas(t *testing.T) { } type args struct { - meter store.GasMeter + meter gas.Meter sig []byte pubkey crypto.PubKey params Params } tests := []struct { - name string - args args - gasConsumed int64 - shouldErr bool + name string + args args + cost gas.Cost + shouldErr bool }{ - {"PubKeyEd25519", args{store.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostED25519, false}, - {"PubKeySecp256k1", args{store.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, DefaultSigVerifyCostSecp256k1, false}, - {"Multisig", args{store.NewInfiniteGasMeter(), amino.MustMarshal(multisignature1), multisigKey1, params}, expectedCost1, false}, - {"unknown key", args{store.NewInfiniteGasMeter(), nil, nil, params}, 0, true}, + {"PubKeyEd25519", args{gas.NewInfiniteMeter(gas.DefaultConfig()), nil, ed25519.GenPrivKey().PubKey(), params}, gas.DefaultConfig().Costs[gas.OpTransactionSigVerifyEd25519], false}, + {"PubKeySecp256k1", args{gas.NewInfiniteMeter(gas.DefaultConfig()), nil, secp256k1.GenPrivKey().PubKey(), params}, gas.DefaultConfig().Costs[gas.OpTransactionSigVerifySecp256k1], false}, + {"Multisig", args{gas.NewInfiniteMeter(gas.DefaultConfig()), amino.MustMarshal(multisignature1), multisigKey1, params}, expectedCost1, false}, + {"unknown key", args{gas.NewInfiniteMeter(gas.DefaultConfig()), nil, nil, params}, 0, true}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - res := DefaultSigVerificationGasConsumer(tt.args.meter, tt.args.sig, tt.args.pubkey, tt.args.params) + res := DefaultSigVerificationGasConsumer(tt.args.meter, tt.args.sig, tt.args.pubkey) if tt.shouldErr { require.False(t, res.IsOK()) } else { require.True(t, res.IsOK()) - require.Equal(t, tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed())) + require.Equal(t, gas.Gas(tt.cost), tt.args.meter.GasConsumed(), fmt.Sprintf("%v != %d", tt.cost, tt.args.meter.GasConsumed())) } }) } @@ -694,15 +694,15 @@ func generatePubKeysAndSignatures(n int, msg []byte, keyTypeed25519 bool) (pubke return } -func expectedGasCostByKeys(pubkeys []crypto.PubKey) int64 { - cost := int64(0) +func expectedGasCostByKeys(pubkeys []crypto.PubKey) gas.Cost { + var cost gas.Cost for _, pubkey := range pubkeys { pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey)) switch { case strings.Contains(pubkeyType, "ed25519"): - cost += DefaultParams().SigVerifyCostED25519 + cost += gas.DefaultConfig().Costs[gas.OpTransactionSigVerifyEd25519] case strings.Contains(pubkeyType, "secp256k1"): - cost += DefaultParams().SigVerifyCostSecp256k1 + cost += gas.DefaultConfig().Costs[gas.OpTransactionSigVerifySecp256k1] default: panic("unexpected key type") } @@ -831,10 +831,10 @@ func TestCustomSignatureVerificationGasConsumer(t *testing.T) { // setup env := setupTestEnv() // setup an ante handler that only accepts PubKeyEd25519 - anteHandler := NewAnteHandler(env.acck, env.bankk, func(meter store.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) sdk.Result { + anteHandler := NewAnteHandler(env.acck, env.bankk, func(meter gas.Meter, sig []byte, pubkey crypto.PubKey) sdk.Result { switch pubkey := pubkey.(type) { case ed25519.PubKeyEd25519: - meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") + meter.ConsumeGas(gas.OpTransactionSigVerifyEd25519, 1) return sdk.Result{} default: return abciResult(std.ErrInvalidPubKey(fmt.Sprintf("unrecognized public key type: %T", pubkey))) diff --git a/tm2/pkg/sdk/auth/params.go b/tm2/pkg/sdk/auth/params.go index 25593722e83..621200fc278 100644 --- a/tm2/pkg/sdk/auth/params.go +++ b/tm2/pkg/sdk/auth/params.go @@ -14,11 +14,8 @@ type AuthParamsContextKey struct{} // Default parameter values const ( - DefaultMaxMemoBytes int64 = 65536 - DefaultTxSigLimit int64 = 7 - DefaultTxSizeCostPerByte int64 = 10 - DefaultSigVerifyCostED25519 int64 = 590 - DefaultSigVerifyCostSecp256k1 int64 = 1000 + DefaultMaxMemoBytes int64 = 65536 + DefaultTxSigLimit int64 = 7 DefaultGasPricesChangeCompressor int64 = 10 DefaultTargetGasRatio int64 = 70 // 70% of the MaxGas in a block @@ -30,9 +27,6 @@ const ( type Params struct { MaxMemoBytes int64 `json:"max_memo_bytes" yaml:"max_memo_bytes"` TxSigLimit int64 `json:"tx_sig_limit" yaml:"tx_sig_limit"` - TxSizeCostPerByte int64 `json:"tx_size_cost_per_byte" yaml:"tx_size_cost_per_byte"` - SigVerifyCostED25519 int64 `json:"sig_verify_cost_ed25519" yaml:"sig_verify_cost_ed25519"` - SigVerifyCostSecp256k1 int64 `json:"sig_verify_cost_secp256k1" yaml:"sig_verify_cost_secp256k1"` GasPricesChangeCompressor int64 `json:"gas_price_change_compressor" yaml:"gas_price_change_compressor"` TargetGasRatio int64 `json:"target_gas_ratio" yaml:"target_gas_ratio"` InitialGasPrice std.GasPrice `json:"initial_gasprice"` @@ -41,16 +35,12 @@ type Params struct { } // NewParams creates a new Params object -func NewParams(maxMemoBytes, txSigLimit, txSizeCostPerByte, - sigVerifyCostED25519, sigVerifyCostSecp256k1, gasPricesChangeCompressor, targetGasRatio int64, - feeCollector crypto.Address, +func NewParams(maxMemoBytes, txSigLimit, gasPricesChangeCompressor, + targetGasRatio int64, feeCollector crypto.Address, ) Params { return Params{ MaxMemoBytes: maxMemoBytes, TxSigLimit: txSigLimit, - TxSizeCostPerByte: txSizeCostPerByte, - SigVerifyCostED25519: sigVerifyCostED25519, - SigVerifyCostSecp256k1: sigVerifyCostSecp256k1, GasPricesChangeCompressor: gasPricesChangeCompressor, TargetGasRatio: targetGasRatio, FeeCollector: feeCollector, @@ -67,9 +57,6 @@ func DefaultParams() Params { return NewParams( DefaultMaxMemoBytes, DefaultTxSigLimit, - DefaultTxSizeCostPerByte, - DefaultSigVerifyCostED25519, - DefaultSigVerifyCostSecp256k1, DefaultGasPricesChangeCompressor, DefaultTargetGasRatio, crypto.AddressFromPreimage([]byte(DefaultFeeCollectorName)), @@ -83,9 +70,6 @@ func (p Params) String() string { sb.WriteString("Params: \n") fmt.Fprintf(sb, "MaxMemoBytes: %d\n", p.MaxMemoBytes) fmt.Fprintf(sb, "TxSigLimit: %d\n", p.TxSigLimit) - fmt.Fprintf(sb, "TxSizeCostPerByte: %d\n", p.TxSizeCostPerByte) - fmt.Fprintf(sb, "SigVerifyCostED25519: %d\n", p.SigVerifyCostED25519) - fmt.Fprintf(sb, "SigVerifyCostSecp256k1: %d\n", p.SigVerifyCostSecp256k1) fmt.Fprintf(sb, "GasPricesChangeCompressor: %d\n", p.GasPricesChangeCompressor) fmt.Fprintf(sb, "TargetGasRatio: %d\n", p.TargetGasRatio) fmt.Fprintf(sb, "FeeCollector: %s\n", p.FeeCollector.String()) @@ -99,15 +83,6 @@ func (p Params) Validate() error { if p.TxSigLimit <= 0 { return fmt.Errorf("invalid tx signature limit: %d", p.TxSigLimit) } - if p.SigVerifyCostED25519 <= 0 { - return fmt.Errorf("invalid ED25519 signature verification cost: %d", p.SigVerifyCostED25519) - } - if p.SigVerifyCostSecp256k1 <= 0 { - return fmt.Errorf("invalid SECK256k1 signature verification cost: %d", p.SigVerifyCostSecp256k1) - } - if p.TxSizeCostPerByte <= 0 { - return fmt.Errorf("invalid tx size cost per byte: %d", p.TxSizeCostPerByte) - } if p.GasPricesChangeCompressor <= 0 { return fmt.Errorf("invalid gas prices change compressor: %d, it should be larger or equal to 1", p.GasPricesChangeCompressor) } diff --git a/tm2/pkg/sdk/auth/params_test.go b/tm2/pkg/sdk/auth/params_test.go index 2370234b5f0..282de03b41a 100644 --- a/tm2/pkg/sdk/auth/params_test.go +++ b/tm2/pkg/sdk/auth/params_test.go @@ -20,9 +20,6 @@ func TestValidate(t *testing.T) { params: Params{ MaxMemoBytes: 256, TxSigLimit: 10, - TxSizeCostPerByte: 1, - SigVerifyCostED25519: 100, - SigVerifyCostSecp256k1: 200, GasPricesChangeCompressor: 1, TargetGasRatio: 50, FeeCollector: crypto.AddressFromPreimage([]byte("test_collector")), @@ -36,13 +33,6 @@ func TestValidate(t *testing.T) { }, expectsError: true, }, - { - name: "Invalid SigVerifyCostED25519", - params: Params{ - SigVerifyCostED25519: 0, - }, - expectsError: true, - }, { name: "Invalid GasPricesChangeCompressor", params: Params{ @@ -75,9 +65,6 @@ func TestNewParams(t *testing.T) { // Define expected values for each parameter maxMemoBytes := int64(256) txSigLimit := int64(10) - txSizeCostPerByte := int64(5) - sigVerifyCostED25519 := int64(100) - sigVerifyCostSecp256k1 := int64(200) gasPricesChangeCompressor := int64(50) targetGasRatio := int64(75) feeCollector := crypto.AddressFromPreimage([]byte("test_collector")) @@ -86,9 +73,6 @@ func TestNewParams(t *testing.T) { params := NewParams( maxMemoBytes, txSigLimit, - txSizeCostPerByte, - sigVerifyCostED25519, - sigVerifyCostSecp256k1, gasPricesChangeCompressor, targetGasRatio, feeCollector, @@ -98,9 +82,6 @@ func TestNewParams(t *testing.T) { expectedParams := Params{ MaxMemoBytes: maxMemoBytes, TxSigLimit: txSigLimit, - TxSizeCostPerByte: txSizeCostPerByte, - SigVerifyCostED25519: sigVerifyCostED25519, - SigVerifyCostSecp256k1: sigVerifyCostSecp256k1, GasPricesChangeCompressor: gasPricesChangeCompressor, TargetGasRatio: targetGasRatio, FeeCollector: feeCollector, @@ -118,11 +99,10 @@ func TestParamsString(t *testing.T) { params Params want string }{ - {"blank params", Params{}, "Params: \nMaxMemoBytes: 0\nTxSigLimit: 0\nTxSizeCostPerByte: 0\nSigVerifyCostED25519: 0\nSigVerifyCostSecp256k1: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\nFeeCollector: g1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqluuxe\n"}, + {"blank params", Params{}, "Params: \nMaxMemoBytes: 0\nTxSigLimit: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\nFeeCollector: g1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqluuxe\n"}, {"some values", Params{ - MaxMemoBytes: 1_000_000, - TxSizeCostPerByte: 8192, - }, "Params: \nMaxMemoBytes: 1000000\nTxSigLimit: 0\nTxSizeCostPerByte: 8192\nSigVerifyCostED25519: 0\nSigVerifyCostSecp256k1: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\nFeeCollector: g1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqluuxe\n"}, + MaxMemoBytes: 1_000_000, + }, "Params: \nMaxMemoBytes: 1000000\nTxSigLimit: 0\nGasPricesChangeCompressor: 0\nTargetGasRatio: 0\nFeeCollector: g1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqluuxe\n"}, } for _, tt := range cases { diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index c37241fcf60..aca2a7cd6f7 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -14,6 +14,7 @@ import ( bft "github.com/gnolang/gno/tm2/pkg/bft/types" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" ) @@ -340,7 +341,7 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC // add block gas meter for any genesis transactions (allow infinite gas) app.deliverState.ctx = app.deliverState.ctx. - WithBlockGasMeter(store.NewInfiniteGasMeter()) + WithBlockGasMeter(gas.NewInfiniteMeter(gas.DefaultConfig())) // Run the set chain initializer res = app.initChainer(app.deliverState.ctx, req) @@ -546,11 +547,11 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg } // add block gas meter - var gasMeter store.GasMeter + var gasMeter gas.Meter if maxGas := app.getMaximumBlockGas(); maxGas > 0 { - gasMeter = store.NewGasMeter(maxGas) + gasMeter = gas.NewMeter(maxGas, gas.DefaultConfig()) } else { - gasMeter = store.NewInfiniteGasMeter() + gasMeter = gas.NewInfiniteMeter(gas.DefaultConfig()) } app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(gasMeter) @@ -701,7 +702,7 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res result.Events = events result.Info = strings.Join(msgInfos, "\n") result.Log = strings.Join(msgLogs, "\n") - result.GasUsed = ctx.GasMeter().GasConsumed() + result.GasUsed = ctx.GasMeter().GasDetail() return result } @@ -741,9 +742,10 @@ func (app *BaseApp) runTx(ctx Context, tx Tx) (result Result) { if mode == RunTxModeDeliver { gasleft := ctx.BlockGasMeter().Remaining() - ctx = ctx.WithGasMeter(store.NewPassthroughGasMeter( + ctx = ctx.WithGasMeter(gas.NewPassthroughMeter( ctx.GasMeter(), gasleft, + gas.DefaultConfig(), )) } @@ -753,15 +755,10 @@ func (app *BaseApp) runTx(ctx Context, tx Tx) (result Result) { return } - var startingGas int64 - if mode == RunTxModeDeliver { - startingGas = ctx.BlockGasMeter().GasConsumed() - } - defer func() { if r := recover(); r != nil { switch ex := r.(type) { - case store.OutOfGasError: + case gas.OutOfGasError: log := fmt.Sprintf( "out of gas, gasWanted: %d, gasUsed: %d location: %v", gasWanted, @@ -771,20 +768,20 @@ func (app *BaseApp) runTx(ctx Context, tx Tx) (result Result) { result.Error = ABCIError(std.ErrOutOfGas(log)) result.Log = log result.GasWanted = gasWanted - result.GasUsed = ctx.GasMeter().GasConsumed() + result.GasUsed = ctx.GasMeter().GasDetail() return default: log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) result.Error = ABCIError(std.ErrInternal(log)) result.Log = log result.GasWanted = gasWanted - result.GasUsed = ctx.GasMeter().GasConsumed() + result.GasUsed = ctx.GasMeter().GasDetail() return } } // Whether AnteHandler panics or not. result.GasWanted = gasWanted - result.GasUsed = ctx.GasMeter().GasConsumed() + result.GasUsed = ctx.GasMeter().GasDetail() }() // If BlockGasMeter() panics it will be caught by the above recover and will @@ -795,13 +792,9 @@ func (app *BaseApp) runTx(ctx Context, tx Tx) (result Result) { defer func() { if mode == RunTxModeDeliver { ctx.BlockGasMeter().ConsumeGas( - ctx.GasMeter().GasConsumedToLimit(), - "block gas meter", + gas.OpBlockGasSum, + float64(ctx.GasMeter().GasConsumedToLimit()), ) - - if ctx.BlockGasMeter().GasConsumed() < startingGas { - panic(std.ErrGasOverflow("tx gas summation")) - } } }() diff --git a/tm2/pkg/sdk/baseapp_test.go b/tm2/pkg/sdk/baseapp_test.go index 09bad629d6a..fe68f9b0d20 100644 --- a/tm2/pkg/sdk/baseapp_test.go +++ b/tm2/pkg/sdk/baseapp_test.go @@ -17,6 +17,7 @@ import ( bft "github.com/gnolang/gno/tm2/pkg/bft/types" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/sdk/testutils" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" @@ -658,7 +659,7 @@ func TestGasUsedBetweenSimulateAndDeliver(t *testing.T) { simulateRes := app.Simulate(txBytes, tx) require.True(t, simulateRes.IsOK(), fmt.Sprintf("%v", simulateRes)) - require.Greater(t, simulateRes.GasUsed, int64(0)) // gas used should be greater than 0 + require.Greater(t, simulateRes.GasUsed.Total.GasConsumed, int64(0)) // gas used should be greater than 0 deliverRes := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.True(t, deliverRes.IsOK(), fmt.Sprintf("%v", deliverRes)) @@ -740,15 +741,15 @@ func TestSimulateTx(t *testing.T) { anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { limit := gasConsumed - newCtx = ctx.WithGasMeter(store.NewGasMeter(limit)) + newCtx = ctx.WithGasMeter(gas.NewMeter(limit, gas.DefaultConfig())) return }) } routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(routeMsgCounter, newTestHandler(func(ctx Context, msg Msg) Result { - ctx.GasMeter().ConsumeGas(gasConsumed, "test") - return Result{GasUsed: ctx.GasMeter().GasConsumed()} + ctx.GasMeter().ConsumeGas(gas.OpTesting, float64(gasConsumed)) + return Result{GasUsed: ctx.GasMeter().GasDetail()} })) } @@ -769,12 +770,12 @@ func TestSimulateTx(t *testing.T) { // simulate a message, check gas reported result := app.Simulate(txBytes, tx) require.True(t, result.IsOK(), result.Log) - require.Equal(t, gasConsumed, result.GasUsed) + require.Equal(t, gasConsumed, result.GasUsed.Total.GasConsumed) // simulate again, same result result = app.Simulate(txBytes, tx) require.True(t, result.IsOK(), result.Log) - require.Equal(t, gasConsumed, result.GasUsed) + require.Equal(t, gasConsumed, result.GasUsed.Total.GasConsumed) // simulate by calling Query with encoded tx query := abci.RequestQuery{ @@ -788,7 +789,7 @@ func TestSimulateTx(t *testing.T) { require.NoError(t, amino.Unmarshal(queryResult.Value, &res)) require.Nil(t, err, "Result unmarshalling failed") require.True(t, res.IsOK(), res.Log) - require.Equal(t, gasConsumed, res.GasUsed, res.Log) + require.Equal(t, gasConsumed, res.GasUsed.Total.GasConsumed, res.Log) app.EndBlock(abci.RequestEndBlock{}) app.Commit() } @@ -877,14 +878,15 @@ func TestTxGasLimits(t *testing.T) { gasGranted := int64(10) anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { - gmeter := store.NewPassthroughGasMeter( + gmeter := gas.NewPassthroughMeter( ctx.GasMeter(), gasGranted, + gas.DefaultConfig(), ) newCtx = ctx.WithGasMeter(gmeter) count := getCounter(tx) - newCtx.GasMeter().ConsumeGas(count, "counter-ante") + newCtx.GasMeter().ConsumeGas(gas.OpTesting, float64(count)) res = Result{ GasWanted: gasGranted, } @@ -895,7 +897,7 @@ func TestTxGasLimits(t *testing.T) { routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(routeMsgCounter, newTestHandler(func(ctx Context, msg Msg) Result { count := msg.(msgCounter).Counter - ctx.GasMeter().ConsumeGas(count, "counter-handler") + ctx.GasMeter().ConsumeGas(gas.OpTesting, float64(count)) return Result{} })) } @@ -934,7 +936,7 @@ func TestTxGasLimits(t *testing.T) { res := app.Deliver(tx) // check gas used and wanted - require.Equal(t, tc.gasUsed, res.GasUsed, fmt.Sprintf("%d: %v, %v", i, tc, res)) + require.Equal(t, tc.gasUsed, res.GasUsed.Total.GasConsumed, fmt.Sprintf("%d: %v, %v", i, tc, res)) // check for out of gas if !tc.fail { @@ -953,14 +955,15 @@ func TestMaxBlockGasLimits(t *testing.T) { gasGranted := int64(10) anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { - gmeter := store.NewPassthroughGasMeter( + gmeter := gas.NewPassthroughMeter( ctx.GasMeter(), gasGranted, + gas.DefaultConfig(), ) newCtx = ctx.WithGasMeter(gmeter) count := getCounter(tx) - newCtx.GasMeter().ConsumeGas(count, "counter-ante") + newCtx.GasMeter().ConsumeGas(gas.OpTesting, float64(count)) res = Result{ GasWanted: gasGranted, } @@ -971,7 +974,7 @@ func TestMaxBlockGasLimits(t *testing.T) { routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(routeMsgCounter, newTestHandler(func(ctx Context, msg Msg) Result { count := msg.(msgCounter).Counter - ctx.GasMeter().ConsumeGas(count, "counter-handler") + ctx.GasMeter().ConsumeGas(gas.OpTesting, float64(count)) return Result{} })) } @@ -1114,13 +1117,14 @@ func TestGasConsumptionBadTx(t *testing.T) { gasWanted := int64(5) anteOpt := func(bapp *BaseApp) { bapp.SetAnteHandler(func(ctx Context, tx Tx, simulate bool) (newCtx Context, res Result, abort bool) { - gmeter := store.NewPassthroughGasMeter( + gmeter := gas.NewPassthroughMeter( ctx.GasMeter(), gasWanted, + gas.DefaultConfig(), ) newCtx = ctx.WithGasMeter(gmeter) - newCtx.GasMeter().ConsumeGas(getCounter(tx), "counter-ante") + newCtx.GasMeter().ConsumeGas(gas.OpTesting, float64(getCounter(tx))) if getFailOnAnte(tx) { res.Error = ABCIError(std.ErrInternal("ante handler failure")) return newCtx, res, true @@ -1136,7 +1140,7 @@ func TestGasConsumptionBadTx(t *testing.T) { routerOpt := func(bapp *BaseApp) { bapp.Router().AddRoute(routeMsgCounter, newTestHandler(func(ctx Context, msg Msg) Result { count := msg.(msgCounter).Counter - ctx.GasMeter().ConsumeGas(count, "counter-handler") + ctx.GasMeter().ConsumeGas(gas.OpTesting, float64(count)) return Result{} })) } diff --git a/tm2/pkg/sdk/context.go b/tm2/pkg/sdk/context.go index 9e09ace26c6..87baaea25b4 100644 --- a/tm2/pkg/sdk/context.go +++ b/tm2/pkg/sdk/context.go @@ -7,8 +7,9 @@ import ( "github.com/gnolang/gno/tm2/pkg/amino" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/store" - "github.com/gnolang/gno/tm2/pkg/store/gas" + gasstore "github.com/gnolang/gno/tm2/pkg/store/gas" ) /* @@ -28,8 +29,8 @@ type Context struct { txBytes []byte logger *slog.Logger voteInfo []abci.VoteInfo - gasMeter store.GasMeter // XXX make passthroughGasMeter w/ blockGasMeter? - blockGasMeter store.GasMeter + gasMeter gas.Meter // XXX make passthroughGasMeter w/ blockGasMeter? + blockGasMeter gas.Meter minGasPrices []GasPrice consParams *abci.ConsensusParams eventLogger *EventLogger @@ -39,20 +40,20 @@ type Context struct { type Request = Context // Read-only accessors -func (c Context) Context() context.Context { return c.ctx } -func (c Context) Mode() RunTxMode { return c.mode } -func (c Context) MultiStore() store.MultiStore { return c.ms } -func (c Context) BlockHeight() int64 { return c.header.GetHeight() } -func (c Context) BlockTime() time.Time { return c.header.GetTime() } -func (c Context) ChainID() string { return c.chainID } -func (c Context) TxBytes() []byte { return c.txBytes } -func (c Context) Logger() *slog.Logger { return c.logger } -func (c Context) VoteInfos() []abci.VoteInfo { return c.voteInfo } -func (c Context) GasMeter() store.GasMeter { return c.gasMeter } -func (c Context) BlockGasMeter() store.GasMeter { return c.blockGasMeter } -func (c Context) IsCheckTx() bool { return c.mode == RunTxModeCheck } -func (c Context) MinGasPrices() []GasPrice { return c.minGasPrices } -func (c Context) EventLogger() *EventLogger { return c.eventLogger } +func (c Context) Context() context.Context { return c.ctx } +func (c Context) Mode() RunTxMode { return c.mode } +func (c Context) MultiStore() store.MultiStore { return c.ms } +func (c Context) BlockHeight() int64 { return c.header.GetHeight() } +func (c Context) BlockTime() time.Time { return c.header.GetTime() } +func (c Context) ChainID() string { return c.chainID } +func (c Context) TxBytes() []byte { return c.txBytes } +func (c Context) Logger() *slog.Logger { return c.logger } +func (c Context) VoteInfos() []abci.VoteInfo { return c.voteInfo } +func (c Context) GasMeter() gas.Meter { return c.gasMeter } +func (c Context) BlockGasMeter() gas.Meter { return c.blockGasMeter } +func (c Context) IsCheckTx() bool { return c.mode == RunTxModeCheck } +func (c Context) MinGasPrices() []GasPrice { return c.minGasPrices } +func (c Context) EventLogger() *EventLogger { return c.eventLogger } // clone the header before returning func (c Context) BlockHeader() abci.Header { @@ -76,7 +77,7 @@ func NewContext(mode RunTxMode, ms store.MultiStore, header abci.Header, logger header: header, chainID: header.GetChainID(), logger: logger, - gasMeter: store.NewInfiniteGasMeter(), + gasMeter: gas.NewInfiniteMeter(gas.DefaultConfig()), minGasPrices: nil, eventLogger: NewEventLogger(), } @@ -122,12 +123,12 @@ func (c Context) WithVoteInfos(voteInfo []abci.VoteInfo) Context { return c } -func (c Context) WithGasMeter(meter store.GasMeter) Context { +func (c Context) WithGasMeter(meter gas.Meter) Context { c.gasMeter = meter return c } -func (c Context) WithBlockGasMeter(meter store.GasMeter) Context { +func (c Context) WithBlockGasMeter(meter gas.Meter) Context { c.blockGasMeter = meter return c } @@ -172,7 +173,7 @@ func (c Context) Value(key any) any { // Store fetches a Store from the MultiStore, but wrapped for gas calculation. func (c Context) GasStore(key store.StoreKey) store.Store { - return gas.New(c.MultiStore().GetStore(key), c.GasMeter(), store.DefaultGasConfig()) + return gasstore.New(c.MultiStore().GetStore(key), c.GasMeter()) } func (c Context) Store(key store.StoreKey) store.Store { diff --git a/tm2/pkg/sdk/types.go b/tm2/pkg/sdk/types.go index 47395362f1a..d8985613e57 100644 --- a/tm2/pkg/sdk/types.go +++ b/tm2/pkg/sdk/types.go @@ -2,6 +2,7 @@ package sdk import ( abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -24,7 +25,7 @@ type Handler interface { type Result struct { abci.ResponseBase GasWanted int64 - GasUsed int64 + GasUsed gas.GasDetail } // AnteHandler authenticates transactions, before their internal messages are handled. diff --git a/tm2/pkg/store/exports.go b/tm2/pkg/store/exports.go index d6e3b32bc25..a313232e67d 100644 --- a/tm2/pkg/store/exports.go +++ b/tm2/pkg/store/exports.go @@ -19,22 +19,13 @@ type ( StoreKey = types.StoreKey StoreOptions = types.StoreOptions Queryable = types.Queryable - Gas = types.Gas - GasMeter = types.GasMeter - GasConfig = types.GasConfig - OutOfGasError = types.OutOfGasError - GasOverflowError = types.GasOverflowError ) var ( - PruneNothing = types.PruneNothing - PruneEverything = types.PruneEverything - PruneSyncable = types.PruneSyncable - NewGasMeter = types.NewGasMeter - NewInfiniteGasMeter = types.NewInfiniteGasMeter - NewPassthroughGasMeter = types.NewPassthroughGasMeter - DefaultGasConfig = types.DefaultGasConfig - PrefixIterator = types.PrefixIterator - ReversePrefixIterator = types.ReversePrefixIterator - NewStoreKey = types.NewStoreKey + PruneNothing = types.PruneNothing + PruneEverything = types.PruneEverything + PruneSyncable = types.PruneSyncable + PrefixIterator = types.PrefixIterator + ReversePrefixIterator = types.ReversePrefixIterator + NewStoreKey = types.NewStoreKey ) diff --git a/tm2/pkg/store/gas/store.go b/tm2/pkg/store/gas/store.go index 3bffe25ef79..15b4ecbaac4 100644 --- a/tm2/pkg/store/gas/store.go +++ b/tm2/pkg/store/gas/store.go @@ -1,7 +1,7 @@ package gas import ( - "github.com/gnolang/gno/tm2/pkg/overflow" + gasutil "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/store/types" "github.com/gnolang/gno/tm2/pkg/store/utils" ) @@ -11,52 +11,46 @@ var _ types.Store = &Store{} // Store applies gas tracking to an underlying Store. It implements the // Store interface. type Store struct { - gasMeter types.GasMeter - gasConfig types.GasConfig - parent types.Store + gasMeter gasutil.Meter + parent types.Store } // New returns a reference to a new GasStore. -func New(parent types.Store, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store { +// The gas configuration is obtained from the meter. +func New(parent types.Store, gasMeter gasutil.Meter) *Store { kvs := &Store{ - gasMeter: gasMeter, - gasConfig: gasConfig, - parent: parent, + gasMeter: gasMeter, + parent: parent, } return kvs } // Implements Store. func (gs *Store) Get(key []byte) (value []byte) { - gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc) + gs.gasMeter.ConsumeGas(gasutil.OpStoreReadFlat, 1) value = gs.parent.Get(key) - - gas := overflow.Mulp(gs.gasConfig.ReadCostPerByte, types.Gas(len(value))) - gs.gasMeter.ConsumeGas(gas, types.GasReadPerByteDesc) - + gs.gasMeter.ConsumeGas(gasutil.OpStoreReadPerByte, float64(len(value))) return value } // Implements Store. func (gs *Store) Set(key []byte, value []byte) { types.AssertValidValue(value) - gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc) - - gas := overflow.Mulp(gs.gasConfig.WriteCostPerByte, types.Gas(len(value))) - gs.gasMeter.ConsumeGas(gas, types.GasWritePerByteDesc) + gs.gasMeter.ConsumeGas(gasutil.OpStoreWriteFlat, 1) + gs.gasMeter.ConsumeGas(gasutil.OpStoreWritePerByte, float64(len(value))) gs.parent.Set(key, value) } // Implements Store. func (gs *Store) Has(key []byte) bool { - gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc) + gs.gasMeter.ConsumeGas(gasutil.OpStoreHas, 1) return gs.parent.Has(key) } // Implements Store. func (gs *Store) Delete(key []byte) { // charge gas to prevent certain attack vectors even though space is being freed - gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, types.GasDeleteDesc) + gs.gasMeter.ConsumeGas(gasutil.OpStoreDelete, 1) gs.parent.Delete(key) } @@ -93,7 +87,7 @@ func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator { parent = gs.parent.ReverseIterator(start, end) } - gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent) + gi := newGasIterator(gs.gasMeter, parent) if gi.Valid() { gi.(*gasIterator).consumeSeekGas() } @@ -118,16 +112,14 @@ func (gs *Store) Flush() { } type gasIterator struct { - gasMeter types.GasMeter - gasConfig types.GasConfig - parent types.Iterator + gasMeter gasutil.Meter + parent types.Iterator } -func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator) types.Iterator { +func newGasIterator(gasMeter gasutil.Meter, parent types.Iterator) types.Iterator { return &gasIterator{ - gasMeter: gasMeter, - gasConfig: gasConfig, - parent: parent, + gasMeter: gasMeter, + parent: parent, } } @@ -179,7 +171,6 @@ func (gi *gasIterator) Close() error { // based on the current value's length. func (gi *gasIterator) consumeSeekGas() { value := gi.Value() - gas := overflow.Mulp(gi.gasConfig.ReadCostPerByte, types.Gas(len(value))) - gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, types.GasIterNextCostFlatDesc) - gi.gasMeter.ConsumeGas(gas, types.GasValuePerByteDesc) + gi.gasMeter.ConsumeGas(gasutil.OpStoreIterNextFlat, 1) + gi.gasMeter.ConsumeGas(gasutil.OpStoreValuePerByte, float64(len(value))) } diff --git a/tm2/pkg/store/gas/store_test.go b/tm2/pkg/store/gas/store_test.go index 52c8dbf08e8..70c7e6bef67 100644 --- a/tm2/pkg/store/gas/store_test.go +++ b/tm2/pkg/store/gas/store_test.go @@ -5,9 +5,9 @@ import ( "testing" "github.com/gnolang/gno/tm2/pkg/db/memdb" + gasutil "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" "github.com/gnolang/gno/tm2/pkg/store/gas" - "github.com/gnolang/gno/tm2/pkg/store/types" "github.com/stretchr/testify/require" ) @@ -21,22 +21,22 @@ func TestGasKVStoreBasic(t *testing.T) { t.Parallel() mem := dbadapter.Store{DB: memdb.NewMemDB()} - meter := types.NewGasMeter(10000) - st := gas.New(mem, meter, types.DefaultGasConfig()) + meter := gasutil.NewMeter(10000, gasutil.DefaultConfig()) + st := gas.New(mem, meter) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") st.Set(keyFmt(1), valFmt(1)) require.Equal(t, valFmt(1), st.Get(keyFmt(1))) st.Delete(keyFmt(1)) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") - require.Equal(t, meter.GasConsumed(), types.Gas(6429)) + require.Equal(t, meter.GasConsumed(), gasutil.Gas(6429)) } func TestGasKVStoreIterator(t *testing.T) { t.Parallel() mem := dbadapter.Store{DB: memdb.NewMemDB()} - meter := types.NewGasMeter(10000) - st := gas.New(mem, meter, types.DefaultGasConfig()) + meter := gasutil.NewMeter(10000, gasutil.DefaultConfig()) + st := gas.New(mem, meter) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty") st.Set(keyFmt(1), valFmt(1)) @@ -54,15 +54,15 @@ func TestGasKVStoreIterator(t *testing.T) { iterator.Next() require.False(t, iterator.Valid()) require.Panics(t, iterator.Next) - require.Equal(t, meter.GasConsumed(), types.Gas(6987)) + require.Equal(t, meter.GasConsumed(), gasutil.Gas(6987)) } func TestGasKVStoreOutOfGasSet(t *testing.T) { t.Parallel() mem := dbadapter.Store{DB: memdb.NewMemDB()} - meter := types.NewGasMeter(0) - st := gas.New(mem, meter, types.DefaultGasConfig()) + meter := gasutil.NewMeter(0, gasutil.DefaultConfig()) + st := gas.New(mem, meter) require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas") } @@ -70,8 +70,8 @@ func TestGasKVStoreOutOfGasIterator(t *testing.T) { t.Parallel() mem := dbadapter.Store{DB: memdb.NewMemDB()} - meter := types.NewGasMeter(20000) - st := gas.New(mem, meter, types.DefaultGasConfig()) + meter := gasutil.NewMeter(20000, gasutil.DefaultConfig()) + st := gas.New(mem, meter) st.Set(keyFmt(1), valFmt(1)) iterator := st.Iterator(nil, nil) iterator.Next() diff --git a/tm2/pkg/store/prefix/store_test.go b/tm2/pkg/store/prefix/store_test.go index 6a6f2e30eff..beffaf0256a 100644 --- a/tm2/pkg/store/prefix/store_test.go +++ b/tm2/pkg/store/prefix/store_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/gnolang/gno/tm2/pkg/db/memdb" + gasutil "github.com/gnolang/gno/tm2/pkg/gas" "github.com/gnolang/gno/tm2/pkg/iavl" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" "github.com/gnolang/gno/tm2/pkg/store/gas" @@ -104,9 +105,9 @@ func TestIAVLStorePrefix(t *testing.T) { func TestPrefixStoreNoNilSet(t *testing.T) { t.Parallel() - meter := types.NewGasMeter(100000000) + meter := gasutil.NewMeter(100000000, gasutil.DefaultConfig()) mem := dbadapter.Store{DB: memdb.NewMemDB()} - gasStore := gas.New(mem, meter, types.DefaultGasConfig()) + gasStore := gas.New(mem, meter) require.Panics(t, func() { gasStore.Set([]byte("key"), nil) }, "setting a nil value should panic") } diff --git a/tm2/pkg/store/types/gas.go b/tm2/pkg/store/types/gas.go deleted file mode 100644 index 89281cb4074..00000000000 --- a/tm2/pkg/store/types/gas.go +++ /dev/null @@ -1,239 +0,0 @@ -package types - -import ( - "math" - - "github.com/gnolang/gno/tm2/pkg/overflow" -) - -// Gas consumption descriptors. -const ( - GasIterNextCostFlatDesc = "IterNextFlat" - GasValuePerByteDesc = "ValuePerByte" - GasWritePerByteDesc = "WritePerByte" - GasReadPerByteDesc = "ReadPerByte" - GasWriteCostFlatDesc = "WriteFlat" - GasReadCostFlatDesc = "ReadFlat" - GasHasDesc = "Has" - GasDeleteDesc = "Delete" -) - -// Gas measured by the SDK -type Gas = int64 - -// OutOfGasError defines an error thrown when an action results in out of gas. -type OutOfGasError struct { - Descriptor string -} - -func (oog OutOfGasError) Error() string { - return "out of gas in location: " + oog.Descriptor -} - -// GasOverflowError defines an error thrown when an action results gas consumption -// unsigned integer overflow. -type GasOverflowError struct { - Descriptor string -} - -func (oog GasOverflowError) Error() string { - return "gas overflow in location: " + oog.Descriptor -} - -// GasMeter interface to track gas consumption -type GasMeter interface { - GasConsumed() Gas - GasConsumedToLimit() Gas - Limit() Gas - Remaining() Gas - ConsumeGas(amount Gas, descriptor string) - IsPastLimit() bool - IsOutOfGas() bool -} - -//---------------------------------------- -// basicGasMeter - -type basicGasMeter struct { - limit Gas - consumed Gas -} - -// NewGasMeter returns a reference to a new basicGasMeter. -func NewGasMeter(limit Gas) *basicGasMeter { - if limit < 0 { - panic("gas must not be negative") - } - return &basicGasMeter{ - limit: limit, - consumed: 0, - } -} - -func (g *basicGasMeter) GasConsumed() Gas { - return g.consumed -} - -func (g *basicGasMeter) Limit() Gas { - return g.limit -} - -func (g *basicGasMeter) Remaining() Gas { - return overflow.Subp(g.Limit(), g.GasConsumedToLimit()) -} - -func (g *basicGasMeter) GasConsumedToLimit() Gas { - if g.IsPastLimit() { - return g.limit - } - return g.consumed -} - -// TODO rename to DidConsumeGas. -func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) { - if amount < 0 { - panic("gas must not be negative") - } - consumed, ok := overflow.Add(g.consumed, amount) - if !ok { - panic(GasOverflowError{descriptor}) - } - // consume gas even if out of gas. - // corollary, call (Did)ConsumeGas after consumption. - g.consumed = consumed - if consumed > g.limit { - panic(OutOfGasError{descriptor}) - } -} - -func (g *basicGasMeter) IsPastLimit() bool { - return g.consumed > g.limit -} - -func (g *basicGasMeter) IsOutOfGas() bool { - return g.consumed >= g.limit -} - -//---------------------------------------- -// infiniteGasMeter - -type infiniteGasMeter struct { - consumed Gas -} - -// NewInfiniteGasMeter returns a reference to a new infiniteGasMeter. -func NewInfiniteGasMeter() GasMeter { - return &infiniteGasMeter{ - consumed: 0, - } -} - -func (g *infiniteGasMeter) GasConsumed() Gas { - return g.consumed -} - -func (g *infiniteGasMeter) GasConsumedToLimit() Gas { - return g.consumed -} - -func (g *infiniteGasMeter) Limit() Gas { - return 0 -} - -func (g *infiniteGasMeter) Remaining() Gas { - return math.MaxInt64 -} - -func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { - consumed, ok := overflow.Add(g.consumed, amount) - if !ok { - panic(GasOverflowError{descriptor}) - } - g.consumed = consumed -} - -func (g *infiniteGasMeter) IsPastLimit() bool { - return false -} - -func (g *infiniteGasMeter) IsOutOfGas() bool { - return false -} - -//---------------------------------------- -// passthroughGasMeter - -type passthroughGasMeter struct { - Base GasMeter - Head *basicGasMeter -} - -// NewPassthroughGasMeter has a head basicGasMeter, but also passes through -// consumption to a base basicGasMeter. Limit must be less than -// base.Remaining(). -func NewPassthroughGasMeter(base GasMeter, limit int64) passthroughGasMeter { - if limit < 0 { - panic("gas must not be negative") - } - // limit > base.Remaining() is not checked; so that a panic happens when - // gas is actually consumed. - return passthroughGasMeter{ - Base: base, - Head: NewGasMeter(limit), - } -} - -func (g passthroughGasMeter) GasConsumed() Gas { - return g.Head.GasConsumed() -} - -func (g passthroughGasMeter) Limit() Gas { - return g.Head.Limit() -} - -func (g passthroughGasMeter) Remaining() Gas { - return g.Head.Remaining() -} - -func (g passthroughGasMeter) GasConsumedToLimit() Gas { - return g.Head.GasConsumedToLimit() -} - -func (g passthroughGasMeter) ConsumeGas(amount Gas, descriptor string) { - g.Base.ConsumeGas(amount, descriptor) - g.Head.ConsumeGas(amount, descriptor) -} - -func (g passthroughGasMeter) IsPastLimit() bool { - return g.Head.IsPastLimit() -} - -func (g passthroughGasMeter) IsOutOfGas() bool { - return g.Head.IsOutOfGas() -} - -//---------------------------------------- - -// GasConfig defines gas cost for each operation on KVStores -type GasConfig struct { - HasCost Gas - DeleteCost Gas - ReadCostFlat Gas - ReadCostPerByte Gas - WriteCostFlat Gas - WriteCostPerByte Gas - IterNextCostFlat Gas -} - -// DefaultGasConfig returns a default gas config for KVStores. -func DefaultGasConfig() GasConfig { - return GasConfig{ - HasCost: 1000, - DeleteCost: 1000, - ReadCostFlat: 1000, - ReadCostPerByte: 3, - WriteCostFlat: 2000, - WriteCostPerByte: 30, - IterNextCostFlat: 30, - } -} diff --git a/tm2/pkg/store/types/gas_test.go b/tm2/pkg/store/types/gas_test.go deleted file mode 100644 index 933a36f7dfb..00000000000 --- a/tm2/pkg/store/types/gas_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package types - -import ( - "math" - "testing" - - "github.com/gnolang/gno/tm2/pkg/overflow" - "github.com/stretchr/testify/require" -) - -func TestGasMeter(t *testing.T) { - t.Parallel() - - cases := []struct { - limit Gas - usage []Gas - }{ - {10, []Gas{1, 2, 3, 4}}, - {1000, []Gas{40, 30, 20, 10, 900}}, - {100000, []Gas{99999, 1}}, - {100000000, []Gas{50000000, 40000000, 10000000}}, - {65535, []Gas{32768, 32767}}, - {65536, []Gas{32768, 32767, 1}}, - } - - for tcnum, tc := range cases { - meter := NewGasMeter(tc.limit) - used := int64(0) - - for unum, usage := range tc.usage { - used += usage - require.NotPanics(t, func() { meter.ConsumeGas(usage, "") }, "Not exceeded limit but panicked. tc #%d, usage #%d", tcnum, unum) - require.Equal(t, used, meter.GasConsumed(), "Gas consumption not match. tc #%d, usage #%d", tcnum, unum) - require.Equal(t, used, meter.GasConsumedToLimit(), "Gas consumption (to limit) not match. tc #%d, usage #%d", tcnum, unum) - require.False(t, meter.IsPastLimit(), "Not exceeded limit but got IsPastLimit() true") - if unum < len(tc.usage)-1 { - require.False(t, meter.IsOutOfGas(), "Not yet at limit but got IsOutOfGas() true") - } else { - require.True(t, meter.IsOutOfGas(), "At limit but got IsOutOfGas() false") - } - } - - require.Panics(t, func() { meter.ConsumeGas(1, "") }, "Exceeded but not panicked. tc #%d", tcnum) - require.Equal(t, meter.GasConsumedToLimit(), meter.Limit(), "Gas consumption (to limit) not match limit") - require.Equal(t, meter.GasConsumed(), meter.Limit()+1, "Gas consumption not match limit+1") - } -} - -func TestAddUint64Overflow(t *testing.T) { - t.Parallel() - - testCases := []struct { - a, b int64 - result int64 - overflow bool - }{ - {0, 0, 0, false}, - {100, 100, 200, false}, - {math.MaxInt64 / 2, math.MaxInt64/2 + 1, math.MaxInt64, false}, - {math.MaxInt64 / 2, math.MaxInt64/2 + 2, math.MinInt64, true}, - } - - for i, tc := range testCases { - res, ok := overflow.Add(tc.a, tc.b) - overflow := !ok - require.Equal( - t, tc.overflow, overflow, - "invalid overflow result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, - ) - require.Equal( - t, tc.result, res, - "invalid int64 result; tc: #%d, a: %d, b: %d", i, tc.a, tc.b, - ) - } -}